1#!/usr/bin/env python3
2"""Capture explicit page removals and independently reopen the resulting notebook."""
3import argparse
4import hashlib
5import json
6from pathlib import Path
7import runpy
8import signal
9from tempfile import TemporaryDirectory
10
11import native_runner as runner
12
13BASE = 'page-lifecycle/04-nested/notebook/Lifecycle.one'
14LEADING = 'page-lifecycle/movement/08-collapsed-group-move/notebook/Lifecycle.one'
15CASES = {
16 'trailing': (BASE, 9, [8], False),
17 'parent': (BASE, 9, [3], False),
18 'child': (BASE, 9, [4], False),
19 'grandchild': (BASE, 9, [5], False),
20 'group': (BASE, 9, [3, 4, 5], False),
21 'all': (BASE, 9, list(range(9)), False),
22 'permanent-parent': (BASE, 9, [3], True),
23 'leading-parent': (LEADING, 9, [0], False),
24 'leading-group': (LEADING, 9, [0, 1], False),
25 'features': ('m6/native-expanded-control-ui-01/notebook/Features.one', 13,
26 [0, 3, 8, 9, 10, 11, 12], False),
27 'ink': ('native-ink/20260905-ui/notebook/synthetic.one', 1, [0], False),
28}
29
30
31def capture(case, output):
32 source, count, selected, permanent = CASES[case]
33 output.mkdir(parents=True, exist_ok=False)
34 request = {'case': case, 'selected': selected, 'permanent': permanent,
35 'expected_pages': count}
36 (output / 'removal.json').write_text(json.dumps(request, indent=2) + '\n')
37 scripts = {name: (runner.ROOT / 'tools/native' / name).read_bytes()
38 for name in ['page-lifecycle.ps1', 'page-removal.ps1']}
39
40 def interact(target, destination):
41 for name, data in scripts.items():
42 local = destination / 'scripts' / name
43 local.write_bytes(data)
44 result = runner.windows.do_put(local, 'C:\\one-tests\\' + name, target)
45 if result.get('error'):
46 raise RuntimeError(result['error'])
47 result = runner.windows.do_put(output / 'removal.json', r'C:\one-tests\runs\capture\removal.json', target)
48 if result.get('error'):
49 raise RuntimeError(result['error'])
50 (destination / 'interaction.json').write_text(json.dumps({
51 name: hashlib.sha256(data).hexdigest()
52 for name, data in scripts.items()}, indent=2) + '\n')
53 runner.command(target, 'powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\page-removal.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-' + target.upper(), destination, 180000)
54
55 with TemporaryDirectory() as temporary:
56 stage = Path(temporary)
57 (stage / 'Lifecycle.one').symlink_to(runner.ROOT / 'corpus' / source)
58 runner.capture(stage, output / 'native', expected_pages=count,
59 collect_notebook=True, interaction=interact)
60 runner.capture(output / 'native/after/notebook', output / 'cold', collect_notebook=True)
61 compare = runpy.run_path(str(runner.ROOT / 'tools/verify-document.py'))['compare']
62 compare(output / 'native/after/notebook', output / 'cold/read')
63 compare(output / 'cold/notebook', output / 'cold/read')
64
65
66if __name__ == '__main__':
67 parser = argparse.ArgumentParser(description=__doc__)
68 parser.add_argument('case', choices=CASES)
69 parser.add_argument('output', type=Path)
70 args = parser.parse_args()
71
72 def interrupted(_signum, _frame):
73 raise KeyboardInterrupt
74
75 signal.signal(signal.SIGTERM, interrupted)
76 capture(args.case, args.output)