Retain aircraft through native landing and qualify bounded input diagnostics

This commit is contained in:
2026-09-19 20:34:18 +02:00
parent f870443b9b
commit 2fba12f987
129 changed files with 50369 additions and 88 deletions
+152 -9
View File
@@ -114,7 +114,9 @@ class NativePackageTests(unittest.TestCase):
(folder/'external.pak').symlink_to(self.source/'addons/core/addon.gproj')
with self.assertRaises(ValueError): n.package_inventory(folder)
def native_double(self, authored, timeout=30, mutate=False, failure=None, native_code=0, write_status=True, launcher_code=0):
def native_double(self, authored, timeout=30, mutate=False, failure=None, native_code=0, write_status=True, launcher_code=0, silent_workbench=False, exit_after_init=False, cleanup_proof=None, prefix_remains_active=False):
if cleanup_proof is None:
cleanup_proof = {'cleanup_confirmed': True, 'idle_verified': True, 'errors': [], 'orphan_cleanup': {'attempted': False}}
child = Mock(); child.poll.return_value = launcher_code
def spawn(*args, **kwargs):
self.assertNotIn('WINEPREFIX', kwargs['env'])
@@ -124,7 +126,7 @@ class NativePackageTests(unittest.TestCase):
(folder/'console.log').write_text('SYNTHETIC NATIVE PROCESS DOUBLE\n'+(failure or ''))
(self.session/'packages/core/data.pak').write_bytes(b'SYNTHETIC TEST ONLY')
if write_status:
native_command = n.command_for(self.steam, self.session, 'core')
native_command = n.command_for(self.steam, self.session, 'core', silent_workbench, exit_after_init)
native_command = native_command[native_command.index('--')+1:]
(self.session/'evidence/core/native-exit.json').write_text(json.dumps({'command': native_command, 'exit_code': native_code}))
if mutate:
@@ -132,11 +134,17 @@ class NativePackageTests(unittest.TestCase):
return child
def check_output(args, **kwargs):
return '[]' if 'sink-inputs' in args else '987'
with patch.object(n, 'assert_no_game'), patch.object(n, 'assert_prefix_idle'), \
patch.object(n, 'cleanup') as cleanup, patch.object(n.subprocess, 'Popen', side_effect=spawn), \
probes = 0
def idle(*args):
nonlocal probes
probes += 1
if prefix_remains_active and probes > 2:
raise ValueError('synthetic adopted helper remains after native exit')
with patch.object(n, 'assert_no_game'), patch.object(n, 'assert_prefix_idle', side_effect=idle), \
patch.object(n, 'cleanup_owned_run', return_value=cleanup_proof) as cleanup, patch.object(n.subprocess, 'Popen', side_effect=spawn), \
patch.object(n.subprocess, 'check_output', side_effect=check_output), patch.object(n.subprocess, 'run') as process:
result = n.run_project(self.steam, self.compat, self.session, 'core', timeout, authored)
cleanup.assert_called_once()
result = n.run_project(self.steam, self.compat, self.session, 'core', timeout, authored, silent_workbench, exit_after_init)
cleanup.assert_called_once_with(child, Path(n.command_for(self.steam, self.session, 'core')[n.command_for(self.steam, self.session, 'core').index('--')+1]), unittest.mock.ANY, self.compat)
self.assertTrue(any(call.args[0] == ['pactl', 'unload-module', '987'] for call in process.call_args_list))
return result
@@ -179,7 +187,7 @@ class NativePackageTests(unittest.TestCase):
probes += 1
if probes > 2: raise ValueError('synthetic owned prefix child remains')
with patch.object(n, 'assert_no_game'), patch.object(n, 'assert_prefix_idle', side_effect=idle), \
patch.object(n, 'cleanup') as cleanup, patch.object(n.subprocess, 'Popen', return_value=child), \
patch.object(n, 'cleanup_owned_run', return_value={'cleanup_confirmed': False, 'idle_verified': False, 'errors': ['synthetic owned prefix child remains']}) as cleanup, patch.object(n.subprocess, 'Popen', return_value=child), \
patch.object(n.subprocess, 'check_output', side_effect=lambda args, **kw: '[]' if 'sink-inputs' in args else '987'), \
patch.object(n.subprocess, 'run'), patch.object(n.time, 'monotonic', side_effect=itertools.count(0, 6)), patch.object(n.time, 'sleep'):
report = n.run_project(self.steam, self.compat, self.session, 'core', 100, authored)
@@ -225,14 +233,14 @@ class NativePackageTests(unittest.TestCase):
copy.assert_not_called()
self.assertFalse(fresh.exists())
def orchestration_double(self, reports, continuation=True, idle=None):
def orchestration_double(self, reports, continuation=True, idle=None, exit_after_init=False):
import contextlib
import io
import uuid
fresh = Path('/tmp')/('erri-package-orchestration-'+uuid.uuid4().hex)
self.addCleanup(lambda: shutil.rmtree(fresh, ignore_errors=True))
args = argparse.Namespace(steam=self.steam, compat=self.compat, session=fresh,
project='chat', timeout=30, continue_on_failure=continuation)
project='chat', timeout=30, continue_on_failure=continuation, exit_after_init=exit_after_init)
with patch.object(n, 'assert_no_game'), patch.object(n, 'assert_prefix_idle', side_effect=idle), \
patch.object(n.shutil, 'which', return_value='/mock'), \
patch.object(n, 'run_project', side_effect=reports) as run, \
@@ -387,6 +395,141 @@ class NativePackageTests(unittest.TestCase):
self.assertIn('-packAddon', silent)
self.assertIn('-noBackend', silent)
def test_exit_after_init_changes_only_documented_lifecycle_flag(self):
ordinary = n.command_for(self.steam, self.session, 'core')
experiment = n.command_for(self.steam, self.session, 'core', exit_after_init=True)
self.assertEqual(experiment, ordinary + ['-exitAfterInit'])
self.assertNotIn('-wbSilent', experiment)
self.assertNotIn('-run', experiment)
with self.assertRaisesRegex(ValueError, 'only one'):
n.command_for(self.steam, self.session, 'core', True, True)
with patch.object(n, 'run', return_value=1) as run:
base = ['--session', '/tmp/not-launched', '--compat', str(self.compat)]
n.main(base)
self.assertFalse(run.call_args.args[0].silent_workbench)
self.assertFalse(run.call_args.args[0].exit_after_init)
n.main(base + ['--silent-workbench'])
self.assertTrue(run.call_args.args[0].silent_workbench)
self.assertFalse(run.call_args.args[0].exit_after_init)
n.main(base + ['--exit-after-init'])
self.assertTrue(run.call_args.args[0].exit_after_init)
self.assertFalse(run.call_args.args[0].silent_workbench)
run.reset_mock()
with self.assertRaises(SystemExit) as refused:
n.main(base + ['--silent-workbench', '--exit-after-init'])
self.assertEqual(refused.exception.code, 2)
run.assert_not_called()
def test_exit_after_init_forwards_across_dependency_closure(self):
code, summary, calls = self.orchestration_double(
[self.completed_report(True) for _ in range(5)], exit_after_init=True)
self.assertEqual(code, 0)
self.assertEqual(len(calls), 5)
self.assertTrue(all(call.args[-2:] == (False, True) for call in calls))
self.assertFalse(summary['release_qualified'])
def test_exit_after_init_records_exact_command_and_retains_shutdown_errors(self):
authored = n.copy_payload(self.source, self.session)
report = self.native_double(authored, launcher_code=None, exit_after_init=True,
failure='RESOURCES (E): synthetic shutdown UI leak')
launch = json.loads((self.session/'evidence/core/launch.json').read_text())
self.assertEqual(launch['native_command'][-1], '-exitAfterInit')
self.assertTrue(launch['exit_after_init_experiment'])
self.assertTrue(report['exit_after_init_experiment'])
self.assertFalse(report['silent_workbench_experiment'])
status = json.loads((self.session/'evidence/core/native-exit.json').read_text())
self.assertEqual(status['command'], launch['native_command'])
self.assertEqual(report['native_exit_code'], 0)
self.assertEqual(report['completion_observed'], 'native-child-exited')
self.assertFalse(report['candidate_created'])
self.assertTrue(report['errors'])
self.assertFalse(report['release_qualified'])
console = self.session/'profiles/core/logs/logs_test/console.log'
self.assertIn('synthetic shutdown UI leak', console.read_text())
self.assertEqual(report['logs'][str(console)]['sha256'], n.digest(console))
def test_exit_after_init_without_recorded_native_exit_still_refuses(self):
authored = n.copy_payload(self.source, self.session)
report = self.native_double(authored, exit_after_init=True, write_status=False)
self.assertFalse(report['candidate_created'])
self.assertIsNone(report['native_exit_code'])
def test_exit_after_init_timeout_keeps_error_and_cleanup_rules(self):
authored = n.copy_payload(self.source, self.session)
report = self.native_double(authored, exit_after_init=True, timeout=0)
self.assertEqual(report['completion_observed'], 'timeout')
self.assertFalse(report['candidate_created'])
self.assertTrue(report['cleanup_confirmed'])
def test_shared_cleanup_report_preserves_partial_refusal_and_native_errors(self):
authored = n.copy_payload(self.source, self.session)
proof = {'cleanup_confirmed': False, 'idle_verified': False,
'errors': ['private helper cleanup: ambiguous live process'],
'orphan_cleanup': {'attempted': True, 'report': {'cleared': False, 'term_sent': [123]}}}
report = self.native_double(authored, launcher_code=None,
failure='RESOURCES (E): retained UI leak', cleanup_proof=proof)
self.assertEqual(report['cleanup_report'], proof)
self.assertFalse(report['cleanup_confirmed'])
self.assertFalse(report['candidate_created'])
self.assertIn('ambiguous live process', report['runner_error'])
self.assertTrue(any('retained UI leak' in error for error in report['errors']))
self.assertEqual(report['native_exit_code'], 0)
self.assertEqual(report['completion_observed'], 'native-child-exited')
def test_successful_adopted_cleanup_cannot_upgrade_timeout(self):
authored = n.copy_payload(self.source, self.session)
proof = {'cleanup_confirmed': True, 'idle_verified': True, 'errors': [],
'orphan_cleanup': {'attempted': True, 'report': {'cleared': True, 'term_sent': [123], 'kill_sent': []}}}
report = self.native_double(authored, timeout=0, cleanup_proof=proof)
self.assertEqual(report['cleanup_report'], proof)
self.assertTrue(report['cleanup_confirmed'])
self.assertEqual(report['completion_observed'], 'timeout')
self.assertFalse(report['candidate_created'])
def test_post_run_cleanup_does_not_reclassify_prefix_active_native_exit(self):
import itertools
authored = n.copy_payload(self.source, self.session)
proof = {'cleanup_confirmed': True, 'idle_verified': True, 'errors': [],
'orphan_cleanup': {'attempted': True, 'report': {'cleared': True, 'term_sent': [123]}}}
with patch.object(n.time, 'monotonic', side_effect=itertools.count(0, 2)), patch.object(n.time, 'sleep'):
report = self.native_double(authored, timeout=100, launcher_code=None,
cleanup_proof=proof, prefix_remains_active=True,
failure='RESOURCES (E): synthetic shutdown leak')
self.assertEqual(report['native_exit_code'], 0)
self.assertEqual(report['completion_observed'], 'native-or-launcher-exited-prefix-active')
self.assertTrue(report['cleanup_confirmed'])
self.assertFalse(report['candidate_created'])
self.assertTrue(report['errors'])
self.assertEqual(report['cleanup_report'], proof)
def test_packaging_uses_exact_shared_cleanup_adapter_and_scoping(self):
import headless_game as game
import private_prefix_cleanup as private
self.assertIs(n.cleanup_owned_run, game.cleanup_owned_run)
for compat, expected in ((private.COMPAT, ['normal', 'adopted', 'idle']),
(self.compat, ['normal', 'idle'])):
events = []
with patch.object(game, 'cleanup', side_effect=lambda *args: events.append('normal')), \
patch.object(private, 'cleanup_private_prefix', side_effect=lambda *args: events.append('adopted') or {'cleared': True}), \
patch.object(game, 'assert_prefix_idle', side_effect=lambda *args: events.append('idle')):
report = n.cleanup_owned_run(Mock(), private.INSTALLED_PROTON, {}, compat)
self.assertEqual(events, expected)
self.assertTrue(report['cleanup_confirmed'])
self.assertEqual(report['orphan_cleanup']['attempted'], compat == private.COMPAT)
def test_failed_native_launch_does_not_call_post_run_cleanup(self):
authored = n.copy_payload(self.source, self.session)
with patch.object(n, 'assert_no_game'), patch.object(n, 'assert_prefix_idle'), \
patch.object(n.subprocess, 'check_output', return_value='987'), \
patch.object(n.subprocess, 'run'), \
patch.object(n.subprocess, 'Popen', side_effect=OSError('synthetic launch refusal')), \
patch.object(n, 'cleanup_owned_run') as cleanup:
report = n.run_project(self.steam, self.compat, self.session, 'core', 30, authored)
cleanup.assert_not_called()
self.assertIsNone(report['cleanup_report'])
self.assertEqual(report['completion_observed'], 'runner-failed')
self.assertFalse(report['candidate_created'])
if __name__ == '__main__':
unittest.main()