1"""Compare created pages with native XML and cold-reopened object graphs."""
2import argparse
3from copy import deepcopy
4import json
5from pathlib import Path
6import runpy
7import shutil
8import subprocess
9from tempfile import TemporaryDirectory
10from uuid import UUID
11
12from document_model import EXPORTER, ordered_pages, view
13
14compare_document = runpy.run_path(str(Path(__file__).with_name('verify-document.py')))['compare']
15
16
17def compare(source, capture, *, refresh_metadata=False):
18 files = list(source.glob('*.one'))
19 assert len(files) == 1
20 with TemporaryDirectory() as temporary:
21 temporary = Path(temporary)
22 native = temporary / 'native/read'
23 shutil.copytree(capture / 'read', native)
24 notebook = temporary / 'notebook'
25 notebook.mkdir()
26 (notebook / files[0].name).symlink_to(files[0].resolve())
27 compare_document(notebook, native)
28 models = []
29 for ordinal, file in enumerate((files[0], capture / 'notebook' / files[0].name)):
30 output = temporary / str(ordinal)
31 subprocess.run([EXPORTER, file, output], check=True)
32 models.append(json.loads((output / 'document.json').read_text()))
33 before, after = models
34 pages = list(ordered_pages(before))
35 metadata = {}
36 for page_sid, _, page_view, _ in pages:
37 # Native uses the space GUID when it differs from the page-node GUID.
38 guid = UUID(page_sid.split('}')[0].strip('{')).bytes_le
39 salt = UUID('22a8c031-3600-42ee-b714-d7acda2435e8').bytes_le
40 oid = '{' + str(UUID(bytes_le=bytes(a ^ b for a, b in zip(guid, salt, strict=True)))).upper() + '},1'
41 metadata[oid] = (page_sid, page_view['nodes'][page_view['roots']['2']])
42 assert [(sid, page) for sid, _, _, page in pages] == [
43 (sid, page) for sid, _, _, page in ordered_pages(after)]
44 assert before['root'] == after['root']
45 assert before['spaces'].keys() == after['spaces'].keys()
46 filled = refreshed = 0
47 for sid in before['spaces']:
48 _, original = view(before, sid)
49 _, observed = view(after, sid)
50 expected = deepcopy(original['nodes'])
51 if sid == before['root']:
52 for series_id in original['nodes'][original['roots']['1']]['children']:
53 series = expected[series_id]
54 existing = [p for p in series['extra'][0] if p['id'] == 0x24003442]
55 added = [p for p in observed['nodes'][series_id]['extra'][0] if p['id'] == 0x24003442]
56 if existing and refresh_metadata:
57 for copy in existing[0]['value']['Objects']:
58 assert expected[copy]['kind']['type'] == 'Metadata'
59 current = metadata[copy][1]['kind']
60 refreshed += expected[copy]['kind'] != current
61 expected[copy]['kind'] = current
62 if existing or not added:
63 continue
64 copies = []
65 for oid, (page_sid, node) in metadata.items():
66 if page_sid not in series['spaces']:
67 continue
68 assert oid not in expected
69 expected[oid] = node
70 copies.append(oid)
71 filled += 1
72 assert len(added) == 1
73 assert sorted(copies) == sorted(added[0]['value']['Objects'])
74 series['extra'][0].append(added[0])
75 assert original['roots'] == observed['roots']
76 assert expected == observed['nodes'], sid
77 print(f'Passed: {len(pages)} ordered pages and preserved active graphs; {filled} missing section metadata copies filled natively; {refreshed} section metadata copies refreshed')
78 return refreshed
79
80
81if __name__ == '__main__':
82 parser = argparse.ArgumentParser(description=__doc__)
83 parser.add_argument('source', type=Path)
84 parser.add_argument('capture', type=Path)
85 args = parser.parse_args()
86 compare(args.source, args.capture)