#!/usr/bin/env python3 """Disposable, network-isolated integration gate; never mounts existing Computer data.""" import os from pathlib import Path import subprocess import tempfile import uuid ROOT = Path(__file__).resolve().parents[1] if not os.environ.get('DATABASE_URL'): raise SystemExit('DATABASE_URL must point to a disposable test PostgreSQL server; the API recovery gate creates SQLx test databases') name = 'lazyboy-plan-runner-test-' + uuid.uuid4().hex[:12] def run(*args, **kwargs): return subprocess.run(args, check=True, cwd=ROOT, **kwargs) run('python3', 'tests/runner-bootstrap.test.py') run('python3', 'tests/runtime-probe.test.py') run('python3', 'tests/gui-fences.test.py') run('python3', 'tests/file-process-proof.test.py') run('python3', 'tests/file-outbox-scan.test.py') run('python3', 'tests/file-retirement-proof.test.py') run('python3', 'tests/file-journal-capacity.test.py') run('python3', 'tests/file-artifacts.test.py') created = False builder_created = False temporary = tempfile.TemporaryDirectory(prefix='lazyboy-mcp-test-') try: build_image = os.environ.get('LAZYBOY_TEST_MCP_IMAGE', 'lazyboy/controld:plan-test') if 'LAZYBOY_TEST_MCP_IMAGE' not in os.environ: run('docker', 'build', '-f', 'image/computer/Dockerfile', '--target', 'controld', '-t', build_image, '.') run('docker', 'create', '--name', name + '-build', build_image) builder_created = True binary = str(Path(temporary.name) / 'lazyboy-controld') run('docker', 'cp', name + '-build:/lazyboy-controld', binary) runtime_mounts = [] for key, target, mode in [('runner','/var/lib/lazyboy-runner',0o700),('packages','/opt/lazyboy-tools',0o755),('private-packages','/opt/lazyboy-private-tools',0o711),('tool-state','/var/lib/lazyboy-tool-state',0o711)]: directory=Path(temporary.name)/'runtime'/name/key directory.mkdir(parents=True,mode=mode) runtime_mounts.extend(['--mount',f'type=bind,source={directory},target={target}']) computer_args=['docker','run','-d','--name',name,'--network','none','--cpus','2', '--memory','512m','--pids-limit','256','--user','0:0','--label',f'lazyboy.homeKey={name}','--entrypoint', 'sleep',os.environ.get('LAZYBOY_COMPUTER_IMAGE','lazyboy/computer:local'),'3600'] run(*computer_args) created = True run('docker', 'exec', name, 'mkdir', '-m', '700', '-p', '/var/lib/lazyboy-runner') runner_source = Path(temporary.name) / 'runner_jobs.py' runner_source.write_text('RUNTIME_PROBE_SOURCE = ' + repr((ROOT / 'crates/supervisor/src/runtime_probe.py').read_text()) + '\n' + 'FILE_HELPER_SOURCE = ' + repr((ROOT / 'crates/supervisor/src/computer_files.py').read_text()) + '\n' + '\n'.join((ROOT / ('crates/supervisor/src/' + part)).read_text() for part in ['runner_packages.py','runner_files.py','runner_gui_fences.py','runner_jobs.py'])) run('docker', 'cp', str(runner_source), name + ':/var/lib/lazyboy-runner/runner_jobs.py') run('docker', 'cp', 'tests/runner-jobs.test.py', name + ':/tmp/runner-jobs.test.py') run('docker', 'cp', 'tests/runner-files.test.py', name + ':/tmp/runner-files.test.py') run('docker', 'cp', binary, name + ':/usr/local/bin/lazyboy-controld') run('docker', 'cp', 'tests/mcp-http.test.py', name + ':/tmp/mcp-http.test.py') run('docker', 'cp', 'tests/mcp-packages.test.py', name + ':/tmp/mcp-packages.test.py') run('docker', 'cp', 'tests/gui-targets.test.py', name + ':/tmp/gui-targets.test.py') run('docker', 'exec', '-e', 'LAZYBOY_RUNNER_TEST_CONTAINER=1', name, 'python3', '/tmp/gui-targets.test.py') run('docker', 'exec', '-e', 'LAZYBOY_RUNNER_TEST_CONTAINER=1', name, 'python3', '/tmp/runner-jobs.test.py') run('docker', 'exec', '-e', 'LAZYBOY_RUNNER_TEST_CONTAINER=1', name, 'python3', '/tmp/runner-files.test.py') run('docker', 'exec', '-e', 'LAZYBOY_RUNNER_TEST_CONTAINER=1', name, 'python3', '/tmp/mcp-http.test.py') run('docker', 'exec', '-e', 'LAZYBOY_RUNNER_TEST_CONTAINER=1', name, 'python3', '/tmp/mcp-packages.test.py') run('docker', 'cp', 'tests/mcp-restart.test.py', name + ':/tmp/mcp-restart.test.py') run('docker', 'exec', '-e', 'LAZYBOY_RUNNER_TEST_CONTAINER=1', name, 'python3', '/tmp/mcp-restart.test.py', 'prepare') run('docker', 'restart', '--timeout', '5', name) run('docker','exec','-e','LAZYBOY_RUNNER_TEST_CONTAINER=1',name,'python3','/tmp/runner-files.test.py','verify') run('docker', 'exec', '-e', 'LAZYBOY_RUNNER_TEST_CONTAINER=1', name, 'python3', '/tmp/mcp-restart.test.py', 'verify') run('docker', 'exec', '-e', 'LAZYBOY_RUNNER_TEST_CONTAINER=1', name, 'python3', '/tmp/mcp-restart.test.py', 'prepare') run('cargo','test','-p','lazyboy-supervisor','legacy_runtime_migration_preserves_data_and_resumes_publication','--','--ignored','--nocapture', env={**{key:value for key,value in os.environ.items() if key!='HOST_DATA_DIR'},'LAZYBOY_PLAN_TEST_CONTAINER':name,'LAZYBOY_PLAN_MIGRATION_DATA_DIR':temporary.name,'DATA_DIR':temporary.name}) old_id=run('docker','inspect','--format','{{.Id}}',name,capture_output=True,text=True).stdout.strip() run('docker','rm','-f',name) created=False run(*computer_args[:2],*runtime_mounts,*computer_args[2:]) created=True new_id=run('docker','inspect','--format','{{.Id}}',name,capture_output=True,text=True).stdout.strip() assert old_id != new_id, 'recreate must use a different container' run('docker','cp',binary,name+':/usr/local/bin/lazyboy-controld') run('docker','cp','tests/mcp-packages.test.py',name+':/tmp/mcp-packages.test.py') run('docker','cp','tests/mcp-restart.test.py',name+':/tmp/mcp-restart.test.py') run('docker','cp','tests/runner-files.test.py',name+':/tmp/runner-files.test.py') run('docker','exec','-e','LAZYBOY_RUNNER_TEST_CONTAINER=1',name,'python3','/tmp/runner-files.test.py','verify') run('docker','exec','-e','LAZYBOY_RUNNER_TEST_CONTAINER=1',name,'python3','/tmp/mcp-restart.test.py','verify') print('actual remove/recreate with persistent runtime mounts verified',flush=True) run('cargo', 'test', '-p', 'lazyboy-supervisor', 'real_computer_files_cas_and_bidirectional_exec', '--', '--ignored', '--nocapture', env={**os.environ, 'LAZYBOY_PLAN_TEST_CONTAINER': name}) run('cargo','test','-p','lazyboy-supervisor','durable_file_http_transport_replays_without_reapplying','--','--ignored','--nocapture',env={**os.environ,'LAZYBOY_PLAN_TEST_CONTAINER':name}) run('cargo','build','-p','lazyboy-supervisor') run('cargo','test','-p','lazyboy-api','real_file_dispatch_recovers_lost_response_without_reapplying','--','--ignored','--nocapture',env={**os.environ,'LAZYBOY_PLAN_TEST_CONTAINER':name,'LAZYBOY_PLAN_SUPERVISOR_BINARY':str(ROOT/'target/debug/lazyboy-supervisor')}) finally: if builder_created: run('docker', 'rm', '-f', name + '-build') if created: run('docker', 'rm', '-f', name) temporary.cleanup()