Harden base progression and cleanup; validate finite physical resupply

This commit is contained in:
2026-09-19 10:39:39 +02:00
parent 40a474934c
commit 7c53b4bb81
377 changed files with 51882 additions and 92 deletions
+171
View File
@@ -0,0 +1,171 @@
#!/usr/bin/env python3
"""Real addon handoff bytes, mutations and publication failures; no native engine."""
from contextlib import redirect_stdout
import hashlib
import io
import json
import os
from pathlib import Path
import shutil
import sys
import tempfile
import unittest
from unittest.mock import patch
import zipfile
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'tools'))
import source_bundle as bundle
ROOT = Path(__file__).resolve().parents[1]
class SourceBundleTests(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
self.home = Path(self.temp.name)
self.root = self.home / 'source'
shutil.copytree(ROOT / 'addons', self.root / 'addons')
(self.root / 'docs').mkdir()
for name in ('addons.json', 'vanilla-resources.json', 'acceptance.json'):
shutil.copy2(ROOT / 'docs' / name, self.root / 'docs' / name)
self.output = self.home / 'bundle.zip'
def edit(self, path, old, new):
p = self.root / path
text = p.read_text()
self.assertIn(old, text)
p.write_text(text.replace(old, new))
def test_deterministic_bytes_ignore_source_mtime_and_mode(self):
first, manifest = bundle.build(self.root)
for p in (self.root / 'addons').rglob('*'):
if p.is_file():
os.utime(p, (1700000000, 1700000000))
p.chmod(0o600)
second, again = bundle.build(self.root)
self.assertEqual(first, second)
self.assertEqual(manifest, again)
with zipfile.ZipFile(io.BytesIO(first)) as archive:
self.assertEqual(archive.namelist(), sorted(archive.namelist()))
self.assertEqual(set(archive.namelist()), set(manifest['files']) | {'BUNDLE-MANIFEST.json'})
self.assertEqual(json.loads(archive.read('BUNDLE-MANIFEST.json')), manifest)
for name, digest in manifest['files'].items():
self.assertEqual(hashlib.sha256(archive.read(name)).hexdigest(), digest)
self.assertTrue(all(info.date_time == (1980, 1, 1, 0, 0, 0) for info in archive.infolist()))
self.assertFalse(manifest['native_build'] or manifest['release_package'])
self.assertIn(b'DEVELOPMENT SOURCE BUNDLE', archive.read('BUNDLE-README.md'))
self.assertEqual(set(manifest['projects']), set(bundle.PROJECTS))
def test_generated_vendor_original_profile_and_evidence_are_excluded(self):
before, _ = bundle.build(self.root)
for name in ('addons/core/cache/private.c', 'addons/core/vendor/asset.c',
'addons/ai/original/restricted.c', 'addons/core/.git/config',
'addons/core/evidence/raw.c', 'addons/core/runtime/account.c',
'addons/chat/profiles/profile.c', 'addons/core/.build/file.c',
'addons/core/resourceDatabase.rdb', 'addons/core/packed.pak',
'addons/core/runtime.log', 'docs/evidence/private.log'):
p = self.root / name; p.parent.mkdir(parents=True, exist_ok=True); p.write_text('DO NOT COPY')
after, _ = bundle.build(self.root)
self.assertEqual(before, after)
def test_changed_resource_changes_archive_and_manifest(self):
first, manifest = bundle.build(self.root)
name = 'addons/core/Scripts/Game/ERR/ERR_Actors.c'
p = self.root / name; p.write_text(p.read_text() + '\n// reviewed successor edit\n')
second, updated = bundle.build(self.root)
self.assertNotEqual(first, second)
self.assertNotEqual(manifest['files'][name], updated['files'][name])
def test_identity_and_external_dependency_refused(self):
name = 'addons/core/addon.gproj'
self.edit(name, '2528466DE220D91C', '1111111111111111')
with self.assertRaisesRegex(ValueError, 'identity/dependency'): bundle.build(self.root)
self.edit(name, '1111111111111111', '2528466DE220D91C')
self.edit(name, '58D0FB3206B6F859', '5F16D7E4A1CBE075')
with self.assertRaisesRegex(ValueError, 'identity/dependency'): bundle.build(self.root)
def test_missing_transitive_project_refused(self):
shutil.rmtree(self.root / 'addons/intel')
with self.assertRaisesRegex(ValueError, 'identity/dependency'): bundle.build(self.root)
def test_resource_dependency_outside_closure_refused(self):
p = self.root / 'addons/core/Scripts/Game/ERR/BadReference.c'
meta = next((self.root / 'addons/map').rglob('*.meta')).read_text()
import re
resource = re.search(r'Name "([^"\n]+)"', meta)[1]
p.write_text('class ERR_Test { ResourceName asset = "' + resource + '"; }')
with self.assertRaisesRegex(ValueError, 'dependency closure'): bundle.build(self.root)
def test_metadata_and_unknown_asset_format_refused(self):
(self.root / 'addons/core/Prefabs/ERR/Unknown.xob').write_bytes(b'vendor')
with self.assertRaisesRegex(ValueError, 'unreviewed'): bundle.build(self.root)
(self.root / 'addons/core/Prefabs/ERR/Unknown.xob').unlink()
next((self.root / 'addons/core').rglob('*.et.meta')).unlink()
with self.assertRaisesRegex(ValueError, 'metadata|unresolved'): bundle.build(self.root)
def test_source_symlink_and_nonregular_refused(self):
p = self.root / 'addons/core/Scripts/Game/ERR/Link.c'
p.symlink_to('/etc/hostname')
with self.assertRaisesRegex(ValueError, 'symlink'): bundle.build(self.root)
p.unlink(); os.mkfifo(p)
with self.assertRaisesRegex(ValueError, 'nonregular'): bundle.build(self.root)
def test_unsafe_or_case_colliding_member_refused(self):
p = self.root / 'addons/core/Scripts/Game/ERR/escape\\outside.c'; p.write_text('')
with self.assertRaisesRegex(ValueError, 'unsafe'): bundle.build(self.root)
p.unlink()
(self.root / 'addons/core/Scripts/Game/ERR/err_actors.c').write_text('')
with self.assertRaisesRegex(ValueError, 'collision'): bundle.build(self.root)
def test_validation_failure_with_output_argument_leaves_no_archive(self):
self.edit('addons/core/addon.gproj', '2528466DE220D91C', '1111111111111111')
real_build = bundle.build
with patch.object(bundle, 'build', side_effect=lambda **kw: real_build(self.root, **kw)), redirect_stdout(io.StringIO()):
self.assertEqual(bundle.main(['--output', str(self.output)]), 1)
self.assertFalse(self.output.exists())
self.assertEqual(list(self.home.glob('.erri-bundle-*')), [])
def test_changed_tree_during_collection_refused(self):
real_inventory = bundle.inventory
calls = 0
def changing_inventory(root):
nonlocal calls
calls += 1
if calls == 2:
(root / 'addons/core/Scripts/Game/ERR/Late.c').write_text('// late addition')
return real_inventory(root)
with patch.object(bundle, 'inventory', side_effect=changing_inventory):
with self.assertRaisesRegex(ValueError, 'tree changed'): bundle.build(self.root)
self.assertFalse(self.output.exists())
def test_atomic_new_output_no_overwrite_or_symlink(self):
content, _ = bundle.build(self.root)
bundle.write_new(self.output, content, self.root)
self.assertEqual(self.output.read_bytes(), content)
with self.assertRaises(ValueError): bundle.write_new(self.output, b'bad', self.root)
self.assertEqual(self.output.read_bytes(), content)
link = self.home / 'link.zip'; link.symlink_to(self.output)
with self.assertRaises(ValueError): bundle.write_new(link, b'bad', self.root)
self.assertEqual(self.output.read_bytes(), content)
def test_publication_failure_removes_temporary_and_leaves_no_output(self):
with patch.object(bundle.os, 'link', side_effect=OSError('publication failure')):
with self.assertRaises(OSError): bundle.write_new(self.output, b'bytes', self.root)
self.assertFalse(self.output.exists())
self.assertEqual(list(self.home.glob('.erri-bundle-*')), [])
def test_default_preview_creates_nothing_and_release_uses_real_pending_audit(self):
real_build = bundle.build
with patch.object(bundle, 'build', side_effect=lambda **kw: real_build(self.root, **kw)), redirect_stdout(io.StringIO()) as out:
self.assertEqual(bundle.main([]), 0)
self.assertTrue(json.loads(out.getvalue())['preview'])
self.assertFalse(self.output.exists())
with patch.object(bundle, 'build', side_effect=lambda **kw: real_build(self.root, **kw)), redirect_stdout(io.StringIO()) as out:
self.assertEqual(bundle.main(['--mode', 'release', '--output', str(self.output)]), 1)
self.assertIn('cases.twelve_players: status is pending', out.getvalue())
self.assertFalse(self.output.exists())
self.assertEqual(list(self.home.glob('.erri-bundle-*')), [])
if __name__ == '__main__':
unittest.main()