Files
Takeshi b84bade045 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.
2026-10-11 15:58:00 +09:00

69 lines
6.5 KiB
Python

"""Guarded standard collector: logical original suites and actual native argv.
Run the canonical runner function in this same owned Python process so an
abruptly killed wrapper cannot strand a separate runner's unobserved children.
Actual native Popen objects remain owned by the runner's unchanged inner/outer
finally adapters. The wrapper never signals groups or discovered processes.
"""
from datetime import datetime
from zoneinfo import ZoneInfo
from pathlib import Path
import argparse,contextlib,hashlib,importlib.util,json,os,shutil,sys
sys.dont_write_bytecode=True
ROOT=Path(__file__).resolve().parents[4]
def sha(p):return hashlib.sha256(Path(p).read_bytes()).hexdigest()
def load(name,path):
spec=importlib.util.spec_from_file_location(name,path);m=importlib.util.module_from_spec(spec);spec.loader.exec_module(m);return m
def write(p,value):
with p.open('x',encoding='utf-8')as stream:stream.write(json.dumps(value,ensure_ascii=False,indent=2)+'\n')
def expected_roster():
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 main(argv=None):
parser=argparse.ArgumentParser(description=__doc__)
parser.add_argument('--project-root',type=Path,default=ROOT);parser.add_argument('--output',type=Path,required=True)
parser.add_argument('--godot',default='godot');parser.add_argument('--timeout',type=int,default=1200)
parser.add_argument('--standard-runner',type=Path);parser.add_argument('--source-input-refs',type=Path)
parser.add_argument('--marker-contracts',type=Path)
args=parser.parse_args(argv);root=args.project_root.resolve();out=args.output.resolve()
runner_path=(args.standard_runner or root/'tools/run_all_tests.py').resolve()
runner=load('actual_guarded_standard_runner',runner_path);protocol=load('actual_guarded_standard_reader',runner_path.parent/'regression/review_protocol.py')
marker_path=runner_path.parent/'strict_test_markers.py';module=load('guarded_original_marker',marker_path)
contract_path=(args.marker_contracts or root/'docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json').resolve();contracts=module.load_contracts(contract_path)
before=runner.source_refs(root);selected=runner.read(args.source_input_refs)if args.source_input_refs else before
expected={n:r['sha256']for n,r in selected.items()};wrapper_sha=sha(Path(__file__));marker_sha=sha(marker_path);contract_sha=sha(contract_path)
out.mkdir(parents=True,exist_ok=False)
for source,name in [(Path(__file__),'wrapper-input.py'),(marker_path,'marker-module-input.py'),(contract_path,'marker-contract-input.json'),(runner_path,'standard-runner-input.py')]:shutil.copyfile(source,out/name)
write(out/'source-start.json',expected);write(out/'protected-current-source-start.json',before)
logical=['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')
write(out/'expected-tests.json',logical);started=datetime.now(ZoneInfo('Asia/Tokyo')).isoformat()
command=[str(runner_path),'--project-root',str(root),'--output',str(out/'suites'),'--godot',args.godot,'--timeout',str(args.timeout),'--marker-contracts',str(contract_path)]
if args.source_input_refs:command+=['--source-input-refs',str(args.source_input_refs.resolve())]
write(out/'standard-driver-start.json',{'event':'OWNED_IN_PROCESS_STANDARD_CALL','actual_python_pid':os.getpid(),'owner_wrapper_pid':os.getpid(),'native_PID_is_not_wrapper_PID':True,'logical_arguments':command,'runner_sha256':sha(runner_path)})
code=1;call_error=None
try:
with(out/'standard-driver.raw.log').open('x',encoding='utf-8')as stream,contextlib.redirect_stdout(stream),contextlib.redirect_stderr(stream):code=runner.main(command[1:])
except BaseException as caught:call_error=type(caught).__name__+': '+str(caught)
write(out/'standard-driver-end.json',{'event':'OWNED_IN_PROCESS_STANDARD_RETURN','actual_python_pid':os.getpid(),'owner_wrapper_pid':os.getpid(),'exit_code':code,'call_error':call_error,'raw_sha256':sha(out/'standard-driver.raw.log')})
after=runner.source_refs(root);write(out/'protected-current-source-end.json',after)
write(out/'source-end.json',{n:sha(r['path'])for n,r in selected.items()})
changes=sorted(n for n in set(before)|set(after)if before.get(n)!=after.get(n));errors=[];review=None
try:review=protocol.review(out/'suites',root,expected,contracts,os.getpid())
except BaseException as caught:errors.append(type(caught).__name__+': '+str(caught))
unchanged=sha(Path(__file__))==wrapper_sha and sha(marker_path)==marker_sha and sha(contract_path)==contract_sha
passed=code==0 and call_error is None and not changes and unchanged and not errors and review is not None and review['passed']
results=out/'suites/all-tests.json';rows=review['rows']if review else []
report={'scope':'current pinned-source guarded standard regression; no original/native/full-game acceptance','passed':passed,
'protocol':'guarded-standard-v1','wrapper_sha256':wrapper_sha,'wrapper_sha256_after':sha(Path(__file__)),
'wrapper_copy_path':str(out/'wrapper-input.py'),'wrapper_unchanged':unchanged,'all_results_sha256':sha(results)if results.exists()else None,
'marker_module_sha256':marker_sha,'marker_contract_sha256':contract_sha,'standard_runner_sha256':expected['tools/run_all_tests.py'],
'source_map_sha256':sha(out/'source-start.json'),'source_files':len(expected),'started_at_jst':started,
'finished_at_jst':datetime.now(ZoneInfo('Asia/Tokyo')).isoformat(),'runner_exit_code':code,'standard_runner_pid':os.getpid(),
'standard_runner_execution_kind':'same owned Python process; distinct actual native PID receipts per phase',
'suites':len(rows),'checks':review['actual_all_roster_checks']if review else None,'count_from_raw_only':True,
'failed_tests':[r['test']for r in rows if not r['passed']],'changed_paths':changes,'collector_errors':errors+(review['collector_errors']if review else []),
'call_error':call_error,'rows':rows,'actual_review':review,'original_full_acceptance':False,'windows_acceptance':False,
'P28_maximum_scene_performance_acceptance':False}
write(out/'verification.json',report);print(json.dumps({k:v for k,v in report.items()if k not in ['rows','actual_review']},ensure_ascii=False),flush=True)
return 0 if passed else 1
if __name__=='__main__':raise SystemExit(main())