from pathlib import Path import hashlib,json,subprocess,datetime,time,os R=Path(__file__).resolve().parent.parent P=R/'.local/runtime-notices-20260913' Q=R/'.local/runtime-notices-integration-20260913';Q.mkdir() def sha(b):return hashlib.sha256(b).hexdigest() def new(p,b): with p.open('xb') as f:f.write(b) def record(p): b=p.read_bytes();return {'path':str(p.relative_to(R)),'bytes':len(b),'sha256':sha(b)} manifest=R/'scripts/windows-notices/manifest.json';before=manifest.read_bytes() assert sha(before)=='b39beaee5d8aeedcb45c7fd9b65c1b88a4fe784ddb9fe3d4126f5750e478c081' entry=json.loads((P/'runtime-entry.json').read_bytes());base=json.loads(before) expected=json.loads(json.dumps(base));expected['runtimes'].append(entry) assert expected==json.loads((P/'notice-inputs/manifest.json').read_bytes()) nl=b'\r\n' if b'\r\n' in before else b'\n' suffix=nl.join([b' }',b' ]',b'}',b'']) assert before.endswith(suffix) encoded=nl.join((' '+s).encode() for s in json.dumps(entry,indent=2).splitlines()) after=before[:-len(suffix)]+b' },'+nl+encoded+nl+b' ]'+nl+b'}'+nl assert json.loads(after)==expected new(Q/'manifest-before.json',before) manifest.write_bytes(after) attributes=R/'.gitattributes';attrbefore=attributes.read_bytes() line=b'harness/experiments/runtime-notices-20260913/** -text whitespace=-blank-at-eol,-blank-at-eof,cr-at-eol' assert line not in attrbefore attrnl=b'\r\n' if b'\r\n' in attrbefore else b'\n' attributes.write_bytes(attrbefore+(b'' if attrbefore.endswith(b'\n') else attrnl)+line+attrnl) new(Q/'attributes-before',attrbefore) # Exact changed semantics and all actual notice-file digests, without collector/Cargo. for group in ['packages','assets','runtimes']: for row in expected[group]: for n in row.get('notices',[]): assert sha((R/'scripts/windows-notices'/n['file']).read_bytes())==n['sha256'] assert len([x for x in expected['runtimes'] if x['sha256']==entry['sha256']])==1 assert sha((R/'.local/toolchain/llvm-mingw-20260908-ucrt-x86_64/x86_64-w64-mingw32/bin/libunwind.dll').read_bytes())==entry['sha256'] test=R/'scripts/test-windows-notices.ps1';collector=R/'scripts/windows-notices.ps1' testbytes=test.read_bytes() assert b'Remove-Item' not in testbytes and b'Directory]::Delete' not in testbytes and b'rm -' not in testbytes assert b'Synthetic fixture retained for inspection:' in testbytes shell=Path(os.environ['SystemRoot'])/'System32/WindowsPowerShell/v1.0/powershell.exe' command=[str(shell),'-NoProfile','-ExecutionPolicy','Bypass','-File',str(test)] inputs=[record(p) for p in [test,collector,manifest,attributes]] start=datetime.datetime.now(datetime.timezone.utc).isoformat();clock=time.monotonic() with (Q/'synthetic.stdout.log').open('xb') as stdout,(Q/'synthetic.stderr.log').open('xb') as stderr: process=subprocess.run(command,cwd=R,stdout=stdout,stderr=stderr) elapsed=time.monotonic()-clock assert inputs==[record(p) for p in [test,collector,manifest,attributes]] receipt={'start_utc':start,'end_utc':datetime.datetime.now(datetime.timezone.utc).isoformat(),'command':command,'powershell_executable':{'path':str(shell),'sha256':sha(shell.read_bytes())},'elapsed_seconds':elapsed,'exit':process.returncode,'inputs':inputs,'inputs_unchanged':True,'base_manifest_sha256':sha(before),'mapping_only_append':True,'all_notice_digests_pass':True,'runtime_digest_pass':True,'cleanup_review':'Original script retains GUID-named synthetic temp fixture; no recursive removal/move. Cargo/rustc are local mock functions; no actual compiler invoked.','stdout':record(Q/'synthetic.stdout.log'),'stderr':record(Q/'synthetic.stderr.log')} new(Q/'result.json',(json.dumps(receipt,indent=2)+'\n').encode()) print(json.dumps(receipt,indent=2)) assert process.returncode==0 assert b'PASS:' in (Q/'synthetic.stdout.log').read_bytes()