from pathlib import Path import importlib.util, json, os, shutil, subprocess, sys, tarfile, zipfile ROOT = Path(__file__).resolve().parent SEED = ROOT/'seed' SEED.mkdir(exist_ok=True) with zipfile.ZipFile(ROOT/'candidate.ota') as z: with z.open('payload.tar.gz') as p, tarfile.open(fileobj=p, mode='r|gz') as t: for m in t: assert m.isfile() and not m.name.startswith('/') and '..' not in Path(m.name).parts out=SEED/m.name;out.parent.mkdir(parents=True,exist_ok=True) out.write_bytes(t.extractfile(m).read());out.chmod(m.mode) sys.path.insert(0,str(SEED/'software')) def module(name,path): spec=importlib.util.spec_from_file_location(name,path) m=importlib.util.module_from_spec(spec);sys.modules[name]=m;spec.loader.exec_module(m);return m oldpackage=module('app.ota.legacy_package',ROOT/'baseline/package.py') worker=module('legacy_worker',ROOT/'baseline/ota_worker.py') worker.inspect_package=oldpackage.inspect_package worker.extract_payload=oldpackage.extract_payload from scripts import ota_components as c def reset(): for p in [Path('/opt'),Path('/var/lib'),Path('/run'),Path('/usr/local/bin'),Path('/etc/systemd/system')]: for entry in p.iterdir(): if entry.is_dir() and not entry.is_symlink(): shutil.rmtree(entry) else: entry.unlink() c.DATA.mkdir() c.TARGET.mkdir() (c.TARGET/'VERSION').write_text('1.0.3',encoding='utf-8') c.RELEASES.mkdir() (c.DATA/'keep.txt').write_bytes(b'user resource preserved') for name in ('app-unit','ota-unit'): c.FILES[name].write_bytes(('old '+name).encode()) if '--resume' not in sys.argv: reset() else: assert '413 passed' in (ROOT/'validation.log').read_text(encoding='utf-8',errors='replace') request={'schema_version':1,'job_id':'baseline103','target_version':'1.1.0','current_version':'1.0.3', 'package_path':'/opt/candidate.ota','status_path':str(c.RUNTIME/'ota-status.json'), 'accepted_at':'2026-09-07T00:00:00Z'} c.RUNTIME.mkdir(parents=True,exist_ok=True) (c.RUNTIME/'ota-request.json').write_text(json.dumps(request),encoding='utf-8') shutil.copy2(ROOT/'candidate.ota',request['package_path']) tx=worker.Transaction(request) tx.start_visual=lambda: None tx.stop_visual=lambda: None actual_run=worker.run def run(command,**kwargs): if any('dedicated_host.py' in p for p in command): print('Dedicated host policy: separately checked outside namespace',flush=True) return actual_run(command,**kwargs) worker.run=run if '--resume' in sys.argv: actual_run([str(tx.release/'.venv/bin/python'),'-m','scripts.prepare_data_root','--data-root',str(tx.data_candidate),'--runtime-root',str(tx.work_root/'migration-runtime')],cwd=tx.release,env={**os.environ,'PYTHONPATH':str(tx.release)}) else: tx.prepare() assert c.FILES['frpc'].is_file() assert (c.DATA/c.JOURNAL/'state.json').is_file() tx.switch() # Hardware/HTTP health is intentionally simulated in this isolated filesystem. # The real worker ordering, venv, compile, pytest, migration and installation run. tx.succeed() c.finish() assert (c.TARGET/'VERSION').read_text().strip()=='1.1.0' assert (c.DATA/'keep.txt').read_bytes()==b'user resource preserved' assert not (c.DATA/c.JOURNAL).exists() c.check_installed(c.TARGET,'1.1.0') print('PASS: real 1.0.3 worker -> 1.1.0, actual offline venv/tests/native compile/frpc; simulated service health',flush=True) # Exercise idempotence and repair with the actual ARM64 shell installer. bundle=SEED/'software/system-dependencies/frpc' env=os.environ.copy();env['FRPC_BUNDLE']=str(bundle);env['FRPC_RUN_USER']=c.account() installer=['/bin/sh',str(c.TARGET/'scripts/install_frpc_system.sh')] state_path=Path('/run/fake-systemctl.json') state_path.write_text(json.dumps({c.FRP:{'active':True,'enabled':True}})) before=c.FILES['frpc'].stat().st_mtime_ns subprocess.run(installer,env=env,check=True) assert c.FILES['frpc'].stat().st_mtime_ns==before c.FILES['frpc'].write_bytes(b'corrupt') subprocess.run(installer,env=env,check=True) c.check_installed(c.TARGET,'1.1.0') c.FILES['frpc-dropin'].unlink() subprocess.run(installer,env=env,check=True) c.check_installed(c.TARGET,'1.1.0') assert json.loads(state_path.read_text())[c.FRP]=={'active':True,'enabled':True} print('PASS: existing binary skipped; damaged binary and missing drop-in repaired; enabled/active preserved',flush=True) # Test independent rollback after the legacy worker switches program and data. for stage in ('installed','switched','data-gap','service-failure','health-failure'): reset() source=c.RELEASES/'1.1.0-baseline103' shutil.copytree(SEED/'software',source) candidate=c.DATA.with_name('matrix-screen-controller.ota.baseline103') shutil.copytree(c.DATA,candidate) c.RUNTIME.mkdir(parents=True,exist_ok=True) (c.RUNTIME/'ota-request.json').write_text(json.dumps(request),encoding='utf-8') c.begin(source,candidate) assert c.FILES['frpc'].exists() if stage in ('service-failure','health-failure'): failed_tx=worker.Transaction(request) failed_tx.work_root.mkdir(parents=True) failed_tx.start_visual=lambda: None failed_tx.stop_visual=lambda: None if stage=='service-failure': state_path.write_text(json.dumps({'start_fail_once':c.SERVICE})) try: failed_tx.switch() raise RuntimeError('injected health failure') except Exception as error: failed_tx.rollback(error) if stage in ('switched','data-gap'): c.TARGET.rename(c.RELEASES/'1.0.3-pre-ota-baseline103') c.TARGET.symlink_to(source,target_is_directory=True) c.DATA.rename(c.DATA.with_name('matrix-screen-controller.rollback.baseline103')) if stage=='switched': candidate.rename(c.DATA) c.finish(boot=True) assert (c.TARGET/'VERSION').read_text().strip()=='1.0.3' assert (c.DATA/'keep.txt').read_bytes()==b'user resource preserved' assert not c.FILES['frpc'].exists() assert not (c.DATA/c.JOURNAL).exists() print('PASS: component/application interruption recovery '+stage,flush=True) # v2 patch must fail in the real old parser, even without its own path policy. from app.ota.package import build_package from app.ota.versioning import SoftwareVersion mini=ROOT/'patch-source';mini.mkdir(exist_ok=True) (mini/'VERSION').write_text('1.1.1',encoding='utf-8') patch=ROOT/'patch.ota';patch.unlink(missing_ok=True) build_package(mini,SEED/'wheelhouse',patch,version=SoftwareVersion.parse('1.1.1'),release_notes='gate test') try: oldpackage.inspect_package(patch,current_version=SoftwareVersion.parse('1.0.3')) except oldpackage.OtaPackageError: print('PASS: real 1.0.3 parser rejects v2 patch before installation',flush=True) else: raise AssertionError('legacy updater accepted patch')