| 1 | #!/usr/bin/env python3 |
| 2 | """Cold-open every SMB fault artifact and compare its complete native text.""" |
| 3 | import argparse |
| 4 | from concurrent.futures import ThreadPoolExecutor |
| 5 | import hashlib |
| 6 | import json |
| 7 | from pathlib import Path |
| 8 | import signal |
| 9 | import xml.etree.ElementTree as ET |
| 10 | |
| 11 | from native_format import native_characters |
| 12 | from native_xml import ns |
| 13 | from native_probe import ROOT, run as probe |
| 14 | |
| 15 | |
| 16 | def 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 | |
| 51 | def 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 | |
| 72 | if __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()) |