| 1 | #!/usr/bin/env python3 |
| 2 | """Cold-open the retained storage-interruption matrix with disposable OneNote VMs.""" |
| 3 | import argparse |
| 4 | from concurrent.futures import ThreadPoolExecutor |
| 5 | import hashlib |
| 6 | import json |
| 7 | from pathlib import Path |
| 8 | import runpy |
| 9 | import subprocess |
| 10 | import xml.etree.ElementTree as ET |
| 11 | |
| 12 | from native_runner import capture |
| 13 | from document_model import EXPORTER, view |
| 14 | |
| 15 | |
| 16 | def run(matrix, output, workers): |
| 17 | cases = json.loads((matrix / 'native-cases.json').read_text()) |
| 18 | output.mkdir(parents=True, exist_ok=False) |
| 19 | compare = runpy.run_path(str(Path(__file__).with_name('verify-document.py')))['compare'] |
| 20 | |
| 21 | def verify(item): |
| 22 | digest, name = item |
| 23 | source = matrix / name / 'notebook' |
| 24 | before = {p.name: hashlib.sha256(p.read_bytes()).hexdigest() for p in source.iterdir()} |
| 25 | changed, = [p for p in source.iterdir() if hashlib.md5(p.read_bytes()).hexdigest() == digest] |
| 26 | destination = output / name |
| 27 | capture(source, destination, pdf=True) |
| 28 | compare(source, destination / 'read') |
| 29 | if changed.suffix == '.onetoc2': |
| 30 | document = json.loads(subprocess.check_output([EXPORTER, changed])) |
| 31 | _, revision = view(document, document['root']) |
| 32 | color = revision['nodes'][revision['roots']['1']]['kind']['color'] |
| 33 | expected = '#' + int(color).to_bytes(4, 'little')[:3].hex().upper() |
| 34 | assert ET.parse(destination / 'read/hierarchy.xml').getroot().get('color') == expected, 'Native notebook color differs from the persisted TOC' |
| 35 | assert before == {p.name: hashlib.sha256(p.read_bytes()).hexdigest() for p in source.iterdir()} |
| 36 | assert json.loads((destination / 'teardown.json').read_text())['absent'] |
| 37 | (destination / 'verified.json').write_text(json.dumps({'source_sha256': before, 'native_comparison': True, 'vm_deleted': True}, indent=2)) |
| 38 | print('Verified native recovery:', name, flush=True) |
| 39 | return name |
| 40 | |
| 41 | with ThreadPoolExecutor(max_workers=workers) as pool: |
| 42 | verified = list(pool.map(verify, cases.items())) |
| 43 | (output / 'result.json').write_text(json.dumps({'cases': sorted(verified), 'native_comparison': True}, indent=2)) |
| 44 | |
| 45 | |
| 46 | if __name__ == '__main__': |
| 47 | parser = argparse.ArgumentParser(description=__doc__) |
| 48 | parser.add_argument('matrix', type=Path) |
| 49 | parser.add_argument('output', type=Path) |
| 50 | parser.add_argument('--workers', type=int, default=2) |
| 51 | args = parser.parse_args() |
| 52 | if not 1 <= args.workers <= 4: |
| 53 | parser.error('Choose one to four simultaneous captures.') |
| 54 | run(args.matrix, args.output, args.workers) |