This commit is contained in:
1 parent
a5ee0bec73
commit
15a968a2f7
6 files changed
+290
-37
No files matched your search
@@ -89,10 +89,10 @@ class PipelineTests(unittest.TestCase):
|
||||
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_do_not_block_independent_platform_probe(self):
|
||||
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'],'PASS')
|
||||
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
|
||||
|
||||
+77
-5
@@ -1,9 +1,10 @@
|
||||
from __future__ import annotations
|
||||
import hashlib, json, os, shutil, subprocess, sys, tempfile, unittest, zipfile
|
||||
import hashlib, json, os, shutil, subprocess, sys, tempfile, unittest, xml.etree.ElementTree as ET, zipfile
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
sys.path.insert(0,str(Path(__file__).resolve().parents[1]/'tools'))
|
||||
from common import ROOT, COMMIT, ProbeError, git_blob, safe_relative, sha256, write_json, run_command, classify
|
||||
from source_resolution import build_source_resolution, load_source_contract, validate_source_contract
|
||||
from source_fetch import selected, enumerate_blobs, verify
|
||||
from source_audit import inventory
|
||||
from generate import make_target, imports_source, asset_inventory, COMPILER_OPTIONS
|
||||
@@ -22,6 +23,62 @@ for i,good in enumerate(('src/common/KM_Points.pas','Maps/Тест/Тест.map'
|
||||
def test(self,good=good):self.assertEqual(safe_relative(good),good)
|
||||
setattr(PathsTests,f'test_accept_path_{i:02d}',test)
|
||||
|
||||
class SourceContractTests(unittest.TestCase):
|
||||
def contract(self, value):
|
||||
temp = tempfile.TemporaryDirectory(); self.addCleanup(temp.cleanup)
|
||||
path = Path(temp.name) / 'extra-paths.json'; path.write_text(json.dumps(value))
|
||||
return path
|
||||
def test_loads_normalized_targets_and_deduplicated_paths(self):
|
||||
path = self.contract({'schema':'kam-source-resolution/1','adaptations':['src/common/KM_Example.pas'],
|
||||
'source_paths':['tools','tools'],'library_paths':[str(ROOT/'config')]})
|
||||
loaded = load_source_contract(path)
|
||||
self.assertEqual(loaded['adaptation_targets'], ('src/common/KM_Example.pas',))
|
||||
self.assertEqual(loaded['source_paths'], ((ROOT/'tools').resolve(),))
|
||||
self.assertEqual(loaded['library_paths'], ((ROOT/'config').resolve(),))
|
||||
def test_rejects_legacy_and_unsafe_contracts(self):
|
||||
for value in ({'source_paths':[],'library_paths':[]},
|
||||
{'schema':'kam-source-resolution/1','adaptations':['src\\bad.pas'],'source_paths':[],'library_paths':[]}):
|
||||
with self.subTest(value=value), self.assertRaises(ProbeError): load_source_contract(self.contract(value))
|
||||
|
||||
class SourceOwnershipTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tmp=tempfile.TemporaryDirectory();self.root=Path(self.tmp.name);self.vendor=self.root/'vendor';self.sparse=self.root/'port-source/kam_remake'
|
||||
self.vendor.joinpath('src').mkdir(parents=True);self.vendor.joinpath('src/KM_Test.pas').write_text('unit KM_Test; interface implementation end.')
|
||||
self.receipt={'files':[{'path':'src/KM_Test.pas'}]};self.inventory={'units':{'km_test':['src/KM_Test.pas']}}
|
||||
def tearDown(self):self.tmp.cleanup()
|
||||
def validate(self, contract):
|
||||
with patch('source_resolution.ROOT',self.root):
|
||||
return validate_source_contract(contract,self.vendor,self.receipt,self.inventory)
|
||||
def test_registered_adaptation_has_vendor_identity_and_unit(self):
|
||||
self.sparse.joinpath('src').mkdir(parents=True);self.sparse.joinpath('src/KM_Test.pas').write_text('unit km_test; interface implementation end.')
|
||||
result=self.validate({'adaptation_targets':('src/KM_Test.pas',),'source_paths':(),'library_paths':()})
|
||||
self.assertEqual(result['adaptations'][0]['unit_name'],'km_test');self.assertEqual(result['vendor_unit_keys'],frozenset({'km_test'}))
|
||||
def test_rejects_unregistered_sparse_and_generic_shadowing(self):
|
||||
self.sparse.mkdir(parents=True);self.sparse.joinpath('other.inc').write_text('x')
|
||||
with self.assertRaises(ProbeError):self.validate({'adaptation_targets':(),'source_paths':(),'library_paths':()})
|
||||
shutil.rmtree(self.sparse);generic=self.root/'generic';generic.mkdir();generic.joinpath('KM_Test.pas').write_text('not a unit')
|
||||
with self.assertRaises(ProbeError):self.validate({'adaptation_targets':(),'source_paths':(generic,),'library_paths':()})
|
||||
|
||||
class CompilerSourceResolutionTests(unittest.TestCase):
|
||||
def test_orders_deduplicates_and_guards_application_vendor_units(self):
|
||||
with tempfile.TemporaryDirectory() as t:
|
||||
root = Path(t); app = root/'app'; vendor = root/'vendor'; generic = root/'generic'; library = root/'library'
|
||||
for path in (app, vendor/'src', vendor/'ext', generic, library): path.mkdir(parents=True, exist_ok=True)
|
||||
(vendor/'src/KM_Vendor.pas').write_text('unit KM_Vendor; interface implementation end.')
|
||||
first, second = root/'adaptations/z', root/'adaptations/a'
|
||||
first.mkdir(parents=True); second.mkdir(parents=True)
|
||||
validated = {'vendor_unit_keys': frozenset({'km_vendor'}), 'adaptations': (
|
||||
{'target':'z.pas', 'project_file':first/'z.pas'}, {'target':'A.pas', 'project_file':second/'A.pas'},
|
||||
{'target':'a2.pas', 'project_file':second/'a2.pas'}),
|
||||
'source_paths': (generic, second), 'library_paths': (library,)}
|
||||
result = build_source_resolution(validated, vendor, app)
|
||||
self.assertEqual(result['adaptation_dirs'], (second.resolve(), first.resolve()))
|
||||
self.assertEqual(result['compiler_source_paths'][:4],
|
||||
(app.resolve(), second.resolve(), first.resolve(), generic.resolve()))
|
||||
self.assertEqual(result['library_paths'], (library.resolve(),))
|
||||
(app/'KM_VENDOR.pas').write_text('malformed declaration')
|
||||
with self.assertRaises(ProbeError): build_source_resolution(validated, vendor, app)
|
||||
|
||||
class SourceSelectionTests(unittest.TestCase):
|
||||
def test_known_git_blob(self):
|
||||
self.assertEqual(git_blob(b'hello\n'),'ce013625030ba8dba906f756967f9e9ca394464a')
|
||||
@@ -177,10 +234,25 @@ class GenerationTests(unittest.TestCase):
|
||||
code,units=imports_source(PROFILES[1]);self.assertIn('KMPointRight(P)',code);self.assertIn('KM_Points',units)
|
||||
def test_actual_stream_calls(self):
|
||||
code,units=imports_source(PROFILES[2]);self.assertIn('TKMemoryStreamBinary.Create',code);self.assertIn('S.ReadW(U)',code)
|
||||
def test_upstream_manifest_uses_resolved_compiler_order(self):
|
||||
with tempfile.TemporaryDirectory() as t:
|
||||
root=Path(t);vendor=root/'vendor';generic=root/'generic';library=root/'library';adaptation=root/'adaptation'
|
||||
for path in (vendor/'src',generic,library,adaptation):path.mkdir(parents=True,exist_ok=True)
|
||||
(vendor/'src/KM_Points.pas').write_text('unit KM_Points; interface implementation end.')
|
||||
validated={'vendor_unit_keys':frozenset({'km_points'}),'adaptations':(
|
||||
{'target':'src/KM_Points.pas','project_file':adaptation/'KM_Points.pas'},),
|
||||
'source_paths':(generic,),'library_paths':(library,)}
|
||||
p=root/'target';rec=make_target(p,PROFILES[1],'aarch64','id',vendor,validated)
|
||||
self.assertEqual(rec['source_paths'][:3],[str((p/'code').resolve()),str(adaptation.resolve()),str(generic.resolve())])
|
||||
xml=ET.parse(p/'CastleEngineManifest.xml')
|
||||
self.assertEqual([node.attrib['value'] for node in xml.findall('./compiler_options/search_paths/path')][:3],
|
||||
['code',os.path.relpath(adaptation,p).replace(os.sep,'/'),os.path.relpath(generic,p).replace(os.sep,'/')])
|
||||
self.assertEqual([node.attrib['value'] for node in xml.findall('./compiler_options/library_paths/path')],
|
||||
[os.path.relpath(library,p).replace(os.sep,'/')])
|
||||
def test_manifest_no_standalone_target(self):
|
||||
with tempfile.TemporaryDirectory() as t:
|
||||
p=Path(t)/'target'
|
||||
rec=make_target(p,PROFILES[0],'aarch64','test-build',None,{'source_paths':[],'library_paths':[]})
|
||||
rec=make_target(p,PROFILES[0],'aarch64','test-build',None,None)
|
||||
xml=(p/'CastleEngineManifest.xml').read_text()
|
||||
self.assertNotIn('standalone_source',xml);self.assertNotIn('test_shims',xml)
|
||||
self.assertIn('game_units="GameInitialize"',xml)
|
||||
@@ -190,7 +262,7 @@ class GenerationTests(unittest.TestCase):
|
||||
def test_decision_profile_uses_existing_generator_with_alternate_game_unit(self):
|
||||
with tempfile.TemporaryDirectory() as t:
|
||||
profile=next(x for x in PROFILES if x['id']=='oq_host_callback')
|
||||
p=Path(t)/'target';rec=make_target(p,profile,'aarch64','id',None,{'source_paths':[],'library_paths':[]})
|
||||
p=Path(t)/'target';rec=make_target(p,profile,'aarch64','id',None,None)
|
||||
xml=(p/'CastleEngineManifest.xml').read_text()
|
||||
self.assertIn('game_units="DecisionHostCallback"',xml)
|
||||
self.assertEqual(rec['game_unit'],'DecisionHostCallback')
|
||||
@@ -204,13 +276,13 @@ class GenerationTests(unittest.TestCase):
|
||||
self.assertEqual([x['path'] for x in rows],['Maps/one.txt'])
|
||||
def test_fresh_target_required(self):
|
||||
with tempfile.TemporaryDirectory() as t:
|
||||
with self.assertRaises(ProbeError):make_target(Path(t),PROFILES[0],'aarch64','id',None,{'source_paths':[],'library_paths':[]})
|
||||
with self.assertRaises(ProbeError):make_target(Path(t),PROFILES[0],'aarch64','id',None,None)
|
||||
def test_optional_assets_preserved(self):
|
||||
with tempfile.TemporaryDirectory() as t:
|
||||
r=Path(t)/'assets';r.mkdir();(r/'Data').mkdir();raw=bytes(range(256))
|
||||
(r/'Data/sample.dat').write_bytes(raw)
|
||||
entries=asset_inventory(r)
|
||||
p=Path(t)/'target';make_target(p,PROFILES[0],'aarch64','id',None,{'source_paths':[],'library_paths':[]},r,entries)
|
||||
p=Path(t)/'target';make_target(p,PROFILES[0],'aarch64','id',None,None,r,entries)
|
||||
self.assertEqual((p/'data/game/Data/sample.dat').read_bytes(),raw)
|
||||
def test_asset_symlinks_rejected(self):
|
||||
with tempfile.TemporaryDirectory() as t:
|
||||
|
||||
Reference in new issue
Block a user