"""End-to-end orchestration tests with EXPLICIT compiler test doubles. These tests validate failure propagation/artifact checks. They DO NOT compile Pascal. Synthetic source receipts/ELF/APK exist only in temporary directories. """ from __future__ import annotations import json, os, shutil, subprocess, sys, tempfile, unittest from pathlib import Path sys.path.insert(0,str(Path(__file__).resolve().parents[1]/'tools')) from common import ROOT, COMMIT, sha256, git_blob, write_json from runner import PROFILES MOCK=r'''#!/usr/bin/env python3 # TEST DOUBLE. NOT A COMPILER. NEVER SHIPPED AS AN ANDROID ARTIFACT. import json,os,sys,zipfile from pathlib import Path sys.path.insert(0,str(Path(__file__).parent)) from helpers import elf if '--version' in sys.argv: print('TEST_DOUBLE_NOT_A_REAL_COMPILER');raise SystemExit(0) if '--os=android' not in sys.argv or not any(a.startswith('--cpu=') for a in sys.argv): print('TEST ERROR: missing explicit Android target');raise SystemExit(97) cpu=next(a.split('=',1)[1] for a in sys.argv if a.startswith('--cpu=')) t=json.loads(Path('target.json').read_text());ident=t['profile'] if sys.argv[-1]=='compile': if ident==os.environ.get('PROBE_FAKE_FAIL'): print("KM_Test.pas(9,1) Fatal: Can't find unit Graphics used by KM_Test");raise SystemExit(1) if ident==os.environ.get('PROBE_FAKE_ZERO_NO_SO'): print('TEST DOUBLE: misleading zero return without output');raise SystemExit(0) Path('lib'+t['name']+'.so').write_bytes(elf(cpu)) for unit in t['units']: Path(unit.lower()+'.ppu').write_bytes(b'PPU_TEST_DOUBLE') print('Compiling '+unit+'.pas (TEST DOUBLE ONLY)') elif sys.argv[-1]=='package': library=Path('lib'+t['name']+'.so');data=library.read_bytes() if ident==os.environ.get('PROBE_FAKE_OLD_APK'):data=data[:-1]+b'x' abi={'aarch64':'arm64-v8a','arm':'armeabi-v7a','x86_64':'x86_64'}[cpu] with zipfile.ZipFile(t['name']+'.apk','w') as z: z.writestr('AndroidManifest.xml','TEST DOUBLE ONLY') z.writestr('classes.dex',b'dex\n035\0TEST DOUBLE ONLY') z.writestr('lib/'+abi+'/'+library.name,data) else:raise SystemExit(98) ''' class PipelineTests(unittest.TestCase): def run_pipeline(self,variables=None,profiles='all',with_source=True): temp=tempfile.TemporaryDirectory();self.addCleanup(temp.cleanup);root=Path(temp.name) for folder in ('tools','config','code'):shutil.copytree(ROOT/folder,root/folder,ignore=shutil.ignore_patterns('__pycache__','toolchain.lock.json')) bindir=root/'mock-bin';bindir.mkdir() shutil.copyfile(Path(__file__).parent/'helpers.py',bindir/'helpers.py') (bindir/'castle-engine').write_text(MOCK.replace('#!/usr/bin/env python3', '#!'+sys.executable+' -S'));(bindir/'castle-engine').chmod(0o755) for tool in ('java','fpc','adb'): (bindir/tool).write_text('#!/bin/sh\necho TEST_DOUBLE_VERSION\n');(bindir/tool).chmod(0o755) (bindir/'apksigner').write_text('#!/bin/sh\necho TEST_DOUBLE_SIGNER\nexit 0\n');(bindir/'apksigner').chmod(0o755) if with_source: src=root/'vendor/kam_remake';src.mkdir(parents=True);files=[] for unit in sorted({u for p in PROFILES for u in p['units']}): p=src/'src'/f'{unit}.pas';p.parent.mkdir(exist_ok=True) p.write_text(f'unit {unit}; interface implementation end.\n') files.append({'path':p.relative_to(src).as_posix(),'size':p.stat().st_size, 'sha256':sha256(p),'git_blob_sha1':git_blob(p.read_bytes())}) write_json(src/'SOURCE_RECEIPT.json',{'schema':'TEST_DOUBLE_RECEIPT','commit':COMMIT, 'repository':'reyandme/kam_remake','source_only':True,'files':files}) env={**os.environ,'PATH':str(bindir)+os.pathsep+os.environ['PATH'],**(variables or {})} p=subprocess.run([sys.executable,'-S',str(root/'tools/runner.py'),'--local','--no-fetch','--profiles',profiles], cwd=root,env=env,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True,timeout=60) latest=json.loads((root/'reports/LATEST.json').read_text()) result=json.loads((root/latest['directory']/'result.json').read_text()) return p,result,root def test_all_stages_can_reach_build_gate_with_test_double(self): p,r,root=self.run_pipeline() self.assertEqual(p.returncode,0,p.stdout);self.assertEqual(r['gate'],'ANDROID_BUILD_GATE_PASS') self.assertEqual(len(r['profiles']),17);self.assertEqual(r['runtime'],'NOT_RUN') self.assertEqual(len(list((root/'dist').rglob('*.apk'))),5) self.assertIn('TEST_DOUBLE',r['toolchain']['castle']['output']) def test_core_failure_does_not_stop_later_probes(self): p,r,_=self.run_pipeline({'PROBE_FAKE_FAIL':'simulation'}) self.assertEqual(p.returncode,1,p.stdout);self.assertEqual(r['gate'],'NOT_BUILDABLE_AS_IS') self.assertEqual(next(x for x in r['profiles'] if x['id']=='scripting')['compile_link']['status'],'PASS') self.assertEqual(next(x for x in r['profiles'] if x['id']=='simulation')['compile_link']['status'],'FAIL') def test_zero_exit_without_library_is_not_a_pass(self): p,r,_=self.run_pipeline({'PROBE_FAKE_ZERO_NO_SO':'core_types'},'core_types') self.assertEqual(p.returncode,1,p.stdout) self.assertEqual(r['profiles'][1]['compile_link']['status'],'FAIL') def test_stale_apk_library_rejected_end_to_end(self): p,r,_=self.run_pipeline({'PROBE_FAKE_OLD_APK':'integration'}) self.assertEqual(p.returncode,1,p.stdout) self.assertEqual(next(x for x in r['profiles'] if x['id']=='integration')['package']['status'],'FAIL') self.assertTrue(any('stale' in e for e in next(x for x in r['profiles'] if x['id']=='integration')['errors'])) def test_partial_selection_never_means_whole_architecture_ready(self): p,r,_=self.run_pipeline(profiles='core_types') self.assertEqual(p.returncode,2,p.stdout);self.assertEqual(r['gate'],'PARTIAL_SELECTION') def test_missing_sources_block_the_entire_run(self): p,r,_=self.run_pipeline(profiles='core_types',with_source=False) self.assertEqual(p.returncode,2,p.stdout);self.assertEqual(r['gate'],'BLOCKED') self.assertEqual(r['profiles'][0]['compile_link']['status'],'BLOCKED') self.assertEqual(r['profiles'][1]['compile_link']['status'],'BLOCKED') def test_no_resource_data_in_evidence(self): import zipfile p,r,root=self.run_pipeline(profiles='platform') evidence=next((root/'reports/runs').rglob('evidence.zip')) with zipfile.ZipFile(evidence) as z: self.assertFalse(any(Path(n).suffix in ('.apk','.so','.dat','.map','.rxx') for n in z.namelist())) if __name__=='__main__':unittest.main()