1#!/usr/bin/env python3
2"""Cold-open every SMB fault artifact and compare its complete native text."""
3import argparse
4from concurrent.futures import ThreadPoolExecutor
5import hashlib
6import json
7from pathlib import Path
8import signal
9import xml.etree.ElementTree as ET
10
11from native_format import native_characters
12from native_xml import ns
13from native_probe import ROOT, run as probe
14
15
16def verify(root, captures):
17 cases = root / 'cases'
18 records = json.loads((cases / 'results.json').read_text())
19 assert records and len({record['case'] for record in records}) == len(records), 'Missing or duplicate fault cases'
20 packets = {f'{role}-{path.stem}': path for role in ('source', 'interrupted', 'recovered')
21 for path in (cases / role).glob('*.one')}
22 expected_names = {f'{role}-{record["case"]}' for record in records for role in ('interrupted', 'recovered')}
23 fixtures = {record['case'].rsplit('-', 1)[0] for record in records}
24 expected_names.update(f'source-{fixture}' for fixture in fixtures)
25 assert set(packets) == expected_names, 'Artifact inventory differs from completed cases'
26 observed = {}
27 for worker in captures.glob('worker-*'):
28 result = json.loads((worker / 'results.json').read_text(encoding='utf-8-sig'))
29 for record in result if isinstance(result, list) else [result]:
30 name = record['name']
31 assert name in packets and name not in observed, 'Unexpected or duplicate native result'
32 assert record['error'] is None and record['pages'] == 1, f'{name}: native open failed'
33 assert record['source_sha256'] == hashlib.sha256(packets[name].read_bytes()).hexdigest(), f'{name}: native input differs'
34 page, = (worker / 'results' / name).glob('page-*.xml')
35 page = ET.parse(page).getroot()
36 containers = page.findall('one:Title', ns) + page.findall('one:Outline', ns)
37 observed[name] = [''.join(char for char, _ in paragraph) for paragraph in native_characters(page, containers)]
38 assert set(observed) == expected_names, 'Missing native captures'
39 for record in records:
40 fixture = record['case'].rsplit('-', 1)[0]
41 intent = json.loads((cases / f'{fixture}-intent.json').read_text())
42 baseline = observed[f'source-{fixture}']
43 assert baseline.count(intent['before']) == 1, 'The native source lacks a unique editing target'
44 for role in ('interrupted', 'recovered'):
45 replacement = intent[record['visible']] if role == 'interrupted' else intent['after'] + intent['suffix']
46 expected = [replacement if text == intent['before'] else text for text in baseline]
47 assert observed[f'{role}-{record["case"]}'] == expected, f'{role}-{record["case"]}: native content differs from the recorded outcome'
48 return {'cases': len(records), 'cold_native_opens': len(observed), 'exact_native_text': True}
49
50
51def run(root, output):
52 assert json.loads((root / 'verification.json').read_text())['server_hashes_match']
53 output.mkdir(parents=True, exist_ok=False)
54 (output / 'scripts').mkdir()
55 scripts = [output / 'scripts' / name for name in ('cold.ps1', 'probe.ps1')]
56 for path in scripts: path.write_bytes((ROOT / 'tools/native' / path.name).read_bytes())
57 packets = [(role, path) for role in ('source', 'interrupted', 'recovered')
58 for path in sorted((root / 'cases' / role).glob('*.one'))]
59 workers = min(8, len(packets))
60 inputs = [output / f'input-{i}' for i in range(workers)]
61 for path in inputs: path.mkdir()
62 for i, (role, path) in enumerate(packets):
63 (inputs[i % workers] / f'{role}-{path.name}').symlink_to(path.resolve())
64 def capture(i): probe(inputs[i], output / f'worker-{i}', scripts)
65 with ThreadPoolExecutor(max_workers=workers) as pool:
66 list(pool.map(capture, range(workers)))
67 result = verify(root, output)
68 (output / 'verification.json').write_text(json.dumps(result, indent=2))
69 print(json.dumps(result), flush=True)
70
71
72if __name__ == '__main__':
73 parser = argparse.ArgumentParser(description=__doc__)
74 parser.add_argument('run', type=Path)
75 parser.add_argument('captures', type=Path)
76 parser.add_argument('--verify-only', action='store_true')
77 args = parser.parse_args()
78 def interrupted(_signal, _frame): raise KeyboardInterrupt
79 signal.signal(signal.SIGTERM, interrupted)
80 if args.verify_only: print(json.dumps(verify(args.run.resolve(), args.captures.resolve()), indent=2))
81 else: run(args.run.resolve(), args.captures.resolve())