| 1 | #!/usr/bin/env python3 |
| 2 | """Compare every interrupted/recovered image with an independent cold native export.""" |
| 3 | import argparse |
| 4 | import hashlib |
| 5 | import json |
| 6 | from pathlib import Path |
| 7 | import xml.etree.ElementTree as ET |
| 8 | from native_format import native_characters |
| 9 | from native_xml import ns |
| 10 | |
| 11 | |
| 12 | def 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 | |
| 17 | def 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 | |
| 48 | if __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)) |