1#!/usr/bin/env python3
2"""Cold-open the retained storage-interruption matrix with disposable OneNote VMs."""
3import argparse
4from concurrent.futures import ThreadPoolExecutor
5import hashlib
6import json
7from pathlib import Path
8import runpy
9import subprocess
10import xml.etree.ElementTree as ET
11
12from native_runner import capture
13from document_model import EXPORTER, view
14
15
16def 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
46if __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)