1#!/usr/bin/env python3
2"""Compare every interrupted/recovered image with an independent cold native export."""
3import argparse
4import hashlib
5import json
6from pathlib import Path
7import xml.etree.ElementTree as ET
8from native_format import native_characters
9from native_xml import ns
10
11
12def paragraphs(path):
13 root = ET.parse(path).getroot()
14 return [''.join(char for char, _ in text) for text in native_characters(root, root.findall('one:Outline', ns))]
15
16
17def verify(run, cold, source_capture):
18 original = paragraphs(source_capture)
19 target, = [text for text in original if text.startswith('Concurrent edits:')]
20 other = [text for text in original if text != target]
21 expected = {}
22 for case in json.loads((run/'results.json').read_text()):
23 for side in ('remote', 'local'):
24 if side+'_image' not in case: continue
25 digest, text = case[side+'_image'], case[side+'_text']
26 assert text in (target, target+' [offline-recovery]'), 'Recovery oracle is unrelated to the native source'
27 wanted = sorted([text, *other])
28 if digest in expected: assert expected[digest] == wanted, 'One image has contradictory expected states'
29 expected[digest] = wanted
30 inputs = json.loads((cold/'run.json').read_text())['inputs']
31 assert inputs == {digest+'.one': digest for digest in expected}, 'Cold input inventory differs from recovery images'
32 records = json.loads((cold/'results.json').read_text(encoding='utf-8-sig'))
33 if isinstance(records, dict): records = [records]
34 assert len(records) == len(expected) and {record['name'] for record in records} == set(expected), 'Cold results omit or duplicate an image'
35 for record in records:
36 digest = record['name']
37 assert record['source_sha256'] == digest, 'Native input hash differs'
38 assert hashlib.sha256((run/'images'/f'{digest}.one').read_bytes()).hexdigest() == digest, 'Retained image changed'
39 assert record['error'] is None and record['pages'] == 1, f'Native open failed: {record}'
40 pages = list((cold/'results'/digest).glob('page-*.xml'))
41 assert len(pages) == 1
42 assert sorted(paragraphs(pages[0])) == expected[digest], f'Native content differs for {digest}'
43 assert json.loads((cold/'teardown.json').read_text())['absent'], 'Cold VM remains'
44 return {'exact_images':len(records), 'exact_paragraphs':sum(map(len, expected.values())), 'native_errors':0,
45 'maximum_native_export_seconds':max(record['seconds'] for record in records)}
46
47
48if __name__ == '__main__':
49 parser = argparse.ArgumentParser(description=__doc__)
50 parser.add_argument('run', type=Path)
51 parser.add_argument('cold', type=Path)
52 parser.add_argument('source_capture', type=Path)
53 args = parser.parse_args()
54 result = verify(args.run, args.cold, args.source_capture)
55 (args.run/'cold-verification.json').write_text(json.dumps(result, indent=2))
56 print(json.dumps(result, indent=2))