| 1 | """Compare created pages with native XML and cold-reopened object graphs.""" |
| 2 | import argparse |
| 3 | from copy import deepcopy |
| 4 | import json |
| 5 | from pathlib import Path |
| 6 | import runpy |
| 7 | import shutil |
| 8 | import subprocess |
| 9 | from tempfile import TemporaryDirectory |
| 10 | from uuid import UUID |
| 11 | |
| 12 | from document_model import EXPORTER, ordered_pages, view |
| 13 | |
| 14 | compare_document = runpy.run_path(str(Path(__file__).with_name('verify-document.py')))['compare'] |
| 15 | |
| 16 | |
| 17 | def 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 | |
| 81 | if __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) |