Save The Tree implementation and acceptance work in progress
Preserve production code, regression fixtures, audio assets and provenance, test infrastructure, and the original-completion plan and progress ledger. Final game and Windows acceptance remain pending; excluded local artifacts and native experiments are not part of this snapshot.
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
"""Strict proof gates. Synthetic self tests never count as engine evidence."""
|
||||
from pathlib import Path
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
|
||||
CASES = {'paused_success_exact_once', 'actual_duplicate_refusal', 'actual_public_authority_replay_silent', 'actual_noop_silent', 'two_actual_enqueue_validation_refusals', 'two_actual_empty_executor_refusals'}
|
||||
PAIRED = {'paused_success_exact_once', 'actual_public_authority_replay_silent', 'two_actual_empty_executor_refusals'}
|
||||
TEARDOWN_TESTS = {'tests/ui/test_ui_audio_feedback.gd', 'tests/ui/test_main_dashboard.gd', 'tests/ui/test_supply_management.gd', 'tests/ui/test_business_management.gd', 'tests/ui/test_elevator_management.gd'}
|
||||
|
||||
|
||||
def sha(path):
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def pin_file(pin, directory):
|
||||
path = Path(pin['path'])
|
||||
assert path.parent == directory and path.is_file() and not path.is_symlink()
|
||||
assert sha(path) == pin['sha256'] and path.stat().st_size == pin['bytes'] > 0
|
||||
assert pin['field_deletions'] == 0
|
||||
return path, {'sha256': sha(path), 'bytes': path.stat().st_size}
|
||||
|
||||
|
||||
def dto_tree(value):
|
||||
if isinstance(value, dict):
|
||||
if {'code', 'command_id', 'is_replay'} <= set(value):
|
||||
assert set(value) == {'code', 'command_id', 'details', 'is_replay', 'message', 'resolution_hints', 'target_id'}
|
||||
assert isinstance(value['is_replay'], bool) and isinstance(value['command_id'], int)
|
||||
for item in value.values(): dto_tree(item)
|
||||
elif isinstance(value, list):
|
||||
for item in value: dto_tree(item)
|
||||
|
||||
|
||||
def command_gate(proof_path, out, pid):
|
||||
proof = json.loads(proof_path.read_text())
|
||||
dto_tree(proof)
|
||||
assert proof['Godot_pid'] == pid
|
||||
assert proof['gameplay_snapshot_field_deletions'] == 0 and proof['buffer_CommandResult_objects_recorded_as_full_DTO'] is True
|
||||
assert proof['source_fallback'] is False and proof['formal_P22_P25_acceptance'] is False
|
||||
cases = {case['case']: case for case in proof['cases']}
|
||||
assert len(cases) == len(proof['cases']) == 6 and set(cases) == CASES
|
||||
pins = {}
|
||||
for name, case in cases.items():
|
||||
assert set(case['snapshots']) == ({'before', 'after', 'direct_control'} if name in PAIRED else {'before', 'after'})
|
||||
for part, pin in case['snapshots'].items():
|
||||
assert pin['full_gameplay_dictionary'] is True
|
||||
path, digest = pin_file(pin, out / 'actual-command-result-buffer-proof-typed')
|
||||
assert path.name == name + '-' + part + '.varbytes'
|
||||
pins[str(path.relative_to(out))] = digest
|
||||
if name in PAIRED:
|
||||
assert case['extra']['direct_control_pair_verified'] is True
|
||||
assert case['snapshots']['after']['sha256'] == case['snapshots']['direct_control']['sha256']
|
||||
success = cases['paused_success_exact_once']; duplicate = cases['actual_duplicate_refusal']
|
||||
replay = cases['actual_public_authority_replay_silent']; noop = cases['actual_noop_silent']
|
||||
invalid = cases['two_actual_enqueue_validation_refusals']; empty = cases['two_actual_empty_executor_refusals']
|
||||
assert success['returned_result']['code'] == 'OK' and success['returned_result']['is_replay'] is False and success['cues'] == ['success']
|
||||
assert duplicate['returned_result']['code'] == 'DUPLICATE_IDEMPOTENCY_KEY' and duplicate['returned_result']['is_replay'] is False and duplicate['cues'] == ['refusal']
|
||||
assert duplicate['snapshots']['before']['sha256'] == duplicate['snapshots']['after']['sha256']
|
||||
assert replay['returned_result']['code'] == 'OK' and replay['returned_result']['is_replay'] is True and replay['cues'] == []
|
||||
assert replay['after_audio_state']['armed'] == {} and replay['after_audio_state']['active_submissions'] == []
|
||||
assert replay['extra']['public_command_type'] == 'report_authority_incident' and replay['extra']['fabricated_result_fields'] == 0
|
||||
assert replay['extra']['setup_result']['code'] == 'OK' and replay['extra']['setup_result']['is_replay'] is False
|
||||
assert replay['extra']['observed_queue_replay_copy']['code'] == 'OK' and replay['extra']['observed_queue_replay_copy']['is_replay'] is True
|
||||
assert replay['extra']['stored_result'] == replay['returned_result'] == replay['extra']['direct_control_result']
|
||||
completions = [event for event in replay['events'] if event['kind'] == 'command_completed']
|
||||
assert len(completions) == 1 and completions[0]['result'] == replay['returned_result'] and len(completions[0]['audio_state']['active_submissions']) == 1
|
||||
assert noop['returned_result']['code'] == 'OK' and noop['returned_result']['is_replay'] is False and noop['returned_result']['details']['unchanged'] is True and noop['cues'] == []
|
||||
for case in (invalid, empty):
|
||||
assert case['returned_result']['code'] == case['extra']['first_result']['code'] == 'INVALID_COMMAND'
|
||||
assert case['returned_result']['is_replay'] is False and case['extra']['first_result']['is_replay'] is False and case['cues'] == ['refusal', 'refusal']
|
||||
assert invalid['extra']['command_type'] == 'audio_test_ok' and 0 <= invalid['extra']['execute_at'] < invalid['extra']['issued_tick']
|
||||
assert invalid['returned_result']['command_id'] == invalid['extra']['first_result']['command_id']
|
||||
assert not [event for event in invalid['events'] if event['kind'] == 'command_completed']
|
||||
assert invalid['snapshots']['before']['sha256'] == invalid['snapshots']['after']['sha256']
|
||||
assert empty['extra']['command_type'] == '' and empty['returned_result']['command_id'] == empty['extra']['first_result']['command_id'] + 1
|
||||
assert empty['snapshots']['before']['sha256'] != empty['snapshots']['after']['sha256']
|
||||
assert empty['extra']['direct_control_first_result'] == empty['extra']['first_result'] and empty['extra']['direct_control_second_result'] == empty['returned_result']
|
||||
completions = [event for event in empty['events'] if event['kind'] == 'command_completed']
|
||||
assert len(completions) == 2 and [event['result'] for event in completions] == [empty['extra']['first_result'], empty['returned_result']]
|
||||
assert len(pins) == 15
|
||||
return {'verified': True, 'report_sha256': sha(proof_path), 'full_typed_snapshot_files': pins}
|
||||
|
||||
|
||||
def teardown_gate(directory, pid, out):
|
||||
paths = sorted(directory.glob('owner_*.json'))
|
||||
assert len(paths) == 1
|
||||
proof_path = paths[0]; proof = json.loads(proof_path.read_text())
|
||||
assert proof['owned_engine_pid'] == pid and proof['owner_instance_id'] > 0 and proof['passed'] is True
|
||||
assert proof['maximum_wait_us'] == 500000 and 0 < proof['waited_actual_us'] < 500000 and proof['deadline_reached'] is False
|
||||
assert proof['owner_freed'] is True and proof['all_owned_targets_released'] is True and proof['whole_runtime_unchanged'] is True
|
||||
assert proof['bus_deleted'] is False and proof['cues_muted_for_test'] is False and proof['actual_human_hearing'] is False and proof['WindowQuit_verified'] is False and proof['formal_acceptance'] is False
|
||||
assert len(proof['initial_targets']) == len(proof['final_targets']) >= 8
|
||||
assert all(row['alive'] is True for row in proof['initial_targets']) and all(row['alive'] is False for row in proof['final_targets'])
|
||||
for kind in ('stream', 'packet_sequence'):
|
||||
assert {row['role'] for row in proof['initial_targets'] if row['kind'] == kind} == {'click', 'success', 'refusal', 'notification'}
|
||||
pins = {}
|
||||
assert set(proof['full_typed_snapshots']) == {'before', 'after'}
|
||||
for part, pin in proof['full_typed_snapshots'].items():
|
||||
path, digest = pin_file(pin, directory)
|
||||
assert path.name == proof_path.stem + '-' + part + '.varbytes'
|
||||
pins[str(path.relative_to(out))] = digest
|
||||
assert proof['full_typed_snapshots']['before']['sha256'] == proof['full_typed_snapshots']['after']['sha256']
|
||||
assert set(directory.iterdir()) == {proof_path, *(Path(pin['path']) for pin in proof['full_typed_snapshots'].values())}
|
||||
return {'verified': True, 'report_sha256': sha(proof_path), 'waited_actual_us': proof['waited_actual_us'], 'deadline_reached': False, 'full_typed_snapshot_files': pins}
|
||||
|
||||
|
||||
def ogg_gate(proof_path, pid, overlay):
|
||||
proof = json.loads(proof_path.read_text())
|
||||
assert proof['Godot_pid'] == pid and proof['human_hearing_verified'] is False and proof['native_Windows'] == 'pending'
|
||||
roles = {'click': 'click_001.ogg', 'success': 'confirmation_001.ogg', 'refusal': 'error_008.ogg', 'notification': 'bong_001.ogg'}
|
||||
rows = {row['role']: row for row in proof['rows']}
|
||||
assert len(rows) == len(proof['rows']) == 4 and set(rows) == set(roles)
|
||||
for role, row in rows.items():
|
||||
assert row['encoded_source_sha256'] == overlay['assets/audio/ui/' + roles[role]] and row['class'] == 'AudioStreamOggVorbis'
|
||||
assert row['mix_rate'] > 0 and 0 < row['mixed_frames'] <= row['mix_rate'] * 2 and row['stream_seconds'] > 0
|
||||
assert row['finite'] is True and math.isfinite(row['sample_peak']) and 0 < row['sample_peak'] < 1
|
||||
assert row['one_shot_ended'] is True and row['player_playing_after_request'] is True and row['player_stopped_on_loaded_epoch'] is True
|
||||
return {'verified': True, 'report_sha256': sha(proof_path), 'actual_roles': sorted(rows), 'audio_driver': proof['audio_driver'], 'human_hearing_verified': False, 'native_Windows': 'pending'}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"profile_guard.gd": {
|
||||
"text": "extends RefCounted\n## External runtime-only bootstrap; changes neither source nor immutable PCK.\nstatic func activate() -> bool:\n\tvar custom_name := OS.get_environment(\"THE_TREE_PACK_PROFILE_NAME\")\n\tvar expected := OS.get_environment(\"THE_TREE_PACK_PROFILE_EXPECTED\")\n\tvar report_path := OS.get_environment(\"THE_TREE_PACK_PROFILE_REPORT\")\n\tvar before := OS.get_user_data_dir()\n\tvar name_regex := RegEx.new()\n\tvar regex_ok: bool = name_regex.compile(\"^TheTree-PCK-tests-[0-9a-f]{32}-[a-z0-9-]+$\") == OK\n\tvar fresh: bool = not expected.is_empty() and not DirAccess.dir_exists_absolute(expected) and not FileAccess.file_exists(expected)\n\tProjectSettings.set_setting(\"application/config/use_custom_user_dir\", true)\n\tProjectSettings.set_setting(\"application/config/custom_user_dir_name\", custom_name)\n\tvar actual := OS.get_user_data_dir()\n\tvar globalized := ProjectSettings.globalize_path(\"user://guard-probe\")\n\tvar conditions: Array[bool] = [regex_ok and name_regex.search(custom_name) != null,\n\t\tnot expected.is_empty() and expected.is_absolute_path() and actual == expected,\n\t\tglobalized == expected.path_join(\"guard-probe\"),\n\t\tbool(ProjectSettings.get_setting(\"application/config/use_custom_user_dir\", false)) and String(ProjectSettings.get_setting(\"application/config/custom_user_dir_name\", \"\")) == custom_name,\n\t\tfresh]\n\tvar valid: bool = not conditions.has(false)\n\tvar report := {\"verified\": valid, \"conditions\": conditions, \"conditions_checked\": 5,\n\t\t\"before\": before, \"actual_user_data_dir\": actual, \"expected_user_data_dir\": expected,\n\t\t\"globalized_probe\": globalized, \"custom_user_dir_name\": custom_name,\n\t\t\"fresh_before_parent_initialize\": fresh, \"runtime_settings_only\": true,\n\t\t\"project_settings_persisted\": false, \"profile_writes_before_guard\": 0}\n\tvar file: FileAccess = null\n\tif report_path.is_absolute_path() and not FileAccess.file_exists(report_path):\n\t\tfile = FileAccess.open(report_path, FileAccess.WRITE)\n\tif file == null:\n\t\tpush_error(\"PACK_PROFILE_GUARD cannot create fresh external evidence\")\n\t\treturn false\n\tfile.store_string(JSON.stringify(report, \"\\t\", true, true) + \"\\n\")\n\tfile.close()\n\tif not valid:\n\t\tpush_error(\"PACK_PROFILE_GUARD refused before parent save-writing initializer\")\n\tprint(\"PACK_PROFILE_GUARD verified=%s\" % str(valid))\n\treturn valid\n",
|
||||
"sha256": "597696c705cc8f0e79a35265ca3ef8df55f7fc3c4e0e5a2ce9a4e20e29e29a6f",
|
||||
"source_materialized_only_in_external_owned_output": true
|
||||
},
|
||||
"parse_full_dependencies.gd": {
|
||||
"text": "extends SceneTree\n\nconst Guard := preload(\"profile_guard.gd\")\n\nfunc _initialize() -> void:\n\tif not Guard.activate():\n\t\tquit(97)\n\t\treturn\n\tvar file := FileAccess.open(OS.get_environment(\"THE_TREE_FULL_PARSE_PATHS\"), FileAccess.READ)\n\tassert(file != null)\n\tvar paths: Array = JSON.parse_string(file.get_as_text())\n\tfile.close()\n\tvar unique := {}\n\tvar failures := 0\n\tfor path: String in paths:\n\t\tassert(not unique.has(path))\n\t\tunique[path] = true\n\t\tvar resource: Resource = load(path)\n\t\tif not resource is GDScript or not (resource as GDScript).can_instantiate():\n\t\t\tfailures += 1\n\t\t\tpush_error(\"FULL_DEPENDENCY_PARSE cannot instantiate: \" + path)\n\tprint(\"FULL_DEPENDENCY_PARSE checks=%d failures=%d\" % [unique.size(), failures])\n\tquit(0 if failures == 0 else 1)\n",
|
||||
"sha256": "585a0c1da5c050e3bebc5f66793a625419d2db27299f1131cf3a62ffd3236126",
|
||||
"source_materialized_only_in_external_owned_output": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
"""Finite original digest-NUL diagnostic contract; never alter native/raw results."""
|
||||
from pathlib import Path
|
||||
import hashlib, json, re
|
||||
HERE = Path(__file__).resolve().parent
|
||||
def sha(p): return hashlib.sha256(Path(p).read_bytes()).hexdigest()
|
||||
def require(ok, label):
|
||||
if not ok: raise AssertionError(label)
|
||||
def contract(): return json.loads((HERE / 'unicode-diagnostic-contract.json').read_bytes())
|
||||
def review_streams(native, streams, suite, source_path=None):
|
||||
"""Exact321 original attempts in stderr and logger, stdout0; every other error0.
|
||||
|
||||
Marker counts are separate. A positive completion cannot whitelist a
|
||||
diagnostic. This function never deletes/changes any line or DTO field.
|
||||
"""
|
||||
require(set(streams) == {'stdout', 'stderr', 'native_file'}, 'all actual diagnostic transports required')
|
||||
known = contract(); unicode = {}; other = {}; terminated = {}
|
||||
for transport, text in streams.items():
|
||||
require(type(text) is str, 'strict UTF8-decoded diagnostic transport')
|
||||
lines = text.splitlines(keepends=True)
|
||||
unicode[transport] = [l.rstrip('\r\n') for l in lines if 'Unicode parsing error' in l]
|
||||
terminated[transport] = all(l.endswith('\n') for l in lines if 'Unicode parsing error' in l)
|
||||
other[transport] = [l.rstrip('\r\n') for l in lines
|
||||
if any(t in l for t in ['ERROR:', 'SCRIPT ERROR:', 'Parse Error:'])
|
||||
or re.match(r'^\s*(?:FAIL:|FAILED:|FAIL )', l)
|
||||
or native.KNOWN_EXIT_LEAK.fullmatch(l.rstrip('\r\n'))]
|
||||
require(not any(other.values()), 'native ERROR/FAIL/ObjectDB/resource diagnostics never allowed')
|
||||
observed = {n: len(v) for n, v in unicode.items()}
|
||||
allowed = False
|
||||
if suite == known['test']:
|
||||
require(source_path is not None and sha(source_path) == known['source_sha256'], 'exact original NUL producer source required')
|
||||
require(observed == known['allowed_stream_distribution'], 'all original321 attempts and actual logger transport must agree')
|
||||
require(all(terminated.values()) and all(l == known['exact_line'] for lines in unicode.values() for l in lines),
|
||||
'exact terminated original NUL diagnostic only')
|
||||
require(unicode['stderr'] == unicode['native_file'], 'literal complete diagnostic sequence stderr/native logger equality')
|
||||
allowed = True
|
||||
else:
|
||||
require(not any(unicode.values()), 'Unicode outside exact original digest fixture is not allowed')
|
||||
return {'verified': True, 'original_NUL_fixture_contract_applied': allowed,
|
||||
'producer_source_sha256': known['source_sha256'] if allowed else None,
|
||||
'actual_transport_counts': observed, 'actual_complete_diagnostic_sequences': unicode,
|
||||
'unknown_or_error_or_leak_lines': other, 'raw_lines_deleted_or_rewritten': 0,
|
||||
'contract_sha256': sha(HERE / 'unicode-diagnostic-contract.json'),
|
||||
'allowed_e31_error_lines': known['allowed_immutable_e31_error_lines'] if allowed else []}
|
||||
|
||||
def diagnostic_result_gate(result, review):
|
||||
require(review['verified'] is True, 'actual finite diagnostic review required')
|
||||
expected = review['allowed_e31_error_lines']
|
||||
require(result['error_lines'] == expected, 'immutable e31 errors differ from exact finite diagnostic contract')
|
||||
raw = result['strict_native_diagnostics']
|
||||
require(raw['errors'] == expected and raw['banner_count'] == 1
|
||||
and raw['known_unicode_diagnostic_count'] == 0
|
||||
and raw['unrecognized_unicode_diagnostic_count'] == (321 if review['original_NUL_fixture_contract_applied'] else 0)
|
||||
and raw['exit_leak_diagnostic_lines'] == [], 'original e31 unmodified diagnostic report literal cardinality')
|
||||
return True
|
||||
@@ -0,0 +1,61 @@
|
||||
"""Whole real ordinary-owner proof parser; synthetic controls are not engine evidence."""
|
||||
from pathlib import Path
|
||||
import hashlib,json
|
||||
|
||||
def sha(p):return hashlib.sha256(Path(p).read_bytes()).hexdigest()
|
||||
def read(p):return json.loads(Path(p).read_bytes())
|
||||
def require(ok,msg):
|
||||
if not ok:raise AssertionError(msg)
|
||||
|
||||
def control_gate(control,pid):
|
||||
require(control['owned_engine_pid']==pid and type(control['owner_instance_id']) is int and control['owner_instance_id']>0 and control['passed'] is True,'actual owner/PID/pass')
|
||||
require(control['maximum_wait_us']==500000 and control['whole_Runtime_field_deletions']==0,'whole Runtime and bound')
|
||||
for key in ['ordinary_owner_release_verified','clock_process_flags_unchanged','root_gui_input_flag_unchanged','audio_teardown_passed']:require(control[key] is True,key)
|
||||
for key in ['gameplay_expectations_changed','clock_flags_intentionally_modified','application_close_called','SceneTree_quit_overridden','WindowQuit_verified','formal_acceptance']:require(control[key] is False,key)
|
||||
before,after=control['before'],control['after'];ids=before['owner_ids'];owner=control['owner_instance_id']
|
||||
require(all(type(i) is int and i>0 for i in ids) and ids==sorted(set(ids)) and owner in ids,'alive distinct owner set')
|
||||
expected=sorted(i for i in ids if i!=owner);require(control['expected_remaining_owner_ids']==expected and after['owner_ids']==expected,'ordinary exact owner removal')
|
||||
require(before['owner_meta_present'] is True and before['closing'] is False,'ordinary scope')
|
||||
for key in ['clock_process','clock_physics_process','clock_process_mode','root_gui_disable_input']:require(before[key]==after[key],'unchanged control '+key)
|
||||
if expected:require(after['owner_meta_present'] is True and after['closing'] is False and after['auto_accept_quit'] is False,'remaining owner retains shared coordinator')
|
||||
else:require(after['owner_meta_present'] is False and after['auto_accept_quit']==before['original_auto_accept_quit'],'last-owner restores original automatic quit')
|
||||
return owner
|
||||
|
||||
def audio_gate(path,pid,directory,out,owner):
|
||||
proof=read(path);require(path.name=='owner_'+str(owner)+'.json' and path.parent==directory,'actual audio owner filename')
|
||||
require(proof['owned_engine_pid']==pid and proof['owner_instance_id']==owner and proof['passed'] is True,'actual audio PID/owner/pass')
|
||||
require(proof['maximum_wait_us']==500000 and 0<proof['waited_actual_us']<500000 and proof['deadline_reached'] is False,'strict elapsed deadline')
|
||||
for key in ['owner_freed','all_owned_targets_released','whole_runtime_unchanged']:require(proof[key] is True,key)
|
||||
for key in ['bus_deleted','cues_muted_for_test','actual_human_hearing','WindowQuit_verified','formal_acceptance']:require(proof[key] is False,key)
|
||||
initial,final=proof['initial_targets'],proof['final_targets'];require(len(initial)==len(final)>=8,'actual WeakRef target count')
|
||||
signatures=lambda rows:[(r['kind'],r['role'],r['instance_id']) for r in rows]
|
||||
require(signatures(initial)==signatures(final) and len(set(signatures(initial)))==len(initial),'actual initial/final targets exact IDs')
|
||||
require(all(r['alive'] is True for r in initial) and all(r['alive'] is False for r in final),'real WeakRef release')
|
||||
for kind in ['stream','packet_sequence']:require({r['role'] for r in initial if r['kind']==kind}=={'click','success','refusal','notification'},'all four actual roles '+kind)
|
||||
snapshots=proof['full_typed_snapshots'];require(set(snapshots)=={'before','after'},'whole before/after pair')
|
||||
pins={};paths=[]
|
||||
for part,pin in snapshots.items():
|
||||
p=Path(pin['path']);require(p.parent==directory and p.name==path.stem+'-'+part+'.varbytes' and p.is_file() and not p.is_symlink(),'whole typed path')
|
||||
require(pin['field_deletions']==0 and p.stat().st_size==pin['bytes']>0 and sha(p)==pin['sha256'],'whole typed size/SHA/deletions')
|
||||
paths.append(p);pins[str(p.relative_to(out))]={'bytes':p.stat().st_size,'sha256':sha(p)}
|
||||
require(paths[0].read_bytes()==paths[1].read_bytes(),'literal allbytes before/after equality')
|
||||
return {'report_path':str(path),'report_sha256':sha(path),'owner_instance_id':owner,'waited_actual_us':proof['waited_actual_us'],'initial_targets':initial,'final_targets':final,'full_typed_snapshot_files':pins},set(paths)|{path}
|
||||
|
||||
def ordinary_gate(directory,pid,out,labels):
|
||||
directory=Path(directory);out=Path(out);require(directory.is_relative_to(out) and not directory.is_symlink(),'owned proof directory')
|
||||
require(labels and len(labels)==len(set(labels)),'unique planned labels')
|
||||
names=['control.json'] if labels==['single'] else ['control-'+label+'.json' for label in labels]
|
||||
audio=directory/'owned-audio';require(audio.is_dir() and not audio.is_symlink(),'real owned audio directory')
|
||||
require(set(directory.iterdir())=={audio,*[directory/n for n in names]},'all actual control files exact; no duplicate/missing/extra')
|
||||
rows=[];ids=[];audio_paths=set()
|
||||
for label,name in zip(labels,names):
|
||||
p=directory/name;require(p.is_file() and not p.is_symlink(),'control regular file')
|
||||
control=read(p);owner=control_gate(control,pid);require(owner not in ids,'distinct actual sequential owner IDs')
|
||||
detail,paths=audio_gate(audio/('owner_'+str(owner)+'.json'),pid,audio,out,owner);audio_paths|=paths;ids.append(owner)
|
||||
rows.append({'label':label,'actual_owner_instance_id':owner,'control_path':str(p),'control_sha256':sha(p),'control':control,'audio':detail})
|
||||
require(set(audio.iterdir())==audio_paths,'all actual audio JSON/typed set exactly enumerated')
|
||||
if len(labels)>1:
|
||||
# These three exact fixtures create the replacement Main only after releasing
|
||||
# its predecessor. Do not invent continuity of Runtime between gameplay steps.
|
||||
for row in rows:require(row['control']['before']['owner_ids']==[row['actual_owner_instance_id']] and row['control']['after']['owner_ids']==[],'sequential source owner scope')
|
||||
return {'verified':True,'planned_label_sequence':labels,'actual_owner_ID_sequence':ids,'unique_controlproof_paths':[r['control_path'] for r in rows],'ordinary_owner_pairs':rows,'expected_control_count':len(labels),'actual_audio_report_count':len(rows),'whole_typed_field_deletions':0,'Clock_GUI_flags_intentionally_modified':False,'application_close_called':False,'all_files_enumerated':True,'WindowQuit_verified':False,'formal_acceptance':False}
|
||||
@@ -0,0 +1,225 @@
|
||||
"""Owned child lifecycle reused from exact c007e helper, strict raw diagnostics.
|
||||
No external process/group signals. A tracked child Popen is finally killed/waited
|
||||
by the vendored helper even if a START/END receipt cannot be written.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import hashlib, json, os, re, subprocess, time
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
HELPER_SHA = 'c007e712c281573254faa1233c2ac92e100ab87393256a6dc096b35283e43037'
|
||||
_helper_file = HERE / 'p09_owned_engine_process.py'
|
||||
if hashlib.sha256(_helper_file.read_bytes()).hexdigest() != HELPER_SHA:
|
||||
raise RuntimeError('Exact previously reviewed owned-child helper changed')
|
||||
helper = {'__name__': 'owned_diagnostic_vendored_helper', '__file__': str(_helper_file)}
|
||||
exec(compile(_helper_file.read_bytes(), str(_helper_file), 'exec'), helper)
|
||||
BANNER = 'Godot Engine v4.7.2.stable.official.ed1daf0bf - https://godotengine.org'
|
||||
# Exact finite diagnostic families from the preserved strict collector r06.
|
||||
KNOWN_EXIT_LEAK = re.compile(r'^\s*(?:(?:WARNING:|ERROR:)[^\r\n]*(?:ObjectDB instances leaked|(?:[0-9]+ )?ObjectDB instances were leaked at exit|resources? still in use at exit)|Leaked instance:)[^\r\n]*$')
|
||||
DIGEST_SCRIPT = 'res://tests/performance/test_digest_performance.gd'
|
||||
DIGEST_SOURCE_SHA = '363772fc43975f12c9e2056e2b01beb4cd8b2d7544db7528f80c6bd127710c1f'
|
||||
DIGEST_UNICODE_LINE = 'Unicode parsing error, some characters were replaced with � (U+FFFD): Unexpected NUL character'
|
||||
|
||||
|
||||
def process_probe(pid):
|
||||
try:
|
||||
raw = subprocess.check_output(['ps', '-p', str(pid), '-o', 'pid=,ppid=,pgid=,args='], text=True).strip()
|
||||
except subprocess.CalledProcessError:
|
||||
return None
|
||||
parts = raw.split(None, 3)
|
||||
if len(parts) != 4:
|
||||
return None
|
||||
return {'pid': int(parts[0]), 'ppid': int(parts[1]), 'pgid': int(parts[2]), 'args': parts[3]}
|
||||
|
||||
|
||||
def native_diagnostics(stdout, stderr, argv):
|
||||
lines = stdout.splitlines(keepends=True) + stderr.splitlines(keepends=True)
|
||||
banners = [line for line in lines if line.startswith('Godot Engine v')]
|
||||
errors = []
|
||||
if len(banners) != 1 or banners[0] not in [BANNER + '\n', BANNER + '\r\n']:
|
||||
errors.append('Missing, duplicate, wrong, or unterminated exact native engine banner')
|
||||
for raw in lines:
|
||||
line = raw.rstrip('\r\n')
|
||||
if any(token in line for token in ['ERROR:', 'SCRIPT ERROR:', 'Parse Error:']) or re.match(r'^\s*(?:FAIL:|FAILED:|FAIL )', line):
|
||||
errors.append(line)
|
||||
if KNOWN_EXIT_LEAK.fullmatch(line):
|
||||
errors.append(line)
|
||||
unicode_lines = [line.rstrip('\r\n') for line in lines if 'Unicode parsing error' in line]
|
||||
allowed_unicode = False
|
||||
if unicode_lines and '--script' in argv and '--path' in argv:
|
||||
script = argv[argv.index('--script') + 1]
|
||||
stage = Path(argv[argv.index('--path') + 1])
|
||||
source = stage / DIGEST_SCRIPT.removeprefix('res://')
|
||||
allowed_unicode = script == DIGEST_SCRIPT and source.is_file() and hashlib.sha256(source.read_bytes()).hexdigest() == DIGEST_SOURCE_SHA and all(line == DIGEST_UNICODE_LINE for line in unicode_lines)
|
||||
if unicode_lines and not allowed_unicode:
|
||||
errors.append('Unrecognized Unicode diagnostic or source pin')
|
||||
return {'errors': errors, 'banner_count': len(banners), 'known_unicode_diagnostic_count': len(unicode_lines) if allowed_unicode else 0,
|
||||
'unrecognized_unicode_diagnostic_count': 0 if allowed_unicode else len(unicode_lines),
|
||||
'exit_leak_diagnostic_lines': [line.rstrip('\r\n') for line in lines if KNOWN_EXIT_LEAK.fullmatch(line.rstrip('\r\n'))]}
|
||||
|
||||
|
||||
def cleanup_actual_owned_child(process):
|
||||
"""Extra bounded cleanup of exactly the factory's actual Popen object.
|
||||
The old helper's bytes/three tries remain unchanged. These are at most
|
||||
three additional poll/kill/wait attempts, each explicit wait at most 5s.
|
||||
Receipt completeness is independent of actual child-ended observation.
|
||||
"""
|
||||
rows = []
|
||||
code = None
|
||||
if process is None:
|
||||
return {'owned_child_created': False, 'engine_ended': False,
|
||||
'actual_process_exit_code': None, 'attempts': [], 'errors': []}
|
||||
errors = []
|
||||
for attempt in range(3):
|
||||
row = {'attempt': attempt + 1, 'kill_attempted': False, 'wait_timeout_seconds': 5,
|
||||
'wait_completed': False, 'errors': []}
|
||||
try:
|
||||
code = process.poll()
|
||||
row['poll_before'] = code
|
||||
except BaseException as caught:
|
||||
code = None
|
||||
row['errors'].append('poll before ' + type(caught).__name__ + ': ' + str(caught))
|
||||
if code is None:
|
||||
row['kill_attempted'] = True
|
||||
try:
|
||||
process.kill()
|
||||
except BaseException as caught:
|
||||
row['errors'].append('kill ' + type(caught).__name__ + ': ' + str(caught))
|
||||
try:
|
||||
waited = process.wait(timeout=5)
|
||||
row['wait_completed'] = True
|
||||
row['wait_returned_code'] = waited
|
||||
if type(waited) is int:
|
||||
code = waited
|
||||
except BaseException as caught:
|
||||
row['errors'].append('wait ' + type(caught).__name__ + ': ' + str(caught))
|
||||
try:
|
||||
polled = process.poll()
|
||||
row['poll_after'] = polled
|
||||
if polled is not None:
|
||||
code = polled
|
||||
except BaseException as caught:
|
||||
row['errors'].append('poll after ' + type(caught).__name__ + ': ' + str(caught))
|
||||
rows.append(row)
|
||||
errors.extend(row['errors'])
|
||||
if code is not None:
|
||||
break
|
||||
return {'owned_child_created': True, 'engine_ended': code is not None,
|
||||
'actual_process_exit_code': code, 'attempts': rows, 'errors': errors}
|
||||
|
||||
|
||||
def run_owned_diagnostic(argv, folder, timeout, environment, *, now_fn, dump_fn, sha_fn, live_engines_fn,
|
||||
popen_fn=None, probe_fn=None, monotonic_fn=None):
|
||||
if live_engines_fn():
|
||||
raise RuntimeError('Another Godot process owns CPU; diagnostic did not start')
|
||||
folder = Path(folder)
|
||||
folder.mkdir(exist_ok=False)
|
||||
started = now_fn()
|
||||
clock = monotonic_fn or time.monotonic
|
||||
tick = clock()
|
||||
owned = {'process': None, 'start': None, 'end': None, 'start_written': False, 'end_written': False}
|
||||
probe = probe_fn or process_probe
|
||||
actual_popen = popen_fn or subprocess.Popen
|
||||
cwd = Path(argv[argv.index('--path') + 1]) if '--path' in argv else folder
|
||||
with (folder / 'stdout.log').open('wb') as stdout, (folder / 'stderr.log').open('wb') as stderr:
|
||||
def spawn(command, **options):
|
||||
# No receipt, probe, or other failure-prone work before returning
|
||||
# the actual Popen to the helper's owned try/finally.
|
||||
process = actual_popen(command, cwd=options['cwd'], env=options['env'], stdout=stdout, stderr=stderr, start_new_session=True)
|
||||
owned['process'] = process
|
||||
class HelperPopenView:
|
||||
# Preserve the old helper's actual object identity externally.
|
||||
# Only its unbounded wait(None) is mapped to a 5s wait; normal
|
||||
# explicit import/probe/BUILD timeout arguments pass unchanged.
|
||||
@property
|
||||
def pid(self):
|
||||
return process.pid
|
||||
def poll(self):
|
||||
return process.poll()
|
||||
def kill(self):
|
||||
return process.kill()
|
||||
def wait(self, timeout=None):
|
||||
return process.wait(timeout=5 if timeout is None else timeout)
|
||||
return HelperPopenView()
|
||||
|
||||
def write_start(receipt):
|
||||
process = owned['process']
|
||||
if process is None or receipt['actual_pid'] != process.pid or receipt['command'] != argv or receipt['owner_pid'] != os.getpid():
|
||||
raise RuntimeError('START binding differs from actual owned Popen/argv/owner')
|
||||
current = probe(process.pid)
|
||||
exact_current = current is not None and current['pid'] == process.pid and current['ppid'] == os.getpid() and current['pgid'] == process.pid and current['args'] == ' '.join(argv)
|
||||
ended_before_probe = current is None and process.poll() is not None
|
||||
record = dict(receipt, pid=process.pid, argv=argv, started_utc=started,
|
||||
current_process_identity=current, live_identity_exact=exact_current,
|
||||
tracked_child_ended_before_initial_probe=ended_before_probe,
|
||||
signal_scope='tracked Popen child only; no external group signal')
|
||||
owned['start'] = record
|
||||
if not exact_current and not ended_before_probe:
|
||||
raise RuntimeError('Actual owned child live PID/PPID/PGID/argv identity mismatch')
|
||||
dump_fn(folder / 'started.json', record)
|
||||
owned['start_written'] = True
|
||||
print('RUNTIME_SCALE_PROCESS_START ' + json.dumps({'name': folder.name, 'pid': process.pid}), flush=True)
|
||||
|
||||
def write_end(receipt):
|
||||
process = owned['process']
|
||||
if receipt['command'] != argv or receipt['owner_pid'] != os.getpid() or receipt['actual_pid'] != (process.pid if process is not None else None):
|
||||
raise RuntimeError('END binding differs from actual tracked Popen/argv/owner')
|
||||
record = dict(receipt, pid=process.pid if process is not None else None, argv=argv,
|
||||
ended_utc=now_fn(), signal_scope='tracked Popen child only; no external group signal')
|
||||
owned['end'] = record
|
||||
dump_fn(folder / 'ended.json', record)
|
||||
owned['end_written'] = True
|
||||
|
||||
class LifecycleTextSink:
|
||||
def write(self, text):
|
||||
return stdout.write(text.encode('utf-8'))
|
||||
observed = None
|
||||
adapter_error = None
|
||||
cleanup = None
|
||||
try:
|
||||
observed = helper['run_owned'](argv, cwd, environment, LifecycleTextSink(), {'scope': 'isolated diagnostic owned child'}, timeout,
|
||||
write_start, write_end, popen=spawn)
|
||||
except BaseException as caught:
|
||||
# Includes a second interrupt in the old helper's unguarded poll.
|
||||
adapter_error = type(caught).__name__ + ': ' + str(caught)
|
||||
finally:
|
||||
# Keep the actual factory object, never discover or signal a PID
|
||||
# or group from ps/receipts. All cleanup operations are bounded and
|
||||
# catch BaseException individually, even after helper START failure.
|
||||
cleanup = cleanup_actual_owned_child(owned['process'])
|
||||
if observed is None:
|
||||
observed = {'actual_pid': owned['process'].pid if owned['process'] is not None else None,
|
||||
'exit_code': 130 if adapter_error and adapter_error.startswith('KeyboardInterrupt') else 127,
|
||||
'actual_process_exit_code': cleanup['actual_process_exit_code'],
|
||||
'engine_ended': cleanup['engine_ended'], 'timed_out': None,
|
||||
'lifecycle_error': 'Helper escaped before returning: ' + str(adapter_error),
|
||||
'start_receipt_written': owned['start_written'], 'end_receipt_written': owned['end_written']}
|
||||
dump_fn(folder / 'adapter-owned-cleanup.json', cleanup)
|
||||
raw = native_diagnostics((folder / 'stdout.log').read_text(errors='replace'), (folder / 'stderr.log').read_text(errors='replace'), argv)
|
||||
errors = list(raw['errors'])
|
||||
if adapter_error is not None or cleanup['errors'] or not cleanup['engine_ended']:
|
||||
errors.append('Additional owned cleanup failed/interrupted: ' + str(adapter_error) + '; ' + str(cleanup['errors']))
|
||||
if observed['lifecycle_error'] is not None or not observed['engine_ended'] or not observed['start_receipt_written'] or not observed['end_receipt_written']:
|
||||
errors.append('Owned lifecycle/START/END receipt incomplete: ' + str(observed['lifecycle_error']))
|
||||
lifecycle_errors = [str(value) for value in [observed['lifecycle_error'], adapter_error] if value is not None] + cleanup['errors']
|
||||
result = {'argv': argv, 'pid': owned['process'].pid if owned['process'] is not None else None, 'started_utc': started, 'ended_utc': now_fn(),
|
||||
'wall_seconds': clock() - tick, 'exit_code': cleanup['actual_process_exit_code'] if cleanup['actual_process_exit_code'] is not None else observed['exit_code'],
|
||||
'helper_exit_code': observed['exit_code'], 'timed_out': observed['timed_out'],
|
||||
'stdout_sha256': sha_fn(folder / 'stdout.log'), 'stderr_sha256': sha_fn(folder / 'stderr.log'), 'error_lines': errors,
|
||||
'owned_popen_ended': cleanup['engine_ended'], 'old_helper_engine_ended': observed['engine_ended'], 'start_receipt_written': observed['start_receipt_written'],
|
||||
'end_receipt_written': observed['end_receipt_written'], 'lifecycle_error': '; '.join(lifecycle_errors) if lifecycle_errors else None,
|
||||
'old_helper_lifecycle_error': observed['lifecycle_error'],
|
||||
'actual_start_binding': owned['start'], 'actual_end_binding': owned['end'], 'strict_native_diagnostics': raw,
|
||||
'external_process_or_group_signals': 0, 'owned_child_process_helper_sha256': HELPER_SHA,
|
||||
'adapter_owned_cleanup': cleanup, 'adapter_error': adapter_error,
|
||||
'old_helper_bytes_preserved': True, 'helper_wait_None_maps_to_actual_timeout_seconds': 5,
|
||||
'explicit_helper_wait_timeouts_preserved': True,
|
||||
'additional_cleanup_wait_timeout_seconds': 5, 'additional_cleanup_max_attempts': 3}
|
||||
dump_fn(folder / 'process.json', result)
|
||||
return result
|
||||
|
||||
|
||||
def process_gate(result, require_fn):
|
||||
require_fn(result['exit_code'] == 0 and result['timed_out'] is False and not result['error_lines']
|
||||
and result['owned_popen_ended'] is True and result['start_receipt_written'] is True
|
||||
and result['end_receipt_written'] is True and result['lifecycle_error'] is None,
|
||||
'Owned exit/timeout/START/END/exact banner/diagnostic gate failed')
|
||||
@@ -0,0 +1,76 @@
|
||||
"""Future owned admission: immutable e31/c007 plus actual-Popen outer capture.
|
||||
|
||||
Path r02 finish_captured is copied literally below. Fake controls are not actual
|
||||
engine evidence. No PID discovery or foreign/group signals are used for cleanup.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import importlib.util, subprocess
|
||||
HERE=Path(__file__).resolve().parent
|
||||
spec=importlib.util.spec_from_file_location('exact_e31_full_native',HERE/'owned_diagnostic_process.py')
|
||||
native=importlib.util.module_from_spec(spec);spec.loader.exec_module(native)
|
||||
OWNED=[]
|
||||
|
||||
def finish_captured(processes, cleanup_fn=None):
|
||||
"""Bound caller interruptions per captured object; never discover or signal others.
|
||||
|
||||
The immutable helper itself has at most3 waits of5s. At most3 calls here
|
||||
handle an exception escaping that helper boundary, not a general I/O bound.
|
||||
Every error stays fatal to the phase even if a later cleanup confirms ended.
|
||||
"""
|
||||
cleanup_fn = cleanup_fn or native.cleanup_actual_owned_child
|
||||
rows = []; errors = []
|
||||
for process in processes:
|
||||
cleanup = None; caught_errors = []
|
||||
for attempt in range(3):
|
||||
try:
|
||||
cleanup = cleanup_fn(process); break
|
||||
except BaseException as caught:
|
||||
caught_errors.append(type(caught).__name__ + ': ' + str(caught))
|
||||
ended = isinstance(cleanup, dict) and cleanup.get('engine_ended') is True
|
||||
helper_errors = cleanup.get('errors', []) if isinstance(cleanup, dict) else []
|
||||
rows.append({'pid': process.pid, 'cleanup': cleanup,
|
||||
'outer_cleanup_errors': caught_errors, 'engine_ended': ended if cleanup is not None else None,
|
||||
'outer_maximum_helper_invocations': 3, 'helper_maximum_waits': 3,
|
||||
'per_wait_timeout_seconds': 5, 'foreign_process_or_group_signals': 0})
|
||||
if caught_errors or not ended or helper_errors:
|
||||
errors.append('captured child cleanup incomplete/interrupted ' + str(process.pid))
|
||||
return rows, errors
|
||||
|
||||
def run_phase(argv, folder, timeout, env, *, now_fn, dump_fn, sha_fn, live_engines_fn,
|
||||
popen_fn=None, diagnostic_fn=None, cleanup_fn=None):
|
||||
"""Capture actual object before adapter receipt work; always clean before I/O.
|
||||
|
||||
A START/END/outer write failure stays fatal even after child termination.
|
||||
The full runner also retains OWNED for one final bounded sweep before guards.
|
||||
"""
|
||||
actual_popen=popen_fn or subprocess.Popen
|
||||
diagnostic_fn=diagnostic_fn or native.run_owned_diagnostic
|
||||
captured=[]
|
||||
def spawn(command, **options):
|
||||
process=actual_popen(command,**options)
|
||||
captured.append(process);OWNED.append(process)
|
||||
return process
|
||||
result=None;error=None;rows=[];errors=[]
|
||||
try:
|
||||
result=diagnostic_fn(argv,folder,timeout,env,now_fn=now_fn,dump_fn=dump_fn,
|
||||
sha_fn=sha_fn,live_engines_fn=live_engines_fn,popen_fn=spawn)
|
||||
except BaseException as caught:
|
||||
error=type(caught).__name__+': '+str(caught)
|
||||
finally:
|
||||
rows,errors=finish_captured(captured,cleanup_fn)
|
||||
outer={'actual_captured_pids':[p.pid for p in captured],'cleanup':rows,
|
||||
'adapter_exception':error,'all_captured_ended':bool(captured) and all(r['engine_ended'] is True for r in rows),
|
||||
'outer_cleanup_errors':errors,'foreign_process_or_group_signals':0,
|
||||
'all_cleanup_before_outer_receipt':True,'outer_receipt_written':False,'outer_receipt_error':None}
|
||||
try:
|
||||
# Existence plus SHA confirms durability; the file cannot truthfully
|
||||
# describe whether its own future write has returned. Keep that result
|
||||
# in the caller's later phase receipt, separate from actual child exit.
|
||||
body={key:value for key,value in outer.items()
|
||||
if key not in ['outer_receipt_written','outer_receipt_error']}
|
||||
dump_fn(Path(folder)/'outer-owned-finally.json',body)
|
||||
outer['outer_receipt_written']=True
|
||||
except BaseException as caught:
|
||||
outer['outer_receipt_error']=type(caught).__name__+': '+str(caught)
|
||||
outer['passed']=bool(result) and error is None and outer['all_captured_ended'] and not errors and outer['outer_receipt_written']
|
||||
return result,outer
|
||||
@@ -0,0 +1,48 @@
|
||||
"""Owned subprocess lifecycle and read-only per-phase admission. No game code."""
|
||||
import os,re,shutil,subprocess,time
|
||||
BANNER='Godot Engine v4.7.2.stable.official.ed1daf0bf - https://godotengine.org'
|
||||
MINIMUM_FREE_BYTES=300*1024*1024
|
||||
LEAK=re.compile(r'(?im)^\s*(?:(?:WARNING:|ERROR:)[^\r\n]*(?:ObjectDB instances leaked|resources? still in use at exit)|Leaked instance:)[^\r\n]*$')
|
||||
def engine_banner_valid(raw):
|
||||
lines=[line for line in raw.splitlines(keepends=True)if line.startswith('Godot Engine v')]
|
||||
return len(lines)==1 and lines[0]in[BANNER+'\n',BANNER+'\r\n']
|
||||
def leak_lines(raw):return LEAK.findall(raw)
|
||||
def strict_engine_raw(raw):
|
||||
return engine_banner_valid(raw)and not leak_lines(raw)and not any(t in raw for t in ['ERROR:','SCRIPT ERROR:','Parse Error:'])and not re.search(r'(?m)^\s*(?:FAIL:|FAILED:|FAIL )',raw)
|
||||
def live_engines():
|
||||
return[s for s in subprocess.check_output(['ps','-axo','pid=,comm='],text=True).splitlines()if 'godot'in s.lower()]
|
||||
def admission(root,scan=None,free=None):
|
||||
observed=(scan or live_engines)();available=(free or(lambda p:shutil.disk_usage(p).free))(root)
|
||||
if observed:raise RuntimeError('external/previous Godot present before owned phase: '+str(observed))
|
||||
if type(available)is not int or available<MINIMUM_FREE_BYTES:raise RuntimeError('fresh free disk must be >=300MiB before every phase')
|
||||
return {'live_engines_before':observed,'free_bytes_before':available,'minimum_free_bytes':MINIMUM_FREE_BYTES}
|
||||
def run_owned(command,root,environment,stream,context,timeout,write_start,write_end,popen=None,monotonic_ns=None):
|
||||
process=None;started=None;end=None;exit_code=127;error=None;timed_out=False;start_written=False;end_written=False
|
||||
now=monotonic_ns or time.monotonic_ns
|
||||
try:
|
||||
process=(popen or subprocess.Popen)(command,cwd=root,env=environment,stdout=stream,stderr=subprocess.STDOUT)
|
||||
started=dict(context,event='ENGINE_STARTED',actual_pid=process.pid,owner_pid=os.getpid(),command=command,monotonic_ns=now())
|
||||
write_start(started);start_written=True
|
||||
try:exit_code=process.wait(timeout=timeout)
|
||||
except subprocess.TimeoutExpired:
|
||||
timed_out=True;process.kill();process.wait();exit_code=124
|
||||
except BaseException as caught:
|
||||
error=type(caught).__name__+': '+str(caught);exit_code=130 if isinstance(caught,KeyboardInterrupt)else 127
|
||||
finally:
|
||||
if process is not None:
|
||||
# A second interrupt during cleanup still retries exactly this owned child.
|
||||
# Persistent OS kill/wait errors fail closed and are exposed, never accepted.
|
||||
for attempt in range(3):
|
||||
if process.poll()is not None:break
|
||||
try:process.kill();process.wait(timeout=5)
|
||||
except BaseException as caught:
|
||||
error=(error+'; 'if error else'')+'owned cleanup '+type(caught).__name__+': '+str(caught)
|
||||
ended=process is not None and process.poll()is not None
|
||||
end=dict(context,event='ENGINE_ENDED',actual_pid=process.pid if process is not None else None,owner_pid=os.getpid(),command=command,monotonic_ns=now(),exit_code=exit_code,actual_process_exit_code=process.poll()if process is not None else None,engine_ended=ended,timed_out=timed_out,lifecycle_error=error,start_receipt_written=start_written)
|
||||
try:write_end(end);end_written=True
|
||||
except BaseException as caught:
|
||||
error=(error+'; 'if error else'')+'end receipt '+type(caught).__name__+': '+str(caught)
|
||||
if error:
|
||||
try:stream.write('RUNNER_LIFECYCLE_ERROR: '+error+'\n')
|
||||
except OSError:pass
|
||||
return {'actual_pid':process.pid if process is not None else None,'exit_code':exit_code,'actual_process_exit_code':process.poll()if process is not None else None,'engine_ended':ended,'timed_out':timed_out,'lifecycle_error':error,'start_receipt_written':start_written,'end_receipt_written':end_written,'start':started,'end':end}
|
||||
@@ -0,0 +1,102 @@
|
||||
"""Independent guarded-standard protocol reader; raw and Runtime bytes unchanged."""
|
||||
from pathlib import Path
|
||||
import hashlib, importlib.util, json, re
|
||||
HERE=Path(__file__).resolve().parent
|
||||
def load(name,path):
|
||||
spec=importlib.util.spec_from_file_location(name,path);value=importlib.util.module_from_spec(spec);spec.loader.exec_module(value);return value
|
||||
def read(path):return json.loads(Path(path).read_bytes())
|
||||
def sha(path):return hashlib.sha256(Path(path).read_bytes()).hexdigest()
|
||||
def require(ok,reason):
|
||||
if not ok:raise AssertionError(reason)
|
||||
def review(out,root,expected,contracts,owner_pid):
|
||||
out=Path(out);root=Path(root);runner=load('guarded_reader_exact_runner',HERE.parent/'run_all_tests.py')
|
||||
marker=load('guarded_reader_marker',HERE.parent/'strict_test_markers.py')
|
||||
outer=load('guarded_reader_owned',HERE/'owned_outer.py');diagnostics=load('guarded_reader_diagnostics',HERE/'diagnostic_gate.py')
|
||||
owners=load('guarded_reader_ordinary',HERE/'ordinary_proof_gate.py');audio=load('guarded_reader_audio',HERE/'audio_evidence_gate.py')
|
||||
initial=read(out/'runner-input.json');ended=read(out/'runner-ended.json');rows=read(out/'all-tests.json') if (out/'all-tests.json').exists() else []
|
||||
paths=['tests/run_tests.gd']+sorted(n for n in expected if n.startswith('tests/') and n.endswith('.gd') and Path(n).name.startswith('test') and 'export'not in Path(n).parts and n!='tests/s0/test_kernel_and_save.gd')
|
||||
reviewed=[];errors=[];actual_pids=[];policy=read(HERE/'suite-policy.json')
|
||||
require(initial['protocol']==ended['protocol']=='guarded-standard-v1' and initial['owner_runner_pid']==ended['owner_runner_pid']==owner_pid,'actual in-process runner owner/protocol')
|
||||
require(initial['project_root']==str(root) and initial['roster']==paths and initial['selected_source_map']==expected,'literal logical original suite/source roster')
|
||||
require(initial['runner_sha256']==expected['tools/run_all_tests.py'] and initial['marker_module_sha256']==expected['tools/strict_test_markers.py'],'actual runner/marker source')
|
||||
require(ended['run_id']==initial['run_id'] and ended['all_tests_sha256']==(sha(out/'all-tests.json') if rows else None),'actual result identity/SHA')
|
||||
stage=Path(initial['stage']);require(stage==out/'stage' and stage.is_dir() and not stage.is_symlink(),'one owned private stage')
|
||||
phases=ended['phases'];logical=[('import',None),('parse',None)]+[(str(i).zfill(3),n)for i,n in enumerate(paths)]
|
||||
require([(p['phase'],p['test'])for p in phases]==logical[:len(phases)] and len(phases)<=len(logical),'prefix-only import/parse/original suite order')
|
||||
require([r['test']for r in rows]==[p['test']for p in phases if p['test'] is not None],'all actual suites correspond one phase')
|
||||
previous={};expected_stage={n:{'bytes':(stage/n).stat().st_size,'sha256':digest}for n,digest in expected.items()}
|
||||
for index,phase in enumerate(phases):
|
||||
label=phase['phase'];test=phase['test'];folder=out/label;source=stage/test if test else None
|
||||
try:
|
||||
raw_stdout=(folder/'stdout.log').read_text();raw_stderr=(folder/'stderr.log').read_text();raw=raw_stdout+raw_stderr
|
||||
native=folder/'engine-native.log';result=read(folder/'process.json');start=read(folder/'started.json');end=read(folder/'ended.json')
|
||||
pid=result['pid'];require(type(pid)is int and pid>0 and pid not in actual_pids,'actual distinct native PID');actual_pids.append(pid)
|
||||
for receipt in [start,end]:require(receipt['actual_pid']==pid and receipt['command']==phase['command'] and receipt['owner_pid']==owner_pid,'actual START/END owner/argv/PID')
|
||||
require(start['monotonic_ns']<=end['monotonic_ns'],'actual start/end monotonic order')
|
||||
require(result==phase['native_process'] and result['stdout_sha256']==sha(folder/'stdout.log') and result['stderr_sha256']==sha(folder/'stderr.log'),'immutable process receipt/raw streams')
|
||||
require(phase['raw_native_log']=={'path':str(native),'bytes':native.stat().st_size,'sha256':sha(native)},'absolute native log size/SHA')
|
||||
require(phase['command'][:4]==[initial['engine'],'--headless','--path',str(stage)] and phase['command'][-2:]==['--log-file',str(native)],'actual private resource root/logger binding')
|
||||
if label=='import':require(phase['command'][4:-2]==['--editor','--import','--quit','--verbose'],'actual import recipe')
|
||||
else:
|
||||
script=out/'bootstrap/parse_full_dependencies.gd' if label=='parse' else out/'starters'/(label+'.gd')
|
||||
require(phase['command'][4:-2]==['--script',str(script),'--verbose'] and sha(script)==phase['starter_sha256'],'actual external bootstrap argv/bytes')
|
||||
if test:
|
||||
text='extends '+json.dumps('res://'+test)+'\nconst Guard := preload('+json.dumps(str(out/'bootstrap/profile_guard.gd'))+')\nfunc _initialize() -> void:\n\tif not Guard.activate():\n\t\tquit(97)\n\t\treturn\n\tsuper._initialize()\n'
|
||||
require(script.read_text()==text and phase['logical_script']=='res://'+test,'literal logical suite binding; actual argv is never rewritten')
|
||||
finite=diagnostics.review_streams(outer.native,{'stdout':raw_stdout,'stderr':raw_stderr,'native_file':native.read_text()},test,source)
|
||||
require(finite==phase['finite_original_diagnostic_review'] and diagnostics.diagnostic_result_gate(result,finite)
|
||||
and runner.process_gate(result,finite['allowed_e31_error_lines']),'strict actual raw/error/leak/finite321 contract')
|
||||
capture=phase['outer_actual_capture'];require(capture['actual_captured_pids']==[pid] and capture['passed'] is True and capture['all_captured_ended'] is True and capture['outer_cleanup_errors']==[],'actual captured native ownership')
|
||||
before=read(out/(label+'-startup-profile-before.json'));startup=phase['whole_startup_profile_after']
|
||||
require(before=={'path':startup['path'],'existed':False,'files':{},'before_native_spawn':True} and runner.inventory(startup['path'])==startup['files'],'full fresh startup profile including logger/recovery locks')
|
||||
require(startup['path']==str(runner.profile_path(phase['profile_startup_name'])),'owned startup name/path')
|
||||
if label!='import':
|
||||
profile=phase['whole_runtime_profile_after'];require(runner.inventory(profile['path'])==profile['files'],'full actual runtime private profile')
|
||||
actual=runner.profile_gate(Path(profile['path']),out/(label+'-profile.json'),phase['controlled_environment']['THE_TREE_PACK_PROFILE_NAME'])
|
||||
require(actual==phase['actual_private_profile'],'all five actual Guard conditions and whole runtime profile')
|
||||
if label=='parse':
|
||||
count=len(read(out/'parse-paths.json'));matches=re.findall(r'(?m)^FULL_DEPENDENCY_PARSE checks=([0-9]+) failures=([0-9]+)\n',raw)
|
||||
require(matches==[(str(count),'0')] and phase['actual_parse_checks']==count,'actual all-source parse count')
|
||||
if test:
|
||||
parsed=marker.parse_raw(test,raw,contracts,expected[test]);require(parsed['passed'] and parsed==phase['completion'],'raw original marker schema/count/phase')
|
||||
raw_path=out/(test.replace('/','__')+'.log');require(raw_path.read_text()==raw and sha(raw_path)==phase['log_sha256'],'whole stdout/stderr copied without modification')
|
||||
require(phase['source_sha256']==phase['source_sha256_end']==expected[test] and sha(source)==expected[test],'literal suite source SHA')
|
||||
p=runner.phase_policy(test,source,policy['suite_policies']);require(p==phase['policy'],'exact proof-policy source')
|
||||
route=policy['outside_routes'].get(test)
|
||||
if route:require(runner.routed_report(out/(label+'-evidence'),route,test,pid,raw,stage,expected[test])==phase['outside_report'],'fresh report provenance')
|
||||
proof=out/(label+'-teardown')
|
||||
if p['ordinary_cleanup_prepared']:require(owners.ordinary_gate(proof,pid,out,p['ordinary_control_labels'])==phase['ordinary_owner_pairs'],'whole ordinary owner pairs/control/WeakRef proof')
|
||||
if p['old_bounded_teardown']or test=='tests/ui/test_ui_audio_feedback.gd':require(audio.teardown_gate(proof,pid,out)==phase['old_bounded_proof'],'old bounded wholetyped/WeakRef proof')
|
||||
if test=='tests/ui/test_ui_audio_feedback.gd':
|
||||
audio_dir=out/(label+'-audio-proof')
|
||||
require(audio.command_gate(audio_dir/'actual-command-result-buffer-proof.json',audio_dir,pid)==phase['actual_command_evidence'],'whole command DTO15typed pairs')
|
||||
require(audio.ogg_gate(audio_dir/'actual-ogg-decode-player-state.json',pid,expected)==phase['actual_ogg_evidence'],'actual Ogg evidence; hearing remains false')
|
||||
reviewed.append({'test':test,'actual_checks':parsed['checks'],'passed':True,'source_sha256':expected[test],
|
||||
'log':phase['log'],'log_sha256':phase['log_sha256'],'actual_pid':pid,'engine_ended':True,'completion_marker':parsed['marker'],
|
||||
'errors':[],'known_unicode_diagnostic_count':parsed['known_unicode_diagnostic_count'],'unrecognized_unicode_diagnostic_count':parsed['unrecognized_unicode_diagnostic_count']})
|
||||
requested=policy['fixed_generated_report_routes'].get(test,[]) if test else []
|
||||
require(set(phase['generated_reports'])==set(requested),'fixed generated report exact per-suite set')
|
||||
for n,pin in phase['generated_reports'].items():
|
||||
require(n not in previous and sha(stage/n)==pin['sha256'] and (stage/n).stat().st_size==pin['bytes']
|
||||
and pin['actual_engine_pid']==pid and pin['producer_test']==test and pin['producer_source_sha256']==expected[test]
|
||||
and pin['payload_modified'] is False,'fresh actual report/PID/source/size/SHA')
|
||||
previous[n]={'bytes':pin['bytes'],'sha256':pin['sha256']}
|
||||
require(phase['passed'] is True and phase['failed'] is False and phase['default_player_entire_profile_unchanged'] is True,'strict phase accepted')
|
||||
except BaseException as caught:
|
||||
errors.append(label+': '+type(caught).__name__+': '+str(caught))
|
||||
if test:
|
||||
if reviewed and reviewed[-1]['test']==test:reviewed.pop()
|
||||
reviewed.append({'test':test,'actual_checks':phase.get('reported_checks'),'passed':False,'errors':[errors[-1]]})
|
||||
require(runner.inventory(stage,stage=True)==dict(expected_stage,**previous),'complete stage source/UID/declared report inventory')
|
||||
context=runner.totals(rows,paths)
|
||||
require(all(ended[k]==v for k,v in context.items()),'partial actual counts/NULL metadata derived from real rows')
|
||||
require(ended['actual_captured_PIDs']==actual_pids and len(set(actual_pids))==len(actual_pids),'actual all native captured PID set')
|
||||
require(len(ended['final_owned_cleanup'])==len(actual_pids) and all(r['pid']in actual_pids and r['engine_ended']is True and not r['outer_cleanup_errors'] and not r['cleanup']['errors']for r in ended['final_owned_cleanup']),'bounded cleanup of exactly actual objects')
|
||||
require(ended['engine_ended'] is True and ended['cleanup_errors']==[] and ended['guard_errors']==[],'all actual engine ended/whole guards')
|
||||
require(ended['default_player_after']==initial['default_player_before']==runner.default_player(root),'whole default player including logs/lock exact')
|
||||
passed=not errors and ended['passed']is True and ended['error']is None and len(phases)==len(logical) and [r['test']for r in reviewed]==paths and all(r['passed']for r in reviewed)
|
||||
return {'passed':passed,'rows':reviewed,'collector_errors':errors,'actual_captured_PIDs':actual_pids,
|
||||
'actual_native_phases':len(phases),'actual_parse_checks':next((p.get('actual_parse_checks')for p in phases if p['phase']=='parse'),None),
|
||||
'actual_all_roster_checks':context['actual_all_roster_checks'],'available_marker_checks':context['available_marker_checks'],
|
||||
'strict_accepted_checks':context['strict_accepted_checks'],'remaining_unexecuted_tests':context['remaining_unexecuted_tests'],
|
||||
'missing_marker_paths':context['missing_marker_paths'],'Runtime_typed_normalizations':0,'native_Windows':'pending','formal_acceptance':False}
|
||||
@@ -0,0 +1,436 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"suite_policies": {
|
||||
"tests/ui/test_anri_recruitment.gd": {
|
||||
"source_sha256": "74cfaf73ed01769c49498e5395fda009b70f2f6667f4f902abf4697f01f87962",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": [
|
||||
"single"
|
||||
],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/ui/test_event_log_ui.gd": {
|
||||
"source_sha256": "9b1d7cb112790bb58969338ab1dc0bbe2a1c26b4307c4bfcd37491cf950c557b",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": [
|
||||
"single"
|
||||
],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/mvp/test_amelia_donations.gd": {
|
||||
"source_sha256": "1bd622a3d88374f01e3d8885a84a816572ed296928f6ce7e3f8134ab15f60793",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": [
|
||||
"single"
|
||||
],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/mvp/test_fine_cleaning_effect.gd": {
|
||||
"source_sha256": "7c7df0bac1c072ed4423140e8afe431952ec2a47f76df7dbc24cab62283cae06",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": [
|
||||
"single"
|
||||
],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/supply/test_normal_auto_purchase_chain.gd": {
|
||||
"source_sha256": "6916d644620c49a09ab110d98fe07477bb5bb9f96da3826a0be962610cc55256",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": [
|
||||
"single"
|
||||
],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/supply/test_normal_supply_chain.gd": {
|
||||
"source_sha256": "eedbb301e68285bbd6f701cf7eec15537f116944115807e0d9133a4c2970bb1d",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": [
|
||||
"single"
|
||||
],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/ui/test_approved_free_unique_arrears_ui.gd": {
|
||||
"source_sha256": "ff684e2f227896c1b5df6026f130d45f4289fc6cae40989be51ddc95b2cf135b",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": [
|
||||
"single"
|
||||
],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/ui/test_business_management.gd": {
|
||||
"source_sha256": "2c76697f9a911e9dded63ebbbc2b82ee9e616777c50a637eebd91dc4932f85a5",
|
||||
"ordinary_cleanup_prepared": false,
|
||||
"ordinary_control_labels": [],
|
||||
"old_bounded_teardown": true,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/ui/test_chapel_gameplay.gd": {
|
||||
"source_sha256": "e1996d6bd6caf46bae7becf1de2514e91a6168a686e83563b00803ac71109735",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": [
|
||||
"single"
|
||||
],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/ui/test_cleaning_gameplay.gd": {
|
||||
"source_sha256": "38593cd17829611dd34561ec46a0d746492a6cffebc35a659d49fe69de4234ac",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": [
|
||||
"single"
|
||||
],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/ui/test_dashboard_coalescing.gd": {
|
||||
"source_sha256": "2f4bd8e9d43911067a7e82864454f6779ae92acc48e0b8d4932e5d963e0430ec",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": [
|
||||
"single"
|
||||
],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/ui/test_defense_management.gd": {
|
||||
"source_sha256": "e7debf5746e1ad5976b1afb1d2471c1c2317bcc406ed2716a2368bd4e06e0235",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": [
|
||||
"single"
|
||||
],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/ui/test_dimension_migration_ui.gd": {
|
||||
"source_sha256": "0f0cec88ea8bf5fccdcd5ade02fab70b7dc71135132048cd10db17ad9d964c0d",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": [
|
||||
"single"
|
||||
],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/ui/test_elevator_management.gd": {
|
||||
"source_sha256": "dff2d3f2a1cea84224aee8f70c4ad53d731df173bc7d16d7ec012c0d16c62e11",
|
||||
"ordinary_cleanup_prepared": false,
|
||||
"ordinary_control_labels": [],
|
||||
"old_bounded_teardown": true,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/ui/test_first_operating_day.gd": {
|
||||
"source_sha256": "fcba5cee4ea7b16340871d98634cef56d80ca2197b1b069d47ae6cf287693226",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": [
|
||||
"single"
|
||||
],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/ui/test_guest_decision_log.gd": {
|
||||
"source_sha256": "8532defab87c075ad3196b44af030cdab785abed365e25d5a56d2d42f115cf71",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": [
|
||||
"single"
|
||||
],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/ui/test_guest_observation.gd": {
|
||||
"source_sha256": "8f5af3d6ef367512b34d0cf35fd8c1d2848d020419dddfa234120206dbe4f6e4",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": [
|
||||
"single"
|
||||
],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/ui/test_layer_skin.gd": {
|
||||
"source_sha256": "aab6f0561e222f88681169a82b47631320495df4c359bcf79d2fb228c60ad83b",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": [
|
||||
"front-owner",
|
||||
"back-owner"
|
||||
],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/ui/test_main_audio_feedback.gd": {
|
||||
"source_sha256": "9bc9e13ef3498941b6e8566c0b7666c42e294b63e18ec0da2cfa1cda3e4617bd",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": [
|
||||
"single"
|
||||
],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/ui/test_main_dashboard.gd": {
|
||||
"source_sha256": "fa9ee0c4b9aee00a2be6d05a9dab7c0c3e8b0beb2b944257de1dc308bfc3cc20",
|
||||
"ordinary_cleanup_prepared": false,
|
||||
"ordinary_control_labels": [],
|
||||
"old_bounded_teardown": true,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/ui/test_original_build_pointer.gd": {
|
||||
"source_sha256": "a04a79c5e766659d10c4564d1f11c6554a27cb8af71431295e10bc4967012913",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": [
|
||||
"single"
|
||||
],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/ui/test_original_facility_controls.gd": {
|
||||
"source_sha256": "294b39808d3454f96555b981c8f822f17e1248afa803f7c0da74c35799ae55a1",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": [
|
||||
"single"
|
||||
],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/ui/test_rank_pointer.gd": {
|
||||
"source_sha256": "2c51510f80fb57068c6c06aa56566936685e679720b0fd853d8f814566e730f2",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": [
|
||||
"single"
|
||||
],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/ui/test_selection_autosave.gd": {
|
||||
"source_sha256": "1d7d267ef171041f77241c4c2f1a1bb55a46253f803d5c496507e934bc8fa815",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": [
|
||||
"single"
|
||||
],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/ui/test_selection_lifecycle.gd": {
|
||||
"source_sha256": "ed68b120f422e1b37b8cf0654b6d6605458fe01c7e5d84decc3ba6f4e9366c47",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": [
|
||||
"transition",
|
||||
"finish"
|
||||
],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/ui/test_staff_management.gd": {
|
||||
"source_sha256": "7e37dc47583325a564ef48de31596adeeccfb7151ac5fce952efc6873c8ab704",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": [
|
||||
"restored-transition",
|
||||
"finish"
|
||||
],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/ui/test_staff_rescue.gd": {
|
||||
"source_sha256": "928179383bda38adf9211065b644a37bed3f8128be42b7e7a0fba9a505a40b9e",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": [
|
||||
"single"
|
||||
],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/ui/test_staff_rest.gd": {
|
||||
"source_sha256": "712f2c30ec89da20835e54f2e83b22a358cc6fc05555147ce635ec2592ef75b5",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": [
|
||||
"single"
|
||||
],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/ui/test_stair_transport_details.gd": {
|
||||
"source_sha256": "3abd3655eecbefe1b7cd49e1b16c42f8161c86f20380c298ec6367b42d31555f",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": [
|
||||
"single"
|
||||
],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/ui/test_supply_auto_purchase_ui.gd": {
|
||||
"source_sha256": "ad3788ae4b3f17918be67cf06c4162635da1ecba1ee0a0465d10acc019f3e3ae",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": [
|
||||
"single"
|
||||
],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/ui/test_supply_management.gd": {
|
||||
"source_sha256": "ee96be8b31eb71e84ab813e427a28bcfb76de4a057bd964584a6a497382ea716",
|
||||
"ordinary_cleanup_prepared": false,
|
||||
"ordinary_control_labels": [],
|
||||
"old_bounded_teardown": true,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/ui/test_ui_audio_feedback.gd": {
|
||||
"source_sha256": "62c5c8080b1a05d243977a293a23ab216ef71de38fe46ca6b31c71e0af4555fe",
|
||||
"ordinary_cleanup_prepared": false,
|
||||
"ordinary_control_labels": [],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": true
|
||||
},
|
||||
"tests/ui/test_ui_presentation_audio_contract.gd": {
|
||||
"source_sha256": "9769b89b61630ddf2ef13a927f329ddaa30e04da77987e7a868d0490244d6a25",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": [
|
||||
"before-remount",
|
||||
"final"
|
||||
],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/ui/test_upkeep_closed_ui.gd": {
|
||||
"source_sha256": "915db12e539d98bfcb2cfc3712693ca7fca3ff459aaacba419faff04ac712868",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": [
|
||||
"single"
|
||||
],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/ui/test_upkeep_inspector.gd": {
|
||||
"source_sha256": "99895a24bcddc4c2e01d8960a2fd7eba05be237a64700d78879c68c1a0660f34",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": [
|
||||
"single"
|
||||
],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/ui/test_world_tree_rank.gd": {
|
||||
"source_sha256": "8e108e3ae63d1ee3971c7b236f50a673c407835f094de0d38a736c38261767aa",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": [
|
||||
"single"
|
||||
],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/mvp/test_runtime_upkeep.gd": {
|
||||
"source_sha256": "eae48823d58d78c419d29728e33a5559fbe5131b49e33d04ca4e6fa3a721c29b",
|
||||
"ordinary_cleanup_prepared": false,
|
||||
"ordinary_control_labels": [],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/transport/test_runtime_transport_comparison.gd": {
|
||||
"source_sha256": "ba577397fa8db5a6cf92cfd398dd7a5ece49e6b59856f14772b67b51ddc0bde9",
|
||||
"ordinary_cleanup_prepared": false,
|
||||
"ordinary_control_labels": [],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/transport/test_transport_comparison.gd": {
|
||||
"source_sha256": "7e80e9ef80500278c56f0d1ada5c911f43e3c0a9bc370045fdbebd33c2f02fd5",
|
||||
"ordinary_cleanup_prepared": false,
|
||||
"ordinary_control_labels": [],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/supply/test_mimi_purchase_quote.gd": {
|
||||
"source_sha256": "01f7760cf0fed33fda6358bd407fc1441a198dce9f2713e49bfcc097ee8c8ada",
|
||||
"ordinary_cleanup_prepared": false,
|
||||
"ordinary_control_labels": [],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false,
|
||||
"standalone_ownership": {
|
||||
"Main_instantiated": false,
|
||||
"configure_audio_called": false,
|
||||
"UIAudio_owner_registered": false,
|
||||
"actual_parent": "SceneTree.root",
|
||||
"owned_supply_panel": "configure(runtime) then add_child; queue_free + await process_frame + is_instance_valid rejection",
|
||||
"autoload_Runtime_Economy_lifetime": "native strict diagnostics and whole profile capture remain mandatory; leak outcome unmeasured",
|
||||
"normal_human_viewport_or_OS": false
|
||||
},
|
||||
"source_only_ownership_review": true
|
||||
},
|
||||
"tests/transport/test_runtime_transport.gd": {
|
||||
"source_sha256": "ec6f8fa8e8f2a76a6f6214b4ee84861aba1bb13845a342c5126905b01f428a15",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": [
|
||||
"single"
|
||||
],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/ui/test_actor_selection.gd": {
|
||||
"source_sha256": "f37a0f4805adce4b538fccde1404c1a417f20639fddf36947f076d88409ba0a3",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": [
|
||||
"before-remount",
|
||||
"final"
|
||||
],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/mvp/test_pending_service_pricing_report.gd": {
|
||||
"source_sha256": "50095c41fe76bac7bd9346ce97651d40610fa69b48c3b24f7acc5b371578e753",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": [
|
||||
"single"
|
||||
],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
}
|
||||
},
|
||||
"outside_routes": {
|
||||
"tests/supply/test_normal_auto_purchase_chain.gd": {
|
||||
"env": "THE_TREE_SUPPLY_EVIDENCE_DIR",
|
||||
"filename": "normal-play-report.json",
|
||||
"stdout_prefix": "NORMAL_AUTO_PURCHASE_REPORT ",
|
||||
"stdout_projection": "subset",
|
||||
"report_owner_source_sha256": "6916d644620c49a09ab110d98fe07477bb5bb9f96da3826a0be962610cc55256"
|
||||
},
|
||||
"tests/supply/test_normal_supply_chain.gd": {
|
||||
"env": "THE_TREE_SUPPLY_EVIDENCE_DIR",
|
||||
"filename": "normal-play-report.json",
|
||||
"stdout_prefix": "NORMAL_SUPPLY_REPORT ",
|
||||
"stdout_projection": "subset",
|
||||
"report_owner_source_sha256": "eedbb301e68285bbd6f701cf7eec15537f116944115807e0d9133a4c2970bb1d"
|
||||
},
|
||||
"tests/ui/test_first_operating_day.gd": {
|
||||
"env": "THE_TREE_FIRST_OPERATING_EVIDENCE_DIR",
|
||||
"filename": "operating-report.json",
|
||||
"stdout_prefix": "FIRST_OPERATING_DAY_REPORT ",
|
||||
"stdout_projection": "full",
|
||||
"report_owner_source_sha256": "fcba5cee4ea7b16340871d98634cef56d80ca2197b1b069d47ae6cf287693226"
|
||||
},
|
||||
"tests/ui/test_selection_autosave.gd": {
|
||||
"env": "THE_TREE_FIRST_OPERATING_EVIDENCE_DIR",
|
||||
"filename": "operating-report.json",
|
||||
"stdout_prefix": "FIRST_OPERATING_DAY_REPORT ",
|
||||
"stdout_projection": "full",
|
||||
"inherited_source": "tests/ui/test_first_operating_day.gd",
|
||||
"report_owner_source_sha256": "fcba5cee4ea7b16340871d98634cef56d80ca2197b1b069d47ae6cf287693226"
|
||||
}
|
||||
},
|
||||
"fixed_generated_report_routes": {
|
||||
"tests/mvp/test_runtime_upkeep.gd": [
|
||||
"artifacts/upkeep-2026-09-29/preclose-snapshot.txt",
|
||||
"artifacts/upkeep-2026-09-29/closed-snapshot.txt"
|
||||
],
|
||||
"tests/transport/test_runtime_transport_comparison.gd": [
|
||||
"artifacts/transport/runtime-comparison.json",
|
||||
"artifacts/transport/runtime-comparison-1-trace.json",
|
||||
"artifacts/transport/runtime-comparison-2-trace.json"
|
||||
],
|
||||
"tests/transport/test_transport_comparison.gd": [
|
||||
"artifacts/transport/path-comparison.json",
|
||||
"artifacts/transport/comparison-demand.json"
|
||||
]
|
||||
},
|
||||
"scope": "observed source/proof/IO contracts; no new gameplay assertions"
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"test": "tests/performance/test_digest_performance.gd",
|
||||
"source_sha256": "363772fc43975f12c9e2056e2b01beb4cd8b2d7544db7528f80c6bd127710c1f",
|
||||
"producer_function": "_test_text_chunk_boundaries",
|
||||
"producer_source_lines": [
|
||||
248,
|
||||
249,
|
||||
250,
|
||||
251,
|
||||
252,
|
||||
253,
|
||||
254,
|
||||
255,
|
||||
256
|
||||
],
|
||||
"producer_exact_source": "\n\tfor count: int in [63,64,65,129]:\n\t\tvar strings: Array[String] = []\n\t\tvar names: Array[StringName] = []\n\t\tfor index in count:\n\t\t\tvar text := \"field_%d:\" % index + String.chr(0x1f333) + String.chr(0) + \"end\"\n\t\t\tstrings.append(text)\n\t\t\tnames.append(StringName(text))\n\t\t_expect_bytes(strings, \"UTF8 and NUL string chunk boundary %d\" % count)\n\t\t_expect_bytes(names, \"StringName chunk boundary %d\" % count)\n",
|
||||
"counts_from_unchanged_source": [
|
||||
63,
|
||||
64,
|
||||
65,
|
||||
129
|
||||
],
|
||||
"total_diagnostics": 321,
|
||||
"exact_line": "Unicode parsing error, some characters were replaced with � (U+FFFD): Unexpected NUL character",
|
||||
"historical_full165_raw": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/kitchen-upkeep-full165-preparation-r04/full-execution/suites/tests__performance__test_digest_performance.gd.log",
|
||||
"historical_raw_sha256": "e783664b8a745e42ca44deb4368ce61e2c3abf6238baa60860ab0ac5995a458c",
|
||||
"historical_actual_PID": 1698,
|
||||
"historical_321_exact_lines_verified": true,
|
||||
"historical_Root_review": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/kitchen-upkeep-full165-r04-actual-root-review-r01/review.json",
|
||||
"historical_Root_review_sha256": "5d4e1e59ddc00a12f9d379d2afdf4b0a576303a80f152ee51bd516fa35c42c4b",
|
||||
"allowed_stream_distribution": {
|
||||
"stdout": 0,
|
||||
"stderr": 321,
|
||||
"native_file": 321
|
||||
},
|
||||
"native_file_distribution_basis": "Primary ed1daf0bf full function bodies found by exact signature: String.print_unicode_error -> print_error -> OS.printerr -> CompositeLogger.logv(err=true) -> StdLogger stderr and RotatedFileLogger file. Raw-byte physical source lines, not web-rendered line numbers. Future actual321/321 agreement remains strict, inferred routing is not acceptance.",
|
||||
"allowed_immutable_e31_error_lines": [
|
||||
"Unrecognized Unicode diagnostic or source pin"
|
||||
],
|
||||
"allowed_error_predicate": "Only exact original producer/source and exact whole321 transport agree; e31 raw result/error_lines preserved without mutation",
|
||||
"all_other_Unicode_or_ERROR_or_ObjectDB_allowed": false,
|
||||
"engine_runs": 0,
|
||||
"actual_new_transport_verified": false,
|
||||
"official_logger_source_record": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/ui-audio-path-combined-full-regression-prep-r01/official-logger-source-r02/source-record.json",
|
||||
"sha256": "6e169381373181b8556ef6c8f956769792bf5748aa9d03e999e7903494c0d394"
|
||||
}
|
||||
}
|
||||
+304
-46
@@ -1,53 +1,311 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run every source test, retain logs and continue past failures.
|
||||
"""Exact standard suites in one private source copy, with owned native evidence.
|
||||
|
||||
The exported-package test needs a built PCK and runs via make test-exported-opening.
|
||||
Completion schemas and test bodies are unchanged. Import and every script start
|
||||
in a fresh private startup profile before Godot creates logs/recovery locks;
|
||||
the unchanged Guard then selects a separate fresh runtime profile.
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import argparse, hashlib, importlib.util, json, os, re, shutil, subprocess, sys, time, uuid
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import time
|
||||
sys.dont_write_bytecode = True
|
||||
HERE = Path(__file__).resolve().parent
|
||||
SUPPORT = HERE / 'regression'
|
||||
MIN_SPACE = 300 * 1024 * 1024
|
||||
|
||||
def module(name, path):
|
||||
spec = importlib.util.spec_from_file_location(name, path)
|
||||
value = importlib.util.module_from_spec(spec); spec.loader.exec_module(value); return value
|
||||
def sha(path): return hashlib.sha256(Path(path).read_bytes()).hexdigest()
|
||||
def read(path): return json.loads(Path(path).read_bytes())
|
||||
def require(ok, reason):
|
||||
if not ok: raise AssertionError(reason)
|
||||
def write(path, value):
|
||||
with Path(path).open('x', encoding='utf-8') as stream:
|
||||
stream.write(json.dumps(value, ensure_ascii=False, indent=2) + '\n')
|
||||
def now(): return datetime.now(timezone.utc).isoformat()
|
||||
def roster(root):
|
||||
return ['tests/run_tests.gd'] + sorted(str(p.relative_to(root)) for p in (root/'tests').rglob('test*.gd')
|
||||
if 'export' not in p.relative_to(root).parts and str(p.relative_to(root)) != 'tests/s0/test_kernel_and_save.gd')
|
||||
def inventory(base, stage=False):
|
||||
base = Path(base); result = {}
|
||||
if not base.exists(): return result
|
||||
require(base.is_dir() and not base.is_symlink(), 'ordinary inventory directory')
|
||||
for p in sorted(base.rglob('*')):
|
||||
rel = p.relative_to(base)
|
||||
if stage and '.godot' in rel.parts: continue
|
||||
require(not p.is_symlink(), 'symlink is not an ordinary source/proof/profile')
|
||||
if p.is_file(): result[rel.as_posix()] = {'bytes': p.stat().st_size, 'sha256': sha(p)}
|
||||
return result
|
||||
def source_refs(root):
|
||||
paths = [p for folder in ('autoload','scripts','scenes','data','tests','assets','tools')
|
||||
for p in (root/folder).rglob('*') if p.is_file() and p.name != '.DS_Store' and '__pycache__' not in p.parts]
|
||||
paths += [root/p for p in ('project.godot','export_presets.cfg','Makefile','AGENTS.md')]
|
||||
require(all(not p.is_symlink() for p in paths), 'source aliases refused')
|
||||
return {p.relative_to(root).as_posix(): {'path':str(p),'bytes':p.stat().st_size,'sha256':sha(p)} for p in sorted(paths)}
|
||||
def profile_path(name):
|
||||
require(re.fullmatch(r'TheTree-PCK-tests-[0-9a-f]{32}-[a-z0-9-]+', name) is not None, 'owned profile nonce')
|
||||
if sys.platform == 'darwin': return Path.home()/'Library/Application Support'/name
|
||||
if sys.platform.startswith('linux'): return Path(os.environ.get('XDG_DATA_HOME', str(Path.home()/'.local/share')))/name
|
||||
raise RuntimeError('profile platform requires a reviewed native runner; no Windows acceptance inference')
|
||||
def default_player(root):
|
||||
text = (root/'project.godot').read_text()
|
||||
require('config/name="The Tree"' in text and 'config/use_custom_user_dir=' not in text, 'canonical default profile source')
|
||||
if sys.platform == 'darwin': path = Path.home()/'Library/Application Support/Godot/app_userdata/The Tree'
|
||||
elif sys.platform.startswith('linux'): path = Path(os.environ.get('XDG_DATA_HOME', str(Path.home()/'.local/share')))/'godot/app_userdata/The Tree'
|
||||
else: raise RuntimeError('unreviewed default-profile platform')
|
||||
return {'path':str(path),'existed':path.exists(),'files':inventory(path),'logs_and_locks_included':True}
|
||||
def private_project(public_bytes, name):
|
||||
text = public_bytes.decode('utf-8')
|
||||
require(text.count('[application]\n\n') == 1 and 'config/use_custom_user_dir=' not in text
|
||||
and 'config/custom_user_dir_name=' not in text, 'canonical project has no private execution settings')
|
||||
addition = 'config/use_custom_user_dir=true\nconfig/custom_user_dir_name=' + json.dumps(name) + '\n'
|
||||
altered = text.replace('[application]\n\n', '[application]\n\n' + addition, 1).encode()
|
||||
require(altered.decode().replace(addition, '', 1).encode() == public_bytes, 'two private fields reverse to whole public bytes')
|
||||
return altered
|
||||
def fresh_environment(base): return {k:v for k,v in base.items() if not k.startswith('THE_TREE_')}
|
||||
def phase_policy(test, source, policies):
|
||||
value = policies.get(test, {'ordinary_cleanup_prepared':False,'ordinary_control_labels':[],
|
||||
'old_bounded_teardown':False,'new_audio_QA':False})
|
||||
if test in policies: require(value['source_sha256'] == sha(source), 'suite proof/IO policy source SHA changed')
|
||||
return dict(value, path=test, source_sha256=sha(source), candidate_source_sha256=sha(source))
|
||||
def dependency_paths(test, refs):
|
||||
"""Reviewed literal GDScript dependencies, including relative inherited QA."""
|
||||
pending=[test];seen=set()
|
||||
while pending:
|
||||
name=pending.pop()
|
||||
if name in seen:continue
|
||||
seen.add(name)
|
||||
text=Path(refs[name]['path']).read_text()
|
||||
for quoted in re.findall(r'[\"\']([^\"\']+\.gd)[\"\']',text):
|
||||
target=quoted[6:] if quoted.startswith('res://') else str(Path(name).parent/quoted)
|
||||
if target in refs and target not in seen:pending.append(target)
|
||||
return seen
|
||||
def policy_coverage(paths, refs, policy):
|
||||
for test in paths:
|
||||
dependencies=dependency_paths(test,refs);p=phase_policy(test,Path(refs[test]['path']),policy['suite_policies'])
|
||||
if 'tests/helpers/ui_audio_fixture_cleanup.gd' in dependencies:
|
||||
require(p['ordinary_cleanup_prepared'] and p['ordinary_control_labels'], 'unknown ordinary helper user requires exact reviewed policy '+test)
|
||||
if 'tests/helpers/ui_audio_teardown.gd' in dependencies:
|
||||
require(p['ordinary_cleanup_prepared'] or p['old_bounded_teardown'] or test=='tests/ui/test_ui_audio_feedback.gd', 'unknown bounded teardown user requires reviewed proof policy '+test)
|
||||
def copy_source(source,target):
|
||||
if sys.platform=='darwin':subprocess.run(['/bin/cp','-c',str(source),str(target)],check=True)
|
||||
else:shutil.copyfile(source,target)
|
||||
require(target.stat().st_ino!=Path(source).stat().st_ino or target.stat().st_dev!=Path(source).stat().st_dev,
|
||||
'private source is a separate inode; hardlinks refused')
|
||||
def profile_gate(path, report, name):
|
||||
value = read(report)
|
||||
require(value['verified'] is True and value['conditions'] == [True]*5 and value['conditions_checked'] == 5
|
||||
and value['fresh_before_parent_initialize'] is True and value['profile_writes_before_guard'] == 0
|
||||
and value['actual_user_data_dir'] == str(path) == value['expected_user_data_dir']
|
||||
and value['globalized_probe'] == str(path/'guard-probe') and value['custom_user_dir_name'] == name
|
||||
and value['project_settings_persisted'] is False, 'all five unchanged runtime Guard conditions')
|
||||
return {'report':value,'report_sha256':sha(report),'whole_files':inventory(path)}
|
||||
def process_gate(row, allowed):
|
||||
return (row.get('exit_code') == 0 and row.get('helper_exit_code') == 0 and row.get('timed_out') is False
|
||||
and row.get('error_lines') == allowed and row.get('owned_popen_ended') is True
|
||||
and row.get('old_helper_engine_ended') is True and row.get('start_receipt_written') is True
|
||||
and row.get('end_receipt_written') is True and row.get('lifecycle_error') is None
|
||||
and row.get('external_process_or_group_signals') == 0
|
||||
and row.get('adapter_owned_cleanup',{}).get('engine_ended') is True)
|
||||
def routed_report(directory, route, test, pid, raw, stage, source_sha):
|
||||
file = directory/route['filename']
|
||||
require(set(directory.iterdir()) == {file} and file.is_file() and not file.is_symlink() and file.stat().st_size>0,
|
||||
'exclusive fresh outside report file set')
|
||||
value = read(file); matches = [l[len(route['stdout_prefix']):] for l in raw.splitlines() if l.startswith(route['stdout_prefix'])]
|
||||
require(isinstance(value,dict) and value and len(matches)==1, 'whole report and unique actual stdout JSON')
|
||||
printed = json.loads(matches[0]); require(isinstance(printed,dict) and printed, 'nonempty printed report')
|
||||
require(value==printed if route['stdout_projection']=='full' else all(value.get(k)==v for k,v in printed.items()),
|
||||
'literal original report payload agreement')
|
||||
owner = route.get('inherited_source',test)
|
||||
require(sha(stage/test)==source_sha and sha(stage/owner)==route['report_owner_source_sha256'], 'actual report source provenance')
|
||||
return {'verified':True,'path':str(file),'bytes':file.stat().st_size,'sha256':sha(file),
|
||||
'actual_engine_pid':pid,'producer_test':test,'producer_source_sha256':source_sha,'report_owner_source':owner,
|
||||
'report_owner_source_sha256':sha(stage/owner),'payload_field_deletions':0,'whole_JSON':value}
|
||||
def declared_reports(stage, expected, previous, requested):
|
||||
actual = inventory(stage,stage=True)
|
||||
require(not set(requested)&(set(expected)|set(previous)), 'new reports cannot overwrite source/previous reports')
|
||||
require(set(actual)==set(expected)|set(previous)|set(requested), 'complete source/UID/generated report set')
|
||||
require({n:actual[n] for n in expected}==expected and {n:actual[n] for n in previous}==previous, 'all source and earlier report bytes exact')
|
||||
added = {n:actual[n] for n in requested}
|
||||
for name,pin in added.items():
|
||||
require(pin['bytes']>0, 'nonempty actual generated report')
|
||||
if name.endswith('.json'): read(stage/name)
|
||||
else: (stage/name).read_text()
|
||||
return added
|
||||
def totals(rows, paths):
|
||||
names=[r['test'] for r in rows]; missing=[r['test'] for r in rows if r.get('reported_checks') is None]
|
||||
remaining=[n for n in paths if n not in names]; available=sum(r.get('reported_checks') or 0 for r in rows)
|
||||
return {'actual_suites':len(rows),'missing_marker_paths':missing,'remaining_unexecuted_tests':remaining,
|
||||
'available_marker_checks':available,'actual_all_roster_checks':available if not missing and names==paths else None,
|
||||
'strict_accepted_checks':sum(r['reported_checks'] for r in rows if not r['failed'] and r.get('reported_checks') is not None)}
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--output', type=Path, default=Path('artifacts/all-tests'))
|
||||
parser.add_argument('--godot', default='godot')
|
||||
parser.add_argument('--timeout', type=int, default=1200)
|
||||
args = parser.parse_args()
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
output = args.output.resolve()
|
||||
output.mkdir(parents=True, exist_ok=True)
|
||||
paths = [root / 'tests/run_tests.gd'] + sorted((root / 'tests').rglob('test*.gd'))
|
||||
results = []
|
||||
for path in paths:
|
||||
relative = path.relative_to(root)
|
||||
if 'export' in relative.parts or relative.as_posix() == 'tests/s0/test_kernel_and_save.gd':
|
||||
# The RefCounted kernel module is already invoked by run_tests.gd.
|
||||
continue
|
||||
start = time.monotonic()
|
||||
name = str(relative).replace('/', '__') + '.log'
|
||||
# Stream to disk so long-running scenarios expose progress immediately.
|
||||
with (output / name).open('w') as log:
|
||||
try:
|
||||
run = subprocess.run(
|
||||
[args.godot, '--headless', '--path', str(root), '--script', 'res://' + str(relative)],
|
||||
cwd=root, stdout=log, stderr=subprocess.STDOUT, timeout=args.timeout,
|
||||
)
|
||||
code = run.returncode
|
||||
except subprocess.TimeoutExpired:
|
||||
code = 124
|
||||
content = (output / name).read_text()
|
||||
failed = code != 0 or 'SCRIPT ERROR:' in content or '\nERROR:' in content
|
||||
results.append(dict(test=str(relative), exit_code=code, failed=failed,
|
||||
seconds=round(time.monotonic() - start, 2), log=name))
|
||||
(output / 'all-tests.json').write_text(json.dumps(results, indent=2) + '\n')
|
||||
print(f'{"FAIL" if failed else "PASS"} {relative}', flush=True)
|
||||
failed = sum(result['failed'] for result in results)
|
||||
print(f'{len(results)} tests, {failed} failures; logs: {output}')
|
||||
return 1 if failed else 0
|
||||
def execute_phase(engine, stage, out, phase, test, selected, expected, previous, policy, contracts, paths,
|
||||
public_project, protected_player, root, outer, markers, diagnostics, owners, audio):
|
||||
require(not outer.native.helper['live_engines'](), 'one owned native engine at a time')
|
||||
require(shutil.disk_usage(out).free>=MIN_SPACE, 'fresh phase disk admission')
|
||||
require(source_refs(root)==selected['root_before'] and default_player(root)==protected_player, 'entire current source/default profile unchanged before phase')
|
||||
require(inventory(stage,stage=True)==dict(expected,**previous), 'whole stage source/UID/report inventory before phase')
|
||||
env=fresh_environment(os.environ); startup_name='TheTree-PCK-tests-'+uuid.uuid4().hex+'-standard-startup-'+phase
|
||||
startup=profile_path(startup_name); require(not startup.exists(), 'fresh startup profile before native engine')
|
||||
write(out/(phase+'-startup-profile-before.json'), {'path':str(startup),'existed':False,'files':{},'before_native_spawn':True})
|
||||
private=private_project(public_project,startup_name); (stage/'project.godot').write_bytes(private)
|
||||
folder=out/phase; proof=None; evidence=None; runtime=None; runtime_report=None; script=None
|
||||
row={'phase':phase,'test':test,'passed':False,'failed':True,'validation_error':None,'profile_startup_name':startup_name,
|
||||
'private_project_sha256':hashlib.sha256(private).hexdigest(),'private_project_fields':2,'source_sha256':sha(stage/test) if test else None}
|
||||
try:
|
||||
if phase=='import': argv=[engine,'--headless','--path',str(stage),'--editor','--import','--quit','--verbose']
|
||||
else:
|
||||
name='TheTree-PCK-tests-'+uuid.uuid4().hex+'-standard-runtime-'+phase; runtime=profile_path(name)
|
||||
require(not runtime.exists(), 'fresh runtime profile before Guard')
|
||||
runtime_report=out/(phase+'-profile.json')
|
||||
env.update(THE_TREE_PACK_PROFILE_NAME=name,THE_TREE_PACK_PROFILE_EXPECTED=str(runtime),
|
||||
THE_TREE_PACK_PROFILE_REPORT=str(runtime_report),THE_TREE_FULL_PARSE_PATHS=str(out/'parse-paths.json'))
|
||||
if phase=='parse': script=out/'bootstrap/parse_full_dependencies.gd'
|
||||
else:
|
||||
script=out/'starters'/(phase+'.gd')
|
||||
script.write_text('extends '+json.dumps('res://'+test)+'\nconst Guard := preload('+json.dumps(str(out/'bootstrap/profile_guard.gd'))+')\nfunc _initialize() -> void:\n\tif not Guard.activate():\n\t\tquit(97)\n\t\treturn\n\tsuper._initialize()\n')
|
||||
p=phase_policy(test,stage/test,policy['suite_policies']); row['policy']=p
|
||||
route=policy['outside_routes'].get(test)
|
||||
if route:
|
||||
evidence=out/(phase+'-evidence'); evidence.mkdir(); env[route['env']]=str(evidence)
|
||||
if p['ordinary_cleanup_prepared'] or p['old_bounded_teardown'] or test=='tests/ui/test_ui_audio_feedback.gd':
|
||||
proof=out/(phase+'-teardown'); proof.mkdir()
|
||||
if p['ordinary_cleanup_prepared']:
|
||||
(proof/'owned-audio').mkdir(); env.update(THE_TREE_AUDIO_TEARDOWN_PROOF_DIR=str(proof/'owned-audio'),THE_TREE_AUDIO_FIXTURE_CONTROL_PROOF=str(proof/'control.json'))
|
||||
else: env['THE_TREE_AUDIO_TEARDOWN_PROOF_DIR']=str(proof)
|
||||
if test=='tests/ui/test_ui_audio_feedback.gd':
|
||||
audio_dir=out/(phase+'-audio-proof'); audio_dir.mkdir()
|
||||
env.update(THE_TREE_AUDIO_PROOF_REPORT=str(audio_dir/'actual-ogg-decode-player-state.json'),
|
||||
THE_TREE_AUDIO_COMMAND_PROOF_REPORT=str(audio_dir/'actual-command-result-buffer-proof.json'))
|
||||
argv=[engine,'--headless','--path',str(stage),'--script',str(script),'--verbose']
|
||||
logfile=folder/'engine-native.log'; require(logfile.is_absolute() and not logfile.exists(), 'fresh absolute native log')
|
||||
argv+=['--log-file',str(logfile)]; row.update(command=argv,logical_script='res://'+test if test else None,
|
||||
starter_sha256=sha(script) if script else None,controlled_environment={k:v for k,v in env.items() if k.startswith('THE_TREE_')})
|
||||
result,capture=outer.run_phase(argv,folder,180 if phase=='import' else selected['timeout'],env,
|
||||
now_fn=now,dump_fn=write,sha_fn=sha,live_engines_fn=outer.native.helper['live_engines'])
|
||||
row.update(native_process=result,outer_actual_capture=capture,actual_pid=result.get('pid') if result else None,
|
||||
engine_ended=capture['all_captured_ended'],exit_code=result.get('exit_code') if result else None)
|
||||
stdout=(folder/'stdout.log').read_text(); stderr=(folder/'stderr.log').read_text(); raw=stdout+stderr
|
||||
raw_file=out/(test.replace('/','__')+'.log') if test else folder/'combined-raw.log'; raw_file.write_text(raw)
|
||||
row.update(log=str(raw_file.relative_to(out)),log_sha256=sha(raw_file),raw_native_log={'path':str(logfile),'bytes':logfile.stat().st_size,'sha256':sha(logfile)})
|
||||
if test:
|
||||
parsed=markers.parse_raw(test,raw,contracts,row['source_sha256']); row.update(completion=parsed,
|
||||
reported_checks=parsed['checks'],reported_failures=parsed['failures'],completion_marker=parsed['marker'],collector_errors=parsed['errors'])
|
||||
review=diagnostics.review_streams(outer.native,{'stdout':stdout,'stderr':stderr,'native_file':logfile.read_text()},test,stage/test if test else None)
|
||||
row['finite_original_diagnostic_review']=review
|
||||
require(result and diagnostics.diagnostic_result_gate(result,review) and process_gate(result,review['allowed_e31_error_lines']) and capture['passed'], 'strict native/ownership/logger/finite diagnostics')
|
||||
if script: require(sha(script)==row['starter_sha256'], 'actual starter source remains exact')
|
||||
if runtime: row['actual_private_profile']=profile_gate(runtime,runtime_report,name)
|
||||
if phase=='parse':
|
||||
matches=re.findall(r'(?m)^FULL_DEPENDENCY_PARSE checks=([0-9]+) failures=([0-9]+)\n',raw)
|
||||
require(matches==[(str(len(paths)),'0')], 'unique actual all normal-context dependencies parsed')
|
||||
row['actual_parse_checks']=int(matches[0][0])
|
||||
if test:
|
||||
require(parsed['passed'] and sha(stage/test)==row['source_sha256'], 'exact original suite source/marker schema')
|
||||
if evidence: row['outside_report']=routed_report(evidence,route,test,row['actual_pid'],raw,stage,row['source_sha256'])
|
||||
if p['ordinary_cleanup_prepared']: row['ordinary_owner_pairs']=owners.ordinary_gate(proof,row['actual_pid'],out,p['ordinary_control_labels'])
|
||||
if p['old_bounded_teardown'] or test=='tests/ui/test_ui_audio_feedback.gd': row['old_bounded_proof']=audio.teardown_gate(proof,row['actual_pid'],out)
|
||||
if test=='tests/ui/test_ui_audio_feedback.gd':
|
||||
row['actual_command_evidence']=audio.command_gate(audio_dir/'actual-command-result-buffer-proof.json',audio_dir,row['actual_pid'])
|
||||
row['actual_ogg_evidence']=audio.ogg_gate(audio_dir/'actual-ogg-decode-player-state.json',row['actual_pid'],selected['map'])
|
||||
row['passed']=True; row['failed']=False
|
||||
except BaseException as caught: row['validation_error']=type(caught).__name__+': '+str(caught)
|
||||
finally:
|
||||
# run_phase owns cleanup before any receipts/guards. No discovered PID signals.
|
||||
(stage/'project.godot').write_bytes(public_project)
|
||||
row['whole_startup_profile_after']={'path':str(startup),'files':inventory(startup),'fresh_before_engine':True}
|
||||
if runtime is not None: row['whole_runtime_profile_after']={'path':str(runtime),'files':inventory(runtime)}
|
||||
try:
|
||||
requested=policy['fixed_generated_report_routes'].get(test,[]) if test else []
|
||||
generated=declared_reports(stage,expected,previous,requested); previous.update(generated)
|
||||
for name in generated:
|
||||
if name.endswith('-comparison.json'):
|
||||
value=read(stage/name)
|
||||
require(type(value.get('checks')) is int and value['checks']==row['reported_checks'] and value.get('failures')==0,
|
||||
'actual generated JSON report/marker agreement')
|
||||
row['generated_reports']={n:dict(pin,actual_engine_pid=row.get('actual_pid'),producer_test=test,
|
||||
producer_source_sha256=row['source_sha256'],payload_modified=False)for n,pin in generated.items()}
|
||||
row['source_sha256_end']=sha(stage/test) if test else None
|
||||
row['default_player_entire_profile_unchanged']=default_player(root)==protected_player
|
||||
require(row['default_player_entire_profile_unchanged'] and source_refs(root)==selected['root_before'], 'entire protected source/default player including logs/locks unchanged')
|
||||
except BaseException as caught:
|
||||
row['passed']=False;row['failed']=True;row['guard_error']=type(caught).__name__+': '+str(caught)
|
||||
write(out/(phase+'-receipt.json'),row)
|
||||
return row
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
def main(argv=None):
|
||||
parser=argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--project-root',type=Path,default=HERE.parent); parser.add_argument('--output',type=Path)
|
||||
parser.add_argument('--godot',default='godot');parser.add_argument('--timeout',type=int,default=1200)
|
||||
parser.add_argument('--marker-contracts',type=Path);parser.add_argument('--source-input-refs',type=Path)
|
||||
parser.add_argument('--prepare-only',action='store_true');args=parser.parse_args(argv)
|
||||
root=args.project_root.resolve();out=(args.output or root/'artifacts'/('all-tests-'+uuid.uuid4().hex)).resolve();require(args.timeout>0,'positive timeout')
|
||||
root_before=source_refs(root); refs=read(args.source_input_refs) if args.source_input_refs else root_before
|
||||
require(set(refs)>=set(root_before), 'prospective source must retain every canonical source key')
|
||||
for n,pin in refs.items():
|
||||
path=Path(pin['path']);require(not path.is_symlink() and path.is_file() and path.stat().st_size==pin['bytes'] and sha(path)==pin['sha256'], 'exact selected source input '+n)
|
||||
require(refs['tools/run_all_tests.py']['sha256']==sha(Path(__file__)), 'actual public runner selected source exact')
|
||||
for file in SUPPORT.iterdir():
|
||||
if file.is_file(): require(refs['tools/regression/'+file.name]['sha256']==sha(file), 'actual support selected source exact '+file.name)
|
||||
paths=['tests/run_tests.gd']+sorted(n for n in refs if n.startswith('tests/') and n.endswith('.gd') and Path(n).name.startswith('test') and 'export'not in Path(n).parts and n!='tests/s0/test_kernel_and_save.gd')
|
||||
markers=module('standard_exact_markers',HERE/'strict_test_markers.py');contract_path=(args.marker_contracts or root/'docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json').resolve()
|
||||
contracts=markers.load_contracts(contract_path);require(len(set(paths))==len(paths) and all(n in contracts for n in paths),'unique reviewed roster/schema')
|
||||
policy=read(SUPPORT/'suite-policy.json')
|
||||
for test in set(policy['suite_policies'])&set(paths): phase_policy(test,Path(refs[test]['path']),policy['suite_policies'])
|
||||
policy_coverage(paths,refs,policy)
|
||||
parse_paths=['res://'+n for n in sorted(refs) if n.endswith('.gd')]
|
||||
if args.prepare_only:
|
||||
print(json.dumps({'source_files':len(refs),'suites':len(paths),'parse_paths':len(parse_paths),'future_native_phases':len(paths)+2,'actual_checks':None,'engines':0,'stages':0}));return 0
|
||||
outer=module('standard_exact_owned_outer',SUPPORT/'owned_outer.py');require(not outer.native.helper['live_engines'](),'another Godot owns CPU')
|
||||
engine=shutil.which(args.godot) if not Path(args.godot).is_absolute() else args.godot;require(engine,'native launcher exists')
|
||||
engine=str(Path(engine).resolve());require(shutil.disk_usage(out.parent).free>=MIN_SPACE,'fresh run disk admission')
|
||||
out.mkdir(parents=True,exist_ok=False);stage=out/'stage';stage.mkdir();(out/'starters').mkdir()
|
||||
(out/'bootstrap').mkdir()
|
||||
for name,pin in read(SUPPORT/'bootstrap-scripts.json').items():
|
||||
data=pin['text'].encode('utf-8');require(hashlib.sha256(data).hexdigest()==pin['sha256'], 'unchanged external bootstrap bytes')
|
||||
(out/'bootstrap'/name).write_bytes(data)
|
||||
protected=default_player(root);expected={n:{'bytes':p['bytes'],'sha256':p['sha256']}for n,p in refs.items()}
|
||||
selected={'root_before':root_before,'map':{n:p['sha256']for n,p in refs.items()},'timeout':args.timeout}
|
||||
for n,pin in refs.items():
|
||||
target=stage/n;target.parent.mkdir(parents=True,exist_ok=True)
|
||||
copy_source(Path(pin['path']),target)
|
||||
require(inventory(stage,stage=True)==expected,'one complete private CoW source copy')
|
||||
public=(stage/'project.godot').read_bytes();write(out/'parse-paths.json',parse_paths)
|
||||
run_id=uuid.uuid4().hex;input_receipt={'protocol':'guarded-standard-v1','run_id':run_id,'owner_runner_pid':os.getpid(),
|
||||
'project_root':str(root),'stage':str(stage),'roster':paths,'selected_source_map':selected['map'],
|
||||
'marker_contract_sha256':sha(contract_path),'runner_sha256':sha(Path(__file__)),'marker_module_sha256':sha(HERE/'strict_test_markers.py'),
|
||||
'engine':engine,'engine_sha256':sha(engine),'default_player_before':protected,'performance_is_not_maximum_scene_acceptance':True}
|
||||
write(out/'runner-input.json',input_receipt);write(out/'source-start.json',root_before)
|
||||
phases=[];rows=[];previous={};error=None;cleanup=[];cleanup_errors=[]
|
||||
diagnostics=module('standard_finite_diagnostics',SUPPORT/'diagnostic_gate.py')
|
||||
owners=module('standard_ordinary_proof_gate',SUPPORT/'ordinary_proof_gate.py');audio=module('standard_audio_proof_gate',SUPPORT/'audio_evidence_gate.py')
|
||||
try:
|
||||
for phase,test in [('import',None),('parse',None)]+[(str(i).zfill(3),n)for i,n in enumerate(paths)]:
|
||||
row=execute_phase(engine,stage,out,phase,test,selected,expected,previous,policy,contracts,parse_paths,
|
||||
public,protected,root,outer,markers,diagnostics,owners,audio);phases.append(row)
|
||||
if test:
|
||||
row['sequence']=len(rows);row['godot']=engine;rows.append(row)
|
||||
(out/'all-tests.json').write_text(json.dumps(rows,ensure_ascii=False,indent=2)+'\n')
|
||||
print(('PASS ' if row['passed'] else 'FAIL ')+test,flush=True)
|
||||
require(row['passed'],'first strict failed phase stops without retry: '+phase)
|
||||
except BaseException as caught: error=type(caught).__name__+': '+str(caught)
|
||||
finally:
|
||||
# Sweep actual factory objects before all fragile final receipt/guard writes.
|
||||
cleanup,cleanup_errors=outer.finish_captured(outer.OWNED)
|
||||
context=totals(rows,paths);guard_errors=[]
|
||||
try:
|
||||
require(source_refs(root)==root_before,'full canonical source unchanged at end')
|
||||
require(default_player(root)==protected,'full default player unchanged at end')
|
||||
require(inventory(stage,stage=True)==dict(expected,**previous),'whole stage/UID/report set at end')
|
||||
except BaseException as caught:guard_errors.append(type(caught).__name__+': '+str(caught))
|
||||
ended=bool(outer.OWNED) and all(r['engine_ended']is True for r in cleanup)
|
||||
passed=error is None and not cleanup_errors and not guard_errors and ended and len(phases)==len(paths)+2 and all(r['passed']for r in phases)
|
||||
write(out/'runner-ended.json',dict(context,protocol='guarded-standard-v1',run_id=run_id,owner_runner_pid=os.getpid(),
|
||||
passed=passed,engine_ended=ended,failed_suites=sum(r['failed']for r in rows),actual_captured_PIDs=[p.pid for p in outer.OWNED],
|
||||
final_owned_cleanup=cleanup,cleanup_errors=cleanup_errors,guard_errors=guard_errors,error=error,
|
||||
all_tests_sha256=sha(out/'all-tests.json') if (out/'all-tests.json').exists() else None,phases=phases,
|
||||
default_player_after=default_player(root),formal_acceptance=False,native_Windows='pending'))
|
||||
print(json.dumps(dict(context,passed=passed,error=error,cleanup_errors=cleanup_errors,guard_errors=guard_errors)),flush=True)
|
||||
return 0 if passed else 1
|
||||
if __name__=='__main__':raise SystemExit(main())
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
"""Exact per-suite completion schemas. Counts come only from immutable raw logs."""
|
||||
from pathlib import Path
|
||||
import hashlib,json,re
|
||||
|
||||
FIELDS=('checks','failures','passed','failed','skipped')
|
||||
KNOWN_LEAK_WARNING=re.compile(r'^\s*(?:WARNING:[^\r\n]*(?:ObjectDB instances leaked|resources? still in use at exit)|Leaked instance:)[^\r\n]*$')
|
||||
def sha(path):return hashlib.sha256(Path(path).read_bytes()).hexdigest()
|
||||
def load_contracts(path):
|
||||
data=json.loads(Path(path).read_text())
|
||||
if data.get('schema_version')!=1:raise ValueError('marker contract schema')
|
||||
rows=data.get('rows',[]);by_test={row['test']:row for row in rows}
|
||||
if len(rows)!=len(by_test):raise ValueError('duplicate suite marker contract')
|
||||
for row in rows:
|
||||
pattern(row['template'])
|
||||
for auxiliary in row.get('auxiliary',[]):pattern(auxiliary['template'])
|
||||
return by_test
|
||||
def pattern(template):
|
||||
escaped=re.escape(template)
|
||||
for field in FIELDS:escaped=escaped.replace(re.escape('<'+field+'>'),'(?P<'+field+'>[0-9]+)')
|
||||
compiled=re.compile(escaped)
|
||||
if 'checks' not in compiled.groupindex:raise ValueError('missing named checks schema')
|
||||
return compiled
|
||||
def marker_like(line):
|
||||
return bool(re.match(r'^(?:PASS|FAIL):\s.*checks',line) or re.match(r'^.*?:\s*[0-9]+(?: [A-Za-z]+)* checks(?:[,; /].*)?$',line) or re.match(r'^[A-Z][A-Z0-9_]*(?: phase=[a-z]+)? checks=',line) or line.startswith(' checks='))
|
||||
def integer(value):return type(value) is int
|
||||
def parse_raw(test,content,contracts,source_sha256=None):
|
||||
errors=[];spec=contracts.get(test)
|
||||
if spec is None:return {'passed':False,'checks':None,'failures':None,'marker':None,'errors':['unknown suite marker schema']}
|
||||
lines=content.splitlines(keepends=True)
|
||||
banners=[line for line in lines if line.startswith('Godot Engine v')]
|
||||
if len(banners)!=1 or banners[0] not in ['Godot Engine v4.7.2.stable.official.ed1daf0bf - https://godotengine.org\n','Godot Engine v4.7.2.stable.official.ed1daf0bf - https://godotengine.org\r\n']:errors.append('missing/duplicate/wrong engine banner')
|
||||
diagnostics=[line.rstrip('\r\n') for line in lines if any(token in line for token in ['ERROR:','SCRIPT ERROR:','Parse Error:']) or re.match(r'^\s*(?:FAIL:|FAILED:|FAIL )',line)]
|
||||
if diagnostics:errors.append('raw diagnostics or FAIL lines')
|
||||
leak_warnings=[line.rstrip('\r\n') for line in lines if KNOWN_LEAK_WARNING.fullmatch(line.rstrip('\r\n'))]
|
||||
if leak_warnings:errors.append('known exit object/resource leak warning')
|
||||
unicode_lines=[line.rstrip('\r\n') for line in lines if 'Unicode parsing error' in line]
|
||||
known_unicode=spec.get('known_unicode_diagnostic',{})
|
||||
allowed_unicode=unicode_lines and source_sha256==known_unicode.get('source_sha256') and all(line==known_unicode.get('exact_line') for line in unicode_lines)
|
||||
if unicode_lines and not allowed_unicode:errors.append('unrecognized Unicode diagnostic or source pin')
|
||||
schemas=[dict(spec,primary=True)]+[dict(row,primary=False) for row in spec.get('auxiliary',[])]
|
||||
observed=[];recognized_indices=set()
|
||||
for schema in schemas:
|
||||
compiled=pattern(schema['template']);matches=[]
|
||||
prefix=schema['template'].split('<checks>')[0]
|
||||
for index,line in enumerate(lines):
|
||||
text=line.rstrip('\r\n');match=compiled.fullmatch(text)
|
||||
if match:
|
||||
if not line.endswith('\n'):errors.append('completion line missing newline')
|
||||
matches.append((index,line,{key:int(value) for key,value in match.groupdict().items()}))
|
||||
elif prefix and text.startswith(prefix) and 'checks' in text:errors.append('malformed expected completion schema')
|
||||
required=schema.get('required',True)
|
||||
if len(matches)!=(1 if required else len(matches)) or len(matches)>1:errors.append('missing/duplicate '+('primary' if schema['primary'] else 'auxiliary')+' completion marker')
|
||||
if not matches:
|
||||
if schema['primary']:observed.append(None)
|
||||
continue
|
||||
index,line,values=matches[0];recognized_indices.add(index)
|
||||
if values['checks']<=0:errors.append('nonpositive raw check count')
|
||||
for field in ['failures','failed','skipped']:
|
||||
if values.get(field,0)!=0:errors.append('raw nonzero '+field)
|
||||
if 'passed' in values and values['passed']!=values['checks']:errors.append('raw passed/check count mismatch')
|
||||
observed.append({'line':line,'values':values,'primary':schema['primary']})
|
||||
for index,line in enumerate(lines):
|
||||
if marker_like(line.rstrip('\r\n')) and index not in recognized_indices:errors.append('unexpected other-label/schema completion line')
|
||||
primary=next((row for row in observed if row and row['primary']),None)
|
||||
if primary:
|
||||
for row in observed:
|
||||
if row and row['values']['checks']!=primary['values']['checks']:errors.append('raw auxiliary/primary check count mismatch')
|
||||
if spec.get('json_report'):
|
||||
prefix=spec['json_report']['prefix'];reports=[line[len(prefix):].strip() for line in lines if line.startswith(prefix)]
|
||||
if len(reports)!=1:errors.append('missing/duplicate raw JSON report')
|
||||
else:
|
||||
try:
|
||||
report=json.loads(reports[0]);summary=report['check_summary']
|
||||
if not primary or any(not integer(summary.get(field)) for field in ['total','passed','failed','skipped']) or summary!={'total':primary['values']['checks'],'passed':primary['values']['checks'],'failed':0,'skipped':0}:errors.append('raw JSON/marker check count mismatch')
|
||||
if report.get('status') not in ['PASS','SMOKE_PASS'] or report.get('runtime_errors')!=[] or report.get('unmet_fixture_fields')!=[]:errors.append('raw JSON report not complete and error-free')
|
||||
except (ValueError,KeyError,TypeError):errors.append('malformed raw JSON report')
|
||||
return {'passed':not errors,'checks':primary['values']['checks'] if primary else None,'failures':primary['values'].get('failures',primary['values'].get('failed',0)) if primary else None,'marker':primary['line'] if primary else None,'errors':sorted(set(errors)),'diagnostic_lines':diagnostics,'known_leak_warning_lines':leak_warnings,'known_leak_warning_count':len(leak_warnings),'known_unicode_diagnostic_count':len(unicode_lines) if allowed_unicode else 0,'unrecognized_unicode_diagnostic_count':0 if allowed_unicode else len(unicode_lines)}
|
||||
|
||||
def review_row(row,content,contracts,source_map,project_root,run_id,start,end,raw_sha):
|
||||
parsed=parse_raw(row.get('test',''),content,contracts,source_map.get(row.get('test')));errors=list(parsed['errors'])
|
||||
test=row.get('test');expected_command=[row.get('godot'),'--headless','--path',str(Path(project_root).resolve()),'--script','res://'+str(test)]
|
||||
if not integer(row.get('exit_code')) or row.get('exit_code')!=0 or row.get('failed') is not False or row.get('engine_ended') is not True:errors.append('process failure or not ended')
|
||||
if not integer(row.get('actual_pid')) or row['actual_pid']<=0:errors.append('actual PID missing/invalid')
|
||||
if row.get('command')!=expected_command:errors.append('actual command/test/root mismatch')
|
||||
if not integer(row.get('reported_checks')) or row.get('reported_checks')!=parsed['checks']:errors.append('reported/raw check count mismatch')
|
||||
if not integer(row.get('reported_failures')) or row.get('reported_failures')!=parsed['failures'] or row.get('completion_marker')!=parsed['marker']:errors.append('reported/raw marker mismatch')
|
||||
if test not in source_map or row.get('source_sha256')!=source_map.get(test) or row.get('source_sha256_end')!=source_map.get(test):errors.append('suite source pin mismatch')
|
||||
for receipt in [start,end]:
|
||||
if not isinstance(receipt,dict) or receipt.get('test')!=test or receipt.get('run_id')!=run_id or receipt.get('sequence')!=row.get('sequence') or receipt.get('actual_pid')!=row.get('actual_pid') or receipt.get('command')!=expected_command or receipt.get('source_sha256')!=source_map.get(test):errors.append('start/end PID/input receipt mismatch')
|
||||
if isinstance(start,dict) and isinstance(end,dict):
|
||||
if not integer(start.get('owner_runner_pid')) or start['owner_runner_pid']<=0 or start.get('owner_runner_pid')!=end.get('owner_runner_pid'):errors.append('runner owner PID mismatch')
|
||||
if not integer(start.get('monotonic_ns')) or not integer(end.get('monotonic_ns')) or end.get('monotonic_ns',-1)<start.get('monotonic_ns',0):errors.append('receipt time invalid')
|
||||
if start.get('event')!='ENGINE_STARTED' or end.get('event')!='ENGINE_ENDED' or end.get('engine_ended') is not True or not integer(end.get('exit_code')) or end.get('exit_code')!=row.get('exit_code'):errors.append('process lifecycle receipt mismatch')
|
||||
if end.get('raw_sha256')!=raw_sha or row.get('log_sha256')!=raw_sha:errors.append('raw SHA receipt mismatch')
|
||||
result=dict(parsed);result.update(passed=not errors,errors=sorted(set(errors)),actual_pid=row.get('actual_pid'),test=test)
|
||||
return result
|
||||
|
||||
def review_roster(roster,rows):
|
||||
names=[row.get('test') for row in rows]
|
||||
errors=[]
|
||||
if len(roster)!=len(set(roster)):errors.append('duplicate expected roster')
|
||||
if names!=roster or len(names)!=len(set(names)):errors.append('missing/duplicate/extra/reordered actual roster')
|
||||
if any(not integer(row.get('sequence')) for row in rows) or [row.get('sequence') for row in rows]!=list(range(len(roster))):errors.append('suite sequence mismatch')
|
||||
if len([row.get('log') for row in rows])!=len(set(row.get('log') for row in rows)):errors.append('duplicate suite log binding')
|
||||
return {'passed':not errors,'errors':errors}
|
||||
Reference in New Issue
Block a user