From 93784cadb44ec833ca6d7f3063c5d87255c1d6af Mon Sep 17 00:00:00 2001 From: clover caruso Date: Tue, 8 Sep 2026 00:02:13 -0700 Subject: [PATCH] feat: replace text across ordinary formatting runs Preserve surviving style references and terminal typing style while replacing scalar-aligned UTF-16 ranges. Reject affected fields and preserved run data before publication; reuse existing offline text intents after rebasing onto remote styles. Validate exhaustive ranges, history preservation, interrupted commits and lost replies. Retain six independent native fixtures and extend the twelve-client native/Rust workload to insertion, formatting and cross-run replacement. Checks: 180 Rust tests, eight doctests, 126 Python tests, Clippy and formatting; 7,987 ASan executions; 1,280 mixed editing intents through an outage with independent cold OneNote verification. Assisted-by: gpt-6-astra --- corpus/cross-run-edit/README.md | 20 ++ .../boundary/candidate/synthetic.one | Bin 0 -> 20840 bytes .../cold/notebook/Open Notebook.onetoc2 | Bin 0 -> 4800 bytes .../boundary/cold/notebook/synthetic.one | Bin 0 -> 20840 bytes .../boundary/cold/read/environment.json | 7 + .../boundary/cold/read/hierarchy.xml | 2 + .../boundary/cold/read/page-000.xml | 12 ++ .../boundary/cold/read/payloads.json | 3 + corpus/cross-run-edit/boundary/cold/run.json | 18 ++ .../cross-run-edit/boundary/cold/source.json | 8 + .../boundary/cold/teardown.json | 1 + .../clear/candidate/synthetic.one | Bin 0 -> 21736 bytes .../clear/cold/notebook/Open Notebook.onetoc2 | Bin 0 -> 4800 bytes .../clear/cold/notebook/synthetic.one | Bin 0 -> 21736 bytes .../clear/cold/read/environment.json | 7 + .../clear/cold/read/hierarchy.xml | 2 + .../clear/cold/read/page-000.xml | 7 + .../clear/cold/read/payloads.json | 3 + corpus/cross-run-edit/clear/cold/run.json | 18 ++ corpus/cross-run-edit/clear/cold/source.json | 8 + .../cross-run-edit/clear/cold/teardown.json | 1 + corpus/cross-run-edit/manifest.json | 89 +++++++++ .../partial/candidate/synthetic.one | Bin 0 -> 20944 bytes .../cold/notebook/Open Notebook.onetoc2 | Bin 0 -> 4800 bytes .../partial/cold/notebook/synthetic.one | Bin 0 -> 20944 bytes .../partial/cold/read/environment.json | 7 + .../partial/cold/read/hierarchy.xml | 2 + .../partial/cold/read/page-000.xml | 11 ++ .../partial/cold/read/payloads.json | 3 + corpus/cross-run-edit/partial/cold/run.json | 18 ++ .../cross-run-edit/partial/cold/source.json | 8 + .../cross-run-edit/partial/cold/teardown.json | 1 + .../replace/candidate/synthetic.one | Bin 0 -> 21520 bytes .../cold/notebook/Open Notebook.onetoc2 | Bin 0 -> 4800 bytes .../replace/cold/notebook/synthetic.one | Bin 0 -> 21992 bytes .../replace/cold/read/environment.json | 7 + .../replace/cold/read/hierarchy.xml | 2 + .../replace/cold/read/page-000.xml | 8 + .../replace/cold/read/payloads.json | 3 + corpus/cross-run-edit/replace/cold/run.json | 18 ++ .../cross-run-edit/replace/cold/source.json | 8 + .../cross-run-edit/replace/cold/teardown.json | 1 + .../retype/candidate/synthetic.one | Bin 0 -> 22832 bytes .../cold/notebook/Open Notebook.onetoc2 | Bin 0 -> 4800 bytes .../retype/cold/notebook/synthetic.one | Bin 0 -> 23288 bytes .../retype/cold/read/environment.json | 7 + .../retype/cold/read/hierarchy.xml | 2 + .../retype/cold/read/page-000.xml | 7 + .../retype/cold/read/payloads.json | 3 + corpus/cross-run-edit/retype/cold/run.json | 18 ++ corpus/cross-run-edit/retype/cold/source.json | 8 + .../cross-run-edit/retype/cold/teardown.json | 1 + .../table/candidate/synthetic.one | Bin 0 -> 30864 bytes .../table/cold/notebook/Open Notebook.onetoc2 | Bin 0 -> 4800 bytes .../table/cold/notebook/synthetic.one | Bin 0 -> 30864 bytes ...0b7c57eb3b6110a06aa2114e06d69a7.attachment | 1 + .../table/cold/read/environment.json | 7 + .../table/cold/read/hierarchy.xml | 2 + .../table/cold/read/page-000.xml | 11 ++ .../table/cold/read/payloads.json | 10 + corpus/cross-run-edit/table/cold/run.json | 18 ++ corpus/cross-run-edit/table/cold/source.json | 8 + .../cross-run-edit/table/cold/teardown.json | 1 + .../examples/smb_offline_client.rs | 29 ++- crates/onestore-offline/src/lib.rs | 1 + crates/onestore-offline/tests/sync.rs | 76 ++++++++ crates/onestore/README.md | 2 +- crates/onestore/src/edit.rs | 93 ++++++--- crates/onestore/src/write/tests.rs | 57 +++++- crates/onestore/tests/edit.rs | 180 +++++++++++++++++- fuzz/fuzz_targets/edit_text.rs | 20 +- tools/native_collaboration.py | 8 + tools/native_stress.py | 10 +- tools/offline_document_history.py | 62 ++++-- tools/offline_outage.py | 16 +- tools/test_cross_run_edit.py | 25 +++ tools/test_native_runner.py | 21 ++ tools/test_offline_confirmation.py | 4 +- tools/test_offline_document_history.py | 125 ++++++++---- tools/test_offline_outage.py | 2 +- tools/verify_offline.py | 4 +- tools/verify_offline_confirmation.py | 7 +- 82 files changed, 1018 insertions(+), 131 deletions(-) create mode 100644 corpus/cross-run-edit/README.md create mode 100644 corpus/cross-run-edit/boundary/candidate/synthetic.one create mode 100644 corpus/cross-run-edit/boundary/cold/notebook/Open Notebook.onetoc2 create mode 100644 corpus/cross-run-edit/boundary/cold/notebook/synthetic.one create mode 100644 corpus/cross-run-edit/boundary/cold/read/environment.json create mode 100644 corpus/cross-run-edit/boundary/cold/read/hierarchy.xml create mode 100644 corpus/cross-run-edit/boundary/cold/read/page-000.xml create mode 100644 corpus/cross-run-edit/boundary/cold/read/payloads.json create mode 100644 corpus/cross-run-edit/boundary/cold/run.json create mode 100644 corpus/cross-run-edit/boundary/cold/source.json create mode 100644 corpus/cross-run-edit/boundary/cold/teardown.json create mode 100644 corpus/cross-run-edit/clear/candidate/synthetic.one create mode 100644 corpus/cross-run-edit/clear/cold/notebook/Open Notebook.onetoc2 create mode 100644 corpus/cross-run-edit/clear/cold/notebook/synthetic.one create mode 100644 corpus/cross-run-edit/clear/cold/read/environment.json create mode 100644 corpus/cross-run-edit/clear/cold/read/hierarchy.xml create mode 100644 corpus/cross-run-edit/clear/cold/read/page-000.xml create mode 100644 corpus/cross-run-edit/clear/cold/read/payloads.json create mode 100644 corpus/cross-run-edit/clear/cold/run.json create mode 100644 corpus/cross-run-edit/clear/cold/source.json create mode 100644 corpus/cross-run-edit/clear/cold/teardown.json create mode 100644 corpus/cross-run-edit/manifest.json create mode 100644 corpus/cross-run-edit/partial/candidate/synthetic.one create mode 100644 corpus/cross-run-edit/partial/cold/notebook/Open Notebook.onetoc2 create mode 100644 corpus/cross-run-edit/partial/cold/notebook/synthetic.one create mode 100644 corpus/cross-run-edit/partial/cold/read/environment.json create mode 100644 corpus/cross-run-edit/partial/cold/read/hierarchy.xml create mode 100644 corpus/cross-run-edit/partial/cold/read/page-000.xml create mode 100644 corpus/cross-run-edit/partial/cold/read/payloads.json create mode 100644 corpus/cross-run-edit/partial/cold/run.json create mode 100644 corpus/cross-run-edit/partial/cold/source.json create mode 100644 corpus/cross-run-edit/partial/cold/teardown.json create mode 100644 corpus/cross-run-edit/replace/candidate/synthetic.one create mode 100644 corpus/cross-run-edit/replace/cold/notebook/Open Notebook.onetoc2 create mode 100644 corpus/cross-run-edit/replace/cold/notebook/synthetic.one create mode 100644 corpus/cross-run-edit/replace/cold/read/environment.json create mode 100644 corpus/cross-run-edit/replace/cold/read/hierarchy.xml create mode 100644 corpus/cross-run-edit/replace/cold/read/page-000.xml create mode 100644 corpus/cross-run-edit/replace/cold/read/payloads.json create mode 100644 corpus/cross-run-edit/replace/cold/run.json create mode 100644 corpus/cross-run-edit/replace/cold/source.json create mode 100644 corpus/cross-run-edit/replace/cold/teardown.json create mode 100644 corpus/cross-run-edit/retype/candidate/synthetic.one create mode 100644 corpus/cross-run-edit/retype/cold/notebook/Open Notebook.onetoc2 create mode 100644 corpus/cross-run-edit/retype/cold/notebook/synthetic.one create mode 100644 corpus/cross-run-edit/retype/cold/read/environment.json create mode 100644 corpus/cross-run-edit/retype/cold/read/hierarchy.xml create mode 100644 corpus/cross-run-edit/retype/cold/read/page-000.xml create mode 100644 corpus/cross-run-edit/retype/cold/read/payloads.json create mode 100644 corpus/cross-run-edit/retype/cold/run.json create mode 100644 corpus/cross-run-edit/retype/cold/source.json create mode 100644 corpus/cross-run-edit/retype/cold/teardown.json create mode 100644 corpus/cross-run-edit/table/candidate/synthetic.one create mode 100644 corpus/cross-run-edit/table/cold/notebook/Open Notebook.onetoc2 create mode 100644 corpus/cross-run-edit/table/cold/notebook/synthetic.one create mode 120000 corpus/cross-run-edit/table/cold/read/af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7.attachment create mode 100644 corpus/cross-run-edit/table/cold/read/environment.json create mode 100644 corpus/cross-run-edit/table/cold/read/hierarchy.xml create mode 100644 corpus/cross-run-edit/table/cold/read/page-000.xml create mode 100644 corpus/cross-run-edit/table/cold/read/payloads.json create mode 100644 corpus/cross-run-edit/table/cold/run.json create mode 100644 corpus/cross-run-edit/table/cold/source.json create mode 100644 corpus/cross-run-edit/table/cold/teardown.json create mode 100644 tools/test_cross_run_edit.py diff --git a/corpus/cross-run-edit/README.md b/corpus/cross-run-edit/README.md new file mode 100644 index 0000000000000000000000000000000000000000..9416ce06cbe418ecd8feba1b81b25e9a34ecd14b --- /dev/null +++ b/corpus/cross-run-edit/README.md @@ -0,0 +1,20 @@ +# Cross-run text replacements + +Six edits of the public formatted-insertion notebooks, independently opened by +OneNote 2010 in disposable cold-cache machines. Cases cover partial and complete +replacement, a native table cell, deletion, typing after deletion, and insertion +at a style boundary. Each directory is a separate notebook because the source +section identities can be shared. + +The inserted characters inherit the style at the start of the replaced range; +surviving characters retain their styles. Empty final runs retain their typing +style. These are library edit semantics; the native captures check that OneNote +reads the resulting content and formatting correctly. + +Generate candidates with the ignored `export_native_cross_run_edits` test and +`ONESTORE_CROSS_RUN_OUTPUT` set to a new absolute directory. Capture each candidate +with `tools/native_runner.py --expected-pages 1 --collect-notebook` and compare +with `tools/verify-document.py`. The manifest records source paths, target IDs, +UTF-16 ranges and file hashes. The attachment export links to its canonical copy. +`tools/test_cross_run_edit.py` checks the retained +native oracles without starting a VM. diff --git a/corpus/cross-run-edit/boundary/candidate/synthetic.one b/corpus/cross-run-edit/boundary/candidate/synthetic.one new file mode 100644 index 0000000000000000000000000000000000000000..691ee25017fc105684e7387918db68cd7d431f66 GIT binary patch literal 20840 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 literal 0 HcmV?d00001 diff --git a/corpus/cross-run-edit/boundary/cold/notebook/Open Notebook.onetoc2 b/corpus/cross-run-edit/boundary/cold/notebook/Open Notebook.onetoc2 new file mode 100644 index 0000000000000000000000000000000000000000..8160c30c74cfcf14ea7d5a9a2b6395bff217e273 GIT binary patch literal 4800 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 + diff --git a/corpus/cross-run-edit/boundary/cold/read/page-000.xml b/corpus/cross-run-edit/boundary/cold/read/page-000.xml new file mode 100644 index 0000000000000000000000000000000000000000..bb42447556229173f03bedfd67385398e8845ea4 --- /dev/null +++ b/corpus/cross-run-edit/boundary/cold/read/page-000.xml @@ -0,0 +1,12 @@ + +BoldMiddleitalic é color 東京
+End]]>
Fictitious: café, 東京, مرحبا]]>
diff --git a/corpus/cross-run-edit/boundary/cold/read/payloads.json b/corpus/cross-run-edit/boundary/cold/read/payloads.json new file mode 100644 index 0000000000000000000000000000000000000000..1ce91a413d6fff4d58765187b76756f0e97ae843 --- /dev/null +++ b/corpus/cross-run-edit/boundary/cold/read/payloads.json @@ -0,0 +1,3 @@ +[ + +] \ No newline at end of file diff --git a/corpus/cross-run-edit/boundary/cold/run.json b/corpus/cross-run-edit/boundary/cold/run.json new file mode 100644 index 0000000000000000000000000000000000000000..239ffeebea458eaa341e83c9bab1788516432c4c --- /dev/null +++ b/corpus/cross-run-edit/boundary/cold/run.json @@ -0,0 +1,18 @@ +{ + "notebook": "/Users/clo/dev/one/evidence/m10/cross-run-native/boundary/candidate", + "expected_pages": 1, + "author": null, + "author_timeout_seconds": 600, + "inspect": false, + "collect_notebook": true, + "base": { + "file": "win7-office-base.qcow2", + "format": "qcow2", + "sha256": "a1a4f8fab782ee14885ff801ca2f6347c208fdcfc3513637096f314315c89346", + "virtual_size": 68719476736 + }, + "scripts": { + "cold.ps1": "c177fc72ae6c2634d186f5671a880de20b09f9716532c37c69cb13aa15c7e331", + "read.ps1": "04013bfcccee40a17e2a225f9b9e96f40a3a8daad9e350609654f8eda3ccbb41" + } +} diff --git a/corpus/cross-run-edit/boundary/cold/source.json b/corpus/cross-run-edit/boundary/cold/source.json new file mode 100644 index 0000000000000000000000000000000000000000..506c76e818d92557ac2876ed4ec5b68e94bd9179 --- /dev/null +++ b/corpus/cross-run-edit/boundary/cold/source.json @@ -0,0 +1,8 @@ +[ + { + "path": "synthetic.one", + "bytes": 20840, + "sha256": "a3fac402a10648e99d4bdb4b37b41c825e2b8b34f12d8b1c234e28bd2ec8ac0b", + "mtime_ns": 1788850928468116429 + } +] diff --git a/corpus/cross-run-edit/boundary/cold/teardown.json b/corpus/cross-run-edit/boundary/cold/teardown.json new file mode 100644 index 0000000000000000000000000000000000000000..05a47793de40f322e745c4c4183727e3590ecf70 --- /dev/null +++ b/corpus/cross-run-edit/boundary/cold/teardown.json @@ -0,0 +1 @@ +{"absent": true} diff --git a/corpus/cross-run-edit/clear/candidate/synthetic.one b/corpus/cross-run-edit/clear/candidate/synthetic.one new file mode 100644 index 0000000000000000000000000000000000000000..d5cf382755381ecda3103a0e2c9c30da5ee9aa5b GIT binary patch literal 21736 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 ITWP=kAEqbvu>b%7 literal 0 HcmV?d00001 diff --git a/corpus/cross-run-edit/clear/cold/read/environment.json b/corpus/cross-run-edit/clear/cold/read/environment.json new file mode 100644 index 0000000000000000000000000000000000000000..ea3d73a65d3460e97244fb197908f3f4328ba3c5 --- /dev/null +++ b/corpus/cross-run-edit/clear/cold/read/environment.json @@ -0,0 +1,7 @@ +{ + "powershell": "5.1.14409.1005", + "schema": "xs2010", + "hostname": "ONE-M6-8787CA35", + "cold": true, + "onenote": "14.0.4763.1000" +} diff --git a/corpus/cross-run-edit/clear/cold/read/hierarchy.xml b/corpus/cross-run-edit/clear/cold/read/hierarchy.xml new file mode 100644 index 0000000000000000000000000000000000000000..5e241ed7e4d574a67084499fb2ba7678f64f9fd5 --- /dev/null +++ b/corpus/cross-run-edit/clear/cold/read/hierarchy.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/cross-run-edit/clear/cold/read/page-000.xml b/corpus/cross-run-edit/clear/cold/read/page-000.xml new file mode 100644 index 0000000000000000000000000000000000000000..8ed89f3b47df6d65f933485f0391b80386d21b66 --- /dev/null +++ b/corpus/cross-run-edit/clear/cold/read/page-000.xml @@ -0,0 +1,7 @@ + +Fictitious: café, 東京, مرحبا]]> diff --git a/corpus/cross-run-edit/clear/cold/read/payloads.json b/corpus/cross-run-edit/clear/cold/read/payloads.json new file mode 100644 index 0000000000000000000000000000000000000000..1ce91a413d6fff4d58765187b76756f0e97ae843 --- /dev/null +++ b/corpus/cross-run-edit/clear/cold/read/payloads.json @@ -0,0 +1,3 @@ +[ + +] \ No newline at end of file diff --git a/corpus/cross-run-edit/clear/cold/run.json b/corpus/cross-run-edit/clear/cold/run.json new file mode 100644 index 0000000000000000000000000000000000000000..c8cc9eeff1030cfbe54016e5992f9e22e2cfc83b --- /dev/null +++ b/corpus/cross-run-edit/clear/cold/run.json @@ -0,0 +1,18 @@ +{ + "notebook": "/Users/clo/dev/one/evidence/m10/cross-run-native/clear/candidate", + "expected_pages": 1, + "author": null, + "author_timeout_seconds": 600, + "inspect": false, + "collect_notebook": true, + "base": { + "file": "win7-office-base.qcow2", + "format": "qcow2", + "sha256": "a1a4f8fab782ee14885ff801ca2f6347c208fdcfc3513637096f314315c89346", + "virtual_size": 68719476736 + }, + "scripts": { + "cold.ps1": "c177fc72ae6c2634d186f5671a880de20b09f9716532c37c69cb13aa15c7e331", + "read.ps1": "04013bfcccee40a17e2a225f9b9e96f40a3a8daad9e350609654f8eda3ccbb41" + } +} diff --git a/corpus/cross-run-edit/clear/cold/source.json b/corpus/cross-run-edit/clear/cold/source.json new file mode 100644 index 0000000000000000000000000000000000000000..36480de8ddeff6fffa27089867389edda10a8aa3 --- /dev/null +++ b/corpus/cross-run-edit/clear/cold/source.json @@ -0,0 +1,8 @@ +[ + { + "path": "synthetic.one", + "bytes": 21736, + "sha256": "d6d8c775b4b87e3b8379757773c313a0a720da52dd5e76b4799f2b8e16bb5320", + "mtime_ns": 1788850928454071185 + } +] diff --git a/corpus/cross-run-edit/clear/cold/teardown.json b/corpus/cross-run-edit/clear/cold/teardown.json new file mode 100644 index 0000000000000000000000000000000000000000..05a47793de40f322e745c4c4183727e3590ecf70 --- /dev/null +++ b/corpus/cross-run-edit/clear/cold/teardown.json @@ -0,0 +1 @@ +{"absent": true} diff --git a/corpus/cross-run-edit/manifest.json b/corpus/cross-run-edit/manifest.json new file mode 100644 index 0000000000000000000000000000000000000000..ed9823f91ae5c34507bd55d7581dd8c70e4e7c6f --- /dev/null +++ b/corpus/cross-run-edit/manifest.json @@ -0,0 +1,89 @@ +{ + "generator": "crates/onestore/tests/edit.rs:export_native_cross_run_edits", + "cases": [ + { + "name": "partial", + "object": "{04B13EBF-F611-4402-829D-C72E23221C8B},2", + "range": { + "end": 16, + "start": 2 + }, + "replacement": "\u4e2d\ud83e\udd80", + "retype": false, + "source": "formatted-insertion/native-paragraph/candidate/synthetic.one", + "space": "{64183EF4-4E19-0CBE-3D3B-88FF17D58B1C},1", + "candidate_sha256": "0a93fdb8c8378989a6522b5de803f073109b8972088e61afdf97639eeaea6a02", + "native_sha256": "85e90850f7f0582cd9e4e8f93bb8f77cd773d883cd993b24280b57bd70078a4f" + }, + { + "name": "table", + "object": "{6928B2F5-7D36-44AF-BE3B-36CEBACE8657},2", + "range": { + "end": 24, + "start": 0 + }, + "replacement": "Across \ud83d\udc08 ", + "retype": false, + "source": "formatted-insertion/native-cell/candidate/synthetic.one", + "space": "{64183EF4-4E19-0CBE-3D3B-88FF17D58B1C},1", + "candidate_sha256": "89ce5a50f050c05ecbfb92c8c8eb0538bd041d0596290a5b974a9878e171e6e9", + "native_sha256": "561222f0ecb1ccc17592c9fd44f08f6f4e0b0bfc6120f3b099a902f46c3e8ed2" + }, + { + "name": "clear", + "object": "{04B13EBF-F611-4402-829D-C72E23221C8B},2", + "range": { + "end": 30, + "start": 0 + }, + "replacement": "", + "retype": false, + "source": "formatted-insertion/native-paragraph/candidate/synthetic.one", + "space": "{64183EF4-4E19-0CBE-3D3B-88FF17D58B1C},1", + "candidate_sha256": "d6d8c775b4b87e3b8379757773c313a0a720da52dd5e76b4799f2b8e16bb5320", + "native_sha256": "30dfd26ad6912225f3e637f529f5135f67f45de0c1a0b705ad70bf97866a554e" + }, + { + "name": "replace", + "object": "{04B13EBF-F611-4402-829D-C72E23221C8B},2", + "range": { + "end": 30, + "start": 0 + }, + "replacement": "Replacement e\u0301\ud83e\udd80", + "retype": false, + "source": "formatted-insertion/native-paragraph/candidate/synthetic.one", + "space": "{64183EF4-4E19-0CBE-3D3B-88FF17D58B1C},1", + "candidate_sha256": "4b0ae05431ade4777541592cd8040f7ff18dbfe4ada35a686cda963299988e81", + "native_sha256": "f0cca32ec56e15b7d19aa581dbe7ae3d59bd0cc498450a0576ae9d168365c377" + }, + { + "name": "retype", + "object": "{04B13EBF-F611-4402-829D-C72E23221C8B},2", + "range": { + "end": 30, + "start": 0 + }, + "replacement": "", + "retype": true, + "source": "formatted-insertion/native-paragraph/candidate/synthetic.one", + "space": "{64183EF4-4E19-0CBE-3D3B-88FF17D58B1C},1", + "candidate_sha256": "0d37a5a762e9b08f40ea47b2fad48591fcb5f782d1a130f5363f5116b1175fb3", + "native_sha256": "7b70f0ebe27f09c31a623f27b37ffac4362f9513dd26b2d1a69d1e2c0a38c15a" + }, + { + "name": "boundary", + "object": "{04B13EBF-F611-4402-829D-C72E23221C8B},2", + "range": { + "end": 8, + "start": 4 + }, + "replacement": "Middle", + "retype": false, + "source": "formatted-insertion/native-paragraph/candidate/synthetic.one", + "space": "{64183EF4-4E19-0CBE-3D3B-88FF17D58B1C},1", + "candidate_sha256": "a3fac402a10648e99d4bdb4b37b41c825e2b8b34f12d8b1c234e28bd2ec8ac0b", + "native_sha256": "e9852bed7545600f4fec537be8e614a1cdb4ec8d7ee40c7933a609bd51a7ce8b" + } + ] +} diff --git a/corpus/cross-run-edit/partial/candidate/synthetic.one b/corpus/cross-run-edit/partial/candidate/synthetic.one new file mode 100644 index 0000000000000000000000000000000000000000..634ae10b5cd74fa0f7399f588d6a1b3a93224abe GIT binary patch literal 20944 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 literal 0 HcmV?d00001 diff --git a/corpus/cross-run-edit/partial/cold/notebook/Open Notebook.onetoc2 b/corpus/cross-run-edit/partial/cold/notebook/Open Notebook.onetoc2 new file mode 100644 index 0000000000000000000000000000000000000000..9005a0116a8b1baa8daccb2f7fb2e66a71a5d80c GIT binary patch literal 4800 zcmZ3e|KIuM`!b(-j|5(YE0x7JuPAuF@xQ$5WL}?;cUqsD7|?+I-B|+C6Tf;cy&yjE z?TK|aq=E9t7^eI`5P;aMFaanH6Gsz9;eynF09cR%D#Ze%K^O#pG(-hN%>f~ZFr0L9 z`lNk2&}XsiZRM%wo{DfoC2B73pRniKUH{#0H{UsXdd@PqdMu1BnhW`nF%~7GLZcxt z{m^Ho9*;erCD2_Oic>KxvkQh6ZfnLp}^f zJun&qBOwHk>rC%m#!ZJs4|T)xJt%`;p2`#e;xhrUAP|GZKsdc~mM@485(ibaFmapY zdy7GQE~uDkwn#FFF9PMK=y&-Xf4Eo$ z=sm&62rGesWphgkLl3e?Kqi8~`bdL@2ADb-pppLt7#hGbL=y-2ph{U77-01WP^1AW z+Q7mvMV3KkrwoHgHjv&2q(QZb#;(`jr|3TMUuOH+ntO3%4anItKwQjF$&klT!jQp` z3M4Zbk{R?E@_{0$Ksk^HfD{k_RfAp4#PCgqK?P(cNdK03_rS6Y49M{Vk^#lR=QD{n z=f3^tzbD*gh2PF+??K|&;zfZY4_6%2Y=3Vq{-nyYhoSO_;gikp*++^{gm6*o1S48K6}0n|MTMnhmU N1aO7`adVF$CZftb3t#! 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 + diff --git a/corpus/cross-run-edit/partial/cold/read/page-000.xml b/corpus/cross-run-edit/partial/cold/read/page-000.xml new file mode 100644 index 0000000000000000000000000000000000000000..0ebb7adfd9558b764ad4286e856f0dc975681063 --- /dev/null +++ b/corpus/cross-run-edit/partial/cold/read/page-000.xml @@ -0,0 +1,11 @@ + +Bo中🦀́ color 東京
+End]]>
Fictitious: café, 東京, مرحبا]]>
diff --git a/corpus/cross-run-edit/partial/cold/read/payloads.json b/corpus/cross-run-edit/partial/cold/read/payloads.json new file mode 100644 index 0000000000000000000000000000000000000000..1ce91a413d6fff4d58765187b76756f0e97ae843 --- /dev/null +++ b/corpus/cross-run-edit/partial/cold/read/payloads.json @@ -0,0 +1,3 @@ +[ + +] \ No newline at end of file diff --git a/corpus/cross-run-edit/partial/cold/run.json b/corpus/cross-run-edit/partial/cold/run.json new file mode 100644 index 0000000000000000000000000000000000000000..89a36f65838e777e1877117e08ace2d77625b9e7 --- /dev/null +++ b/corpus/cross-run-edit/partial/cold/run.json @@ -0,0 +1,18 @@ +{ + "notebook": "/Users/clo/dev/one/evidence/m10/cross-run-native/partial/candidate", + "expected_pages": 1, + "author": null, + "author_timeout_seconds": 600, + "inspect": false, + "collect_notebook": true, + "base": { + "file": "win7-office-base.qcow2", + "format": "qcow2", + "sha256": "a1a4f8fab782ee14885ff801ca2f6347c208fdcfc3513637096f314315c89346", + "virtual_size": 68719476736 + }, + "scripts": { + "cold.ps1": "c177fc72ae6c2634d186f5671a880de20b09f9716532c37c69cb13aa15c7e331", + "read.ps1": "04013bfcccee40a17e2a225f9b9e96f40a3a8daad9e350609654f8eda3ccbb41" + } +} diff --git a/corpus/cross-run-edit/partial/cold/source.json b/corpus/cross-run-edit/partial/cold/source.json new file mode 100644 index 0000000000000000000000000000000000000000..a0bfe3927b4f56ee31934bbc3a88f33d30378b8a --- /dev/null +++ b/corpus/cross-run-edit/partial/cold/source.json @@ -0,0 +1,8 @@ +[ + { + "path": "synthetic.one", + "bytes": 20944, + "sha256": "0a93fdb8c8378989a6522b5de803f073109b8972088e61afdf97639eeaea6a02", + "mtime_ns": 1788850928443537242 + } +] diff --git a/corpus/cross-run-edit/partial/cold/teardown.json b/corpus/cross-run-edit/partial/cold/teardown.json new file mode 100644 index 0000000000000000000000000000000000000000..05a47793de40f322e745c4c4183727e3590ecf70 --- /dev/null +++ b/corpus/cross-run-edit/partial/cold/teardown.json @@ -0,0 +1 @@ +{"absent": true} diff --git a/corpus/cross-run-edit/replace/candidate/synthetic.one b/corpus/cross-run-edit/replace/candidate/synthetic.one new file mode 100644 index 0000000000000000000000000000000000000000..7e69f1c7b7f287cd7252c42b39e5a37f727725d1 GIT binary patch literal 21520 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 T4}7e&3M}zU_*mdp+Gqa{sJgUE literal 0 HcmV?d00001 diff --git a/corpus/cross-run-edit/replace/cold/notebook/Open Notebook.onetoc2 b/corpus/cross-run-edit/replace/cold/notebook/Open Notebook.onetoc2 new file mode 100644 index 0000000000000000000000000000000000000000..5dfb21929e5e62ff43a56a406938e2a5218a3f3f GIT binary patch literal 4800 zcmZ3e|KIuM`!b(-j|5(YE0x8+TACy)Q+L*N;|f{s^*@T67|?+I-B|+C6Tf;cy&yjE z?TK|aq=E9t7^eI`5P;aMFaanH6Gsz9;eynF09cR%D#Ze%K^O#pG(-hN%>f~ZFpOlq zqm&*QBjCMd*#!ZYl`^s1P>FTFUt4EQZgkwa{pgB=+Ph!D)MKN!XfEVO#@N)1ij9WA zP!EA4U%r3cSQWNJ0O}Er9EPb4j4EfU4Xsb*GWt%~u72ar?~AI8KxvkQh6ZfnLp=ya z-9H)vBO(Nl>rC%m#!ZJs4|T)xJt%`;p2`#e;xhrUAP|GZKsdc~mM@485(ibaFmapY zdy7GQE~uDkwn#FFF9PMK=yPq40@2#<4;{Uj;i1HSavdL^1QwUuQdc38hU_oKW8*j z9t^!F7#U$DFtBWHNnz+g_6W#C5Lh2+(9i%=Cj&I{zW_r6ScYig03TE-3j+hJ{s4+J zfJA|g0h`Xm@J)t6WuFX#2*`O`=G_BHLO=rx!xUKtnVmqnY#B2WxR{}mA&;SiA%h_mNMY^&P}k^{xZSw?PHI0H2}8Zh*pWMtt2%CcgOn;K1qU!eE^>K+B7 QAut*OI70xrSc8>608$1-0RR91 literal 0 HcmV?d00001 diff --git a/corpus/cross-run-edit/replace/cold/notebook/synthetic.one b/corpus/cross-run-edit/replace/cold/notebook/synthetic.one new file mode 100644 index 0000000000000000000000000000000000000000..e5feb87ed555ad3e1013636bef200930289be1cc GIT binary patch literal 21992 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 + diff --git a/corpus/cross-run-edit/replace/cold/read/page-000.xml b/corpus/cross-run-edit/replace/cold/read/page-000.xml new file mode 100644 index 0000000000000000000000000000000000000000..f10dedcdb2b7f695de065e52fd0bf6d6462c313e --- /dev/null +++ b/corpus/cross-run-edit/replace/cold/read/page-000.xml @@ -0,0 +1,8 @@ + +Replacement é🦀]]>Fictitious: café, 東京, مرحبا]]> diff --git a/corpus/cross-run-edit/replace/cold/read/payloads.json b/corpus/cross-run-edit/replace/cold/read/payloads.json new file mode 100644 index 0000000000000000000000000000000000000000..1ce91a413d6fff4d58765187b76756f0e97ae843 --- /dev/null +++ b/corpus/cross-run-edit/replace/cold/read/payloads.json @@ -0,0 +1,3 @@ +[ + +] \ No newline at end of file diff --git a/corpus/cross-run-edit/replace/cold/run.json b/corpus/cross-run-edit/replace/cold/run.json new file mode 100644 index 0000000000000000000000000000000000000000..a421791cbb1827517874dbde77bcf45634e2fc77 --- /dev/null +++ b/corpus/cross-run-edit/replace/cold/run.json @@ -0,0 +1,18 @@ +{ + "notebook": "/Users/clo/dev/one/evidence/m10/cross-run-native/replace/candidate", + "expected_pages": 1, + "author": null, + "author_timeout_seconds": 600, + "inspect": false, + "collect_notebook": true, + "base": { + "file": "win7-office-base.qcow2", + "format": "qcow2", + "sha256": "a1a4f8fab782ee14885ff801ca2f6347c208fdcfc3513637096f314315c89346", + "virtual_size": 68719476736 + }, + "scripts": { + "cold.ps1": "c177fc72ae6c2634d186f5671a880de20b09f9716532c37c69cb13aa15c7e331", + "read.ps1": "04013bfcccee40a17e2a225f9b9e96f40a3a8daad9e350609654f8eda3ccbb41" + } +} diff --git a/corpus/cross-run-edit/replace/cold/source.json b/corpus/cross-run-edit/replace/cold/source.json new file mode 100644 index 0000000000000000000000000000000000000000..23bf8c03feb0025d2fa10ba70220792b69fc3329 --- /dev/null +++ b/corpus/cross-run-edit/replace/cold/source.json @@ -0,0 +1,8 @@ +[ + { + "path": "synthetic.one", + "bytes": 21520, + "sha256": "4b0ae05431ade4777541592cd8040f7ff18dbfe4ada35a686cda963299988e81", + "mtime_ns": 1788850928458083445 + } +] diff --git a/corpus/cross-run-edit/replace/cold/teardown.json b/corpus/cross-run-edit/replace/cold/teardown.json new file mode 100644 index 0000000000000000000000000000000000000000..05a47793de40f322e745c4c4183727e3590ecf70 --- /dev/null +++ b/corpus/cross-run-edit/replace/cold/teardown.json @@ -0,0 +1 @@ +{"absent": true} diff --git a/corpus/cross-run-edit/retype/candidate/synthetic.one b/corpus/cross-run-edit/retype/candidate/synthetic.one new file mode 100644 index 0000000000000000000000000000000000000000..a7609c4d20b05283165885d231e8f6227ecb6c1e GIT binary patch literal 22832 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 SFd71*A%H6ckc%}~`2zq&$~mh5 literal 0 HcmV?d00001 diff --git a/corpus/cross-run-edit/retype/cold/notebook/synthetic.one b/corpus/cross-run-edit/retype/cold/notebook/synthetic.one new file mode 100644 index 0000000000000000000000000000000000000000..a129ad0badf711baaa5cc994d50bb0ba548da80d GIT binary patch literal 23288 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 literal 0 HcmV?d00001 diff --git a/corpus/cross-run-edit/retype/cold/read/environment.json b/corpus/cross-run-edit/retype/cold/read/environment.json new file mode 100644 index 0000000000000000000000000000000000000000..ef9d31dc2d371df8cecde82f35d9bfe5e22d0cba --- /dev/null +++ b/corpus/cross-run-edit/retype/cold/read/environment.json @@ -0,0 +1,7 @@ +{ + "powershell": "5.1.14409.1005", + "schema": "xs2010", + "hostname": "ONE-M6-71FBAE77", + "cold": true, + "onenote": "14.0.4763.1000" +} diff --git a/corpus/cross-run-edit/retype/cold/read/hierarchy.xml b/corpus/cross-run-edit/retype/cold/read/hierarchy.xml new file mode 100644 index 0000000000000000000000000000000000000000..c7886e9da3bc4f99de8cad43afd6884bd9b9ca8d --- /dev/null +++ b/corpus/cross-run-edit/retype/cold/read/hierarchy.xml @@ -0,0 +1,2 @@ + + diff --git a/corpus/cross-run-edit/retype/cold/read/page-000.xml b/corpus/cross-run-edit/retype/cold/read/page-000.xml new file mode 100644 index 0000000000000000000000000000000000000000..6da713b37f3e48d05bac1420d982c374726fbfe6 --- /dev/null +++ b/corpus/cross-run-edit/retype/cold/read/page-000.xml @@ -0,0 +1,7 @@ + +Fictitious: café, 東京, مرحبا]]> diff --git a/corpus/cross-run-edit/retype/cold/read/payloads.json b/corpus/cross-run-edit/retype/cold/read/payloads.json new file mode 100644 index 0000000000000000000000000000000000000000..1ce91a413d6fff4d58765187b76756f0e97ae843 --- /dev/null +++ b/corpus/cross-run-edit/retype/cold/read/payloads.json @@ -0,0 +1,3 @@ +[ + +] \ No newline at end of file diff --git a/corpus/cross-run-edit/retype/cold/run.json b/corpus/cross-run-edit/retype/cold/run.json new file mode 100644 index 0000000000000000000000000000000000000000..0228b61c79988c46951bd8896478e47234b8fd31 --- /dev/null +++ b/corpus/cross-run-edit/retype/cold/run.json @@ -0,0 +1,18 @@ +{ + "notebook": "/Users/clo/dev/one/evidence/m10/cross-run-native/retype/candidate", + "expected_pages": 1, + "author": null, + "author_timeout_seconds": 600, + "inspect": false, + "collect_notebook": true, + "base": { + "file": "win7-office-base.qcow2", + "format": "qcow2", + "sha256": "a1a4f8fab782ee14885ff801ca2f6347c208fdcfc3513637096f314315c89346", + "virtual_size": 68719476736 + }, + "scripts": { + "cold.ps1": "c177fc72ae6c2634d186f5671a880de20b09f9716532c37c69cb13aa15c7e331", + "read.ps1": "04013bfcccee40a17e2a225f9b9e96f40a3a8daad9e350609654f8eda3ccbb41" + } +} diff --git a/corpus/cross-run-edit/retype/cold/source.json b/corpus/cross-run-edit/retype/cold/source.json new file mode 100644 index 0000000000000000000000000000000000000000..385dde5d64479ba4d46aed9eca7d995d50aa64ce --- /dev/null +++ b/corpus/cross-run-edit/retype/cold/source.json @@ -0,0 +1,8 @@ +[ + { + "path": "synthetic.one", + "bytes": 22832, + "sha256": "0d37a5a762e9b08f40ea47b2fad48591fcb5f782d1a130f5363f5116b1175fb3", + "mtime_ns": 1788850928464340419 + } +] diff --git a/corpus/cross-run-edit/retype/cold/teardown.json b/corpus/cross-run-edit/retype/cold/teardown.json new file mode 100644 index 0000000000000000000000000000000000000000..05a47793de40f322e745c4c4183727e3590ecf70 --- /dev/null +++ b/corpus/cross-run-edit/retype/cold/teardown.json @@ -0,0 +1 @@ +{"absent": true} diff --git a/corpus/cross-run-edit/table/candidate/synthetic.one b/corpus/cross-run-edit/table/candidate/synthetic.one new file mode 100644 index 0000000000000000000000000000000000000000..eb64f3a47ae33ebdfe1a12bea13b8972e2e3c863 GIT binary patch literal 30864 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
dEA#JUzJdc}asW)@bej)9qt32O-g?a^|ypT3} rUHNP3@_AHVNDJsHkN@!k`5PusK??92gjR{J^5{QsKU-LEAOHUWC)kFC literal 0 HcmV?d00001 diff --git a/corpus/cross-run-edit/table/cold/notebook/Open Notebook.onetoc2 b/corpus/cross-run-edit/table/cold/notebook/Open Notebook.onetoc2 new file mode 100644 index 0000000000000000000000000000000000000000..b834c5577784a50913c37ff1a6b39a834828057c GIT binary patch literal 4800 zcmZ3e|KIuM`!b(-j|5(YE0x7Z-DjU4_Gp>k9xjK>s7uMW7|?+I-B|+C6Tf;cy&yjE z?TK|aq=E9t7^eI`5P;aMFaanH6Gsz9;eynF09cR%D#Ze%K^O#pG(-hN%>f~ZFpS(} zb=l%{#d(Kiy|EtK4tsiVLnWSu-g(-XxY)V>LPh1pmT_1NewnhW`nF*Y@$Vxu82 z)I;FNm+xOUR)sAQfO>=@hhb_1qsqph&3euXO71h&y7$g8XxCu`O0y(1G++}S>OnB- z{?QN^5g~wFXL|24ZaOS_s2i5=K^gq=RHgtBp9zQsffytP!s(r}d_jDWIH;tcu$;BEv-zi4RKwS~7T>$jov8X>LLj0HJmpt2GhP*@ymn_z#J5hAZB z($K)50t|YPQof1Rhv(dhcHeOE_$q@Ct5q5T4Gld&IqvCPUS>`VUfo)A*jxKVUV^mh zdNA~!U}S`qz`(M(C552}*&`qmL12BPK|=#foea>({{jpRU>Tx`1AI`WEDQ{=`U5D^ z02OUuVVEMzAhT13K_nYU?*r1H+C*r@Oe5x1|D3j6Zi@S+tMDG=Y#AUfW~gMyV<=(B zU`Pd$nGDGcdJOqMkyM}@$OAwM2!N`=u4ZERCc~ftG83eK%e;GFSq28=_yNg);$Y>D zOV2l^t#?`YG3)Z|=eajQ;@IM)VaqgJaWF$5J7dM{FqgSCny>mD9{y7W1|G;pkWsMs z*sj2%x@`l8!?whrtCjoH6hU&11`NGd7+JW0Y*tX*oMhyNg*mRcsnKNk1&R-#?olur Q0;3^-GX#)}HCXur03!W5R{#J2 literal 0 HcmV?d00001 diff --git a/corpus/cross-run-edit/table/cold/notebook/synthetic.one b/corpus/cross-run-edit/table/cold/notebook/synthetic.one new file mode 100644 index 0000000000000000000000000000000000000000..4b3593c4aaaa51de2427c32348f225bd7240641f GIT binary patch literal 30864 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 s8@;OhHC6e1Dleo3bd|^dc!B&4lc^vD_zgm + diff --git a/corpus/cross-run-edit/table/cold/read/page-000.xml b/corpus/cross-run-edit/table/cold/read/page-000.xml new file mode 100644 index 0000000000000000000000000000000000000000..3c00e0c6e019ff620d2285cc587529403b93940d --- /dev/null +++ b/corpus/cross-run-edit/table/cold/read/page-000.xml @@ -0,0 +1,11 @@ + +Fictitious: café, 東京, مرحبا]]>iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9Y9J1uoA +AAAASUVORK5CYII=Across 🐈 東京
+End]]>
diff --git a/corpus/cross-run-edit/table/cold/read/payloads.json b/corpus/cross-run-edit/table/cold/read/payloads.json new file mode 100644 index 0000000000000000000000000000000000000000..4ca75b134c6ab5f611d249014f04264be3699cf2 --- /dev/null +++ b/corpus/cross-run-edit/table/cold/read/payloads.json @@ -0,0 +1,10 @@ +[ + { + "sha256": "af1af4b8987109ce1e335e538910fb3860b7c57eb3b6110a06aa2114e06d69a7", + "name": "fictitious-attachment.txt", + "object": "{D063C31D-7E07-4993-9344-99F205C533AF}{11}{B0}", + "kind": "InsertedFile", + "page": "{592CC75B-A6C5-4A10-95DD-5BAB86E9B53A}{1}{B0}", + "bytes": 43 + } +] \ No newline at end of file diff --git a/corpus/cross-run-edit/table/cold/run.json b/corpus/cross-run-edit/table/cold/run.json new file mode 100644 index 0000000000000000000000000000000000000000..1936f13f905e4710b3cdcebfa70e63d9623473ba --- /dev/null +++ b/corpus/cross-run-edit/table/cold/run.json @@ -0,0 +1,18 @@ +{ + "notebook": "/Users/clo/dev/one/evidence/m10/cross-run-native/table/candidate", + "expected_pages": 1, + "author": null, + "author_timeout_seconds": 600, + "inspect": false, + "collect_notebook": true, + "base": { + "file": "win7-office-base.qcow2", + "format": "qcow2", + "sha256": "a1a4f8fab782ee14885ff801ca2f6347c208fdcfc3513637096f314315c89346", + "virtual_size": 68719476736 + }, + "scripts": { + "cold.ps1": "c177fc72ae6c2634d186f5671a880de20b09f9716532c37c69cb13aa15c7e331", + "read.ps1": "04013bfcccee40a17e2a225f9b9e96f40a3a8daad9e350609654f8eda3ccbb41" + } +} diff --git a/corpus/cross-run-edit/table/cold/source.json b/corpus/cross-run-edit/table/cold/source.json new file mode 100644 index 0000000000000000000000000000000000000000..579110b25025df76061d9cd19ba14d001327467c --- /dev/null +++ b/corpus/cross-run-edit/table/cold/source.json @@ -0,0 +1,8 @@ +[ + { + "path": "synthetic.one", + "bytes": 30864, + "sha256": "89ce5a50f050c05ecbfb92c8c8eb0538bd041d0596290a5b974a9878e171e6e9", + "mtime_ns": 1788850928450254092 + } +] diff --git a/corpus/cross-run-edit/table/cold/teardown.json b/corpus/cross-run-edit/table/cold/teardown.json new file mode 100644 index 0000000000000000000000000000000000000000..05a47793de40f322e745c4c4183727e3590ecf70 --- /dev/null +++ b/corpus/cross-run-edit/table/cold/teardown.json @@ -0,0 +1 @@ +{"absent": true} diff --git a/crates/onestore-offline/examples/smb_offline_client.rs b/crates/onestore-offline/examples/smb_offline_client.rs index 4cea5e7d44f8dd4ffd85d0db8a51f20d8bc4832d..3b80ea77392b8b240e16e4b7bfd0fc920062349f 100644 --- a/crates/onestore-offline/examples/smb_offline_client.rs +++ b/crates/onestore-offline/examples/smb_offline_client.rs @@ -23,6 +23,8 @@ mod support { use concurrent::document_view; use support::view::view; +const DOCUMENT_OPERATIONS: [&str; 3] = ["insert", "format", "text"]; + fn now() -> u128 { SystemTime::now() .duration_since(UNIX_EPOCH) @@ -275,7 +277,7 @@ fn queue_document( operation: usize, parent: Option, deadline: Instant, -) -> Result<(ExGuid, [u64; 2]), Box> { +) -> Result<(ExGuid, [u64; DOCUMENT_OPERATIONS.len()]), Box> { let source = cache.snapshot()?; let store = Store::parse(&source)?; let index = RevisionIndex::parse(&store)?; @@ -320,8 +322,15 @@ fn queue_document( TextAttribute::FontSize(18.0 + (operation % 9) as f32), TextAttribute::Color(Some([0x12, 0x34, 0x56])), ]; - let mut ids = [0; 2]; + let mut ids = [0; DOCUMENT_OPERATIONS.len()]; for (step, id) in ids.iter_mut().enumerate() { + let kind = DOCUMENT_OPERATIONS[step]; + let range = if step == 2 { + let end = u32::try_from(text.encode_utf16().count())?; + end - 3..end + } else { + range.clone() + }; loop { if Instant::now() >= deadline { return Err("Document queue timed out; cache retained".into()); @@ -330,7 +339,7 @@ fn queue_document( let started = now(); let result = if step == 0 { cache.insert(&source, space, &insertion) - } else { + } else if step == 1 { cache.format( &source, space, @@ -338,13 +347,21 @@ fn queue_document( range.clone(), &attributes, ) + } else { + cache.edit_text( + &source, + space, + insertion.text_object(), + range.clone(), + " e\u{301}🐈", + ) }; match result { Ok(Some(acknowledged)) => { *id = acknowledged; println!( "{}", - json!({"event":"local_document_commit","id":acknowledged,"operation":operation,"kind":if step==0 {"insert"} else {"format"},"space":space.to_string(),"object":insertion.text_object().to_string(),"text":text,"insertion":if step==0 {Some(&insertion)} else {None},"range":[range.start,range.end],"attributes":attributes,"started_us":started,"finished_us":now()}) + json!({"event":"local_document_commit","id":acknowledged,"operation":operation,"kind":kind,"space":space.to_string(),"object":insertion.text_object().to_string(),"text":text,"insertion":if step==0 {Some(&insertion)} else {None},"range":[range.start,range.end],"attributes":attributes,"replacement":if step==2 {Some(" e\u{301}🐈")} else {None},"started_us":started,"finished_us":now()}) ); break; } @@ -400,7 +417,7 @@ fn main() -> Result<(), Box> { let cache = Arc::new(Replica::create(&cache_path, &source)?); println!( "{}", - json!({"event":"ready", "pid":std::process::id(), "actor":args[2], "offline":true, "document_operations":documents}) + json!({"event":"ready", "pid":std::process::id(), "actor":args[2], "offline":true, "document_operations":documents,"document_kinds":if documents {DOCUMENT_OPERATIONS.as_slice()} else {&[]}}) ); while !Path::new(&args[4]).exists() { if Instant::now() >= deadline { @@ -470,7 +487,7 @@ fn main() -> Result<(), Box> { { 1 } else if documents && outage.is_some() { - 24 + 8 * (1 + DOCUMENT_OPERATIONS.len()) } else { 8 }; diff --git a/crates/onestore-offline/src/lib.rs b/crates/onestore-offline/src/lib.rs index a21ba1afca74f36ba8932b5c338e82f725192808..31c7c9505ecf1fbc406f82fe2ac3df17ef9c0407 100644 --- a/crates/onestore-offline/src/lib.rs +++ b/crates/onestore-offline/src/lib.rs @@ -180,6 +180,7 @@ impl Replica { /// Atomically records an intent and its resulting local image; returns its durable ID. /// Unchanged text returns `None`. A stale image returns `Io(ResourceBusy)`. + /// On synchronization, replacement text inherits the remote style at the rebased start. /// After a database error, reopen and inspect the cache before retrying the edit. pub fn edit_text( &self, diff --git a/crates/onestore-offline/tests/sync.rs b/crates/onestore-offline/tests/sync.rs index 8cc2aada4ffffdfa93bcb5e201d3e640ef9579eb..52d1f6b8a2115edf0fcc2d43fc41408c7c0ff349 100644 --- a/crates/onestore-offline/tests/sync.rs +++ b/crates/onestore-offline/tests/sync.rs @@ -1888,6 +1888,82 @@ fn uncertain_insertions_reconcile_the_original_revision_without_duplicate_object } } +#[test] +fn cross_run_text_rebases_preserve_remote_styles_and_survive_lost_replies() { + use onestore::TextAttribute as A; + for fault in [Fault::None, Fault::UnknownAfter, Fault::PanicAfter] { + let directory = tempfile::tempdir().unwrap(); + let path = directory.path().join("cross-run.sqlite"); + let plain = onestore::create_section("cross.one", "ab🦀cde\u{301}fg", "Author").unwrap(); + let (sid, oid, _) = text(&plain); + let bold = PreparedEdit::format(&plain, sid, oid, 1..4, &[A::Bold(true)]).unwrap(); + let source = PreparedEdit::format(bold.as_bytes(), sid, oid, 4..7, &[A::Italic(true)]) + .unwrap() + .as_bytes() + .to_vec(); + let cache = Replica::create(&path, &source).unwrap(); + let first = cache + .edit_text(&source, sid, oid, 2..7, "日本語") + .unwrap() + .unwrap(); + let local = cache.snapshot().unwrap(); + let second = cache + .edit_text(&local, sid, oid, 3..4, "🐈") + .unwrap() + .unwrap(); + let pending = cache.pending().unwrap(); + drop(cache); + let cache = Replica::open(&path).unwrap(); + assert_eq!(cache.pending().unwrap(), pending); + let prefix = PreparedEdit::text(&source, sid, oid, 0..0, "Prefix ").unwrap(); + let remote = + PreparedEdit::format(prefix.as_bytes(), sid, oid, 9..11, &[A::Underline(true)]) + .unwrap(); + let mut server = Server::new(remote.as_bytes()); + server.fault = fault; + let attempted = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + cache.sync_once(&mut server) + })); + if matches!(fault, Fault::None) { + assert!( + matches!(attempted.unwrap().unwrap(), Some((id, EditStatus::Published { .. })) if id == first) + ); + } else { + assert!(matches!( + cache.status(first).unwrap(), + Some(EditStatus::AwaitingConfirmation { .. }) + )); + } + drop(cache); + let cache = Replica::open(&path).unwrap(); + while let Some((_, status)) = cache.sync_once(&mut server).unwrap() { + assert!(matches!(status, EditStatus::Published { .. })); + } + assert_eq!(server.publications, 2); + assert!(matches!( + cache.status(second).unwrap(), + Some(EditStatus::Published { .. }) + )); + assert_eq!(text(&server.durable).2, "Prefix ab日🐈語\u{301}fg"); + assert_eq!(cache.snapshot().unwrap(), server.durable); + let store = Store::parse(&server.durable).unwrap(); + let index = RevisionIndex::parse(&store).unwrap(); + let document = Document::parse(&index).unwrap(); + let space = &document.spaces[&sid]; + let revision = &space.revisions[&space.contexts[&ExGuid::default()]]; + let runs = revision.text_runs(oid).unwrap(); + let replacement = runs.iter().find(|run| run.text == "日🐈語").unwrap(); + assert_eq!(replacement.format.bold, Some(true)); + assert_eq!(replacement.format.underline, Some(true)); + assert_ne!(replacement.format.italic, Some(true)); + let suffix = runs.last().unwrap(); + assert_eq!(suffix.text, "\u{301}fg"); + assert_ne!(suffix.format.bold, Some(true)); + assert_ne!(suffix.format.underline, Some(true)); + assert_ne!(suffix.format.italic, Some(true)); + } +} + #[test] fn offline_formatting_rebases_text_and_merges_independent_attributes() { use onestore::TextAttribute as A; diff --git a/crates/onestore/README.md b/crates/onestore/README.md index 77cf64f3c7bd8e0d0e9545af30ad7fd043f03a7b..2e63064f53df82ca64ab15fb2a70604d78bd5ce6 100644 --- a/crates/onestore/README.md +++ b/crates/onestore/README.md @@ -47,7 +47,7 @@ harness also accepts `--client-profile release`. | `create_section` | Create one page containing one plain-text paragraph and an author, including Unicode | | `create_table_of_contents` | Create ordered section entries from filenames and file identities | | `replace_property_bytes` | Append one scalar-property revision; preserve prior revisions and unrelated property values and references | -| `replace_text`, `commit_text`, `commit_file_text` | Replace a UTF-16 range within one ordinary text run; publish text, run boundaries and modification time together | +| `replace_text`, `commit_text`, `commit_file_text` | Replace a UTF-16 range across ordinary text runs; publish text, run boundaries and modification time together | | `Insertion`, `PreparedEdit::insert` | Insert paragraphs into editable containers or positioned outlines into a page, retaining intent identities across rebases | | `TextAttribute`, `PreparedEdit::format` | Change character formatting over a UTF-16 range while sharing immutable styles; preserve unselected runs | | `PreparedEdit::commit`, `PreparedEdit::commit_file` | Publish the exact prepared image under caller-held exclusion or the conservative filesystem adapter | diff --git a/crates/onestore/src/edit.rs b/crates/onestore/src/edit.rs index a33f06a263e69dfd777f497a16ccf53e0a2821ad..9cf5af2efde70468e991104a7f5e2c34b3d4d18f 100644 --- a/crates/onestore/src/edit.rs +++ b/crates/onestore/src/edit.rs @@ -18,9 +18,11 @@ pub(crate) fn automatic_title(text: &str) -> &str { line } -/// Replaces UTF-16 character positions within one ordinary text run. -/// The inserted text inherits that run's formatting; other runs retain their identities. +/// Replaces UTF-16 character positions across ordinary text runs. +/// Inserted text inherits the run at the start; surviving text retains its formatting. /// Insertion at a run boundary uses the following run, except at the end of text. +/// The final run retains its insertion style even when emptied. +/// Replacing a range with identical text leaves its existing formatting unchanged. /// Returns a complete file image without I/O; use `commit_file_text` to update an existing file. pub fn replace_text( source: &[u8], @@ -79,22 +81,25 @@ pub fn replace_text( } let selected = runs .iter() - .rposition(|run| run.start <= range.start && range.end <= run.end) - .ok_or_else(|| invalid("The edit must stay within one text run"))?; + .rposition(|run| run.start <= range.start && range.start <= run.end) + .ok_or_else(|| invalid("The edit range exceeds the text"))?; let resolved = revision.text_runs(object)?; - let format = &resolved[selected].format; - if [ - format.hidden, - format.hyperlink, - format.math, - format.embedded_object, - ] - .contains(&Some(true)) - || runs[selected] - .extra_set - .is_some_and(|set| !node.extra[set].is_empty()) - { - return Err(invalid("This text run contains a field or embedded data")); + for (i, run) in runs.iter().enumerate() { + if i == selected || (run.start < range.end && range.start < run.end) { + let format = &resolved[i].format; + if [ + format.hidden, + format.hyperlink, + format.math, + format.embedded_object, + ] + .contains(&Some(true)) + || resolved[i].text.contains(['\u{fffc}', '\u{fddf}']) + || run.extra_set.is_some_and(|set| !node.extra[set].is_empty()) + { + return Err(invalid("This text run contains a field or embedded data")); + } + } } let ObjectData::Properties(blob) = raw.objects[&object].data else { unreachable!() @@ -141,20 +146,47 @@ pub fn replace_text( .checked_sub(removed) .and_then(|n| n.checked_add(added)) .ok_or_else(|| invalid("Edited text exceeds the UTF-16 offset range"))?; - let mut boundaries = Vec::new(); - let mut previous = None; - for (i, run) in runs[..runs.len() - 1].iter().enumerate() { - let end = if i >= selected { - run.end - removed + added - } else { - run.end - }; - if previous.is_some_and(|p| p >= end) { - return Err(invalid("The edit would collapse a formatting boundary")); + let mut segments = Vec::new(); + let mut position = 0; + for (i, run) in runs.iter().enumerate() { + let length = run.end.min(range.start).saturating_sub(run.start) + + run.end.saturating_sub(run.start.max(range.end)) + + if i == selected { added } else { 0 }; + let untouched = i != selected && (run.end <= range.start || range.end <= run.start); + if length > 0 || i == runs.len() - 1 || untouched { + position += length; + segments.push((i, position)); } - previous = Some(end); - boundaries.extend_from_slice(&end.to_le_bytes()); } + if segments.len() != runs.len() && properties.sets[0].iter().any(|p| p.id == 0x40003499) { + return Err(invalid("Text edits cannot remove preserved run data")); + } + if segments[..segments.len() - 1] + .windows(2) + .any(|pair| pair[0].1 >= pair[1].1) + { + return Err(invalid("Text-run boundaries must be strictly increasing")); + } + let boundaries: Vec<_> = segments[..segments.len() - 1] + .iter() + .flat_map(|(_, end)| end.to_le_bytes()) + .collect(); + let formats = properties.sets[0] + .iter() + .find(|p| p.id == 0x24001e13) + .map(|p| { + let crate::Value::References { compact_ids, .. } = p.value else { + unreachable!() + }; + if compact_ids.is_empty() { + Vec::new() + } else { + segments + .iter() + .flat_map(|(i, _)| compact_ids[i * 4..i * 4 + 4].iter().copied()) + .collect() + } + }); let mut changed = String::with_capacity(text.len() - (end - start) + replacement.len()); changed.push_str(&text[..start]); changed.push_str(replacement); @@ -183,6 +215,9 @@ pub fn replace_text( if properties.sets[0].iter().any(|p| p.id == 0x1c001e12) { updates.push((0x1c001e12, &boundaries)); } + if let Some(formats) = &formats { + updates.push((0x24001e13, formats)); + } let modified = crate::create::current_timestamps()?.0.to_le_bytes(); updates.push((0x14001d7a, &modified)); let mut edits = vec![crate::write::ObjectEdit { diff --git a/crates/onestore/src/write/tests.rs b/crates/onestore/src/write/tests.rs index f10f53299d01dbb391d47cf15898ec1db91ad6d6..845064f1c0da2afbadca12938a8496554f36eb78 100644 --- a/crates/onestore/src/write/tests.rs +++ b/crates/onestore/src/write/tests.rs @@ -589,7 +589,7 @@ fn character_formatting_preserves_inheritance_and_associated_data() { .find_map(|(id, o)| (o.jcid == 0x6000e).then_some((*sid, *id))) }) .unwrap(); - for variant in 0..7 { + for variant in 0..10 { let fixture = write_revision(&source, sid, |raw| { let mut target = PropertyObject::from_object(&raw.objects[&text])?; target.bytes = properties(&[ @@ -617,8 +617,17 @@ fn character_formatting_preserves_inheritance_and_associated_data() { let reference = target.reference(author)?; target.set(&[(0x24003458, &reference)])?; } - 3 => { - // An unknown nested run property must survive a style-only edit byte for byte. + 3 | 7 | 8 | 9 => { + if variant != 3 { + let ends = if variant == 9 { [2_u32, 2] } else { [2, 4] }; + target.set(&[( + 0x1c001e12, + &ends + .into_iter() + .flat_map(u32::to_le_bytes) + .collect::>(), + )])?; + } let parsed = PropertySets::parse(&target.bytes)?; let at = parsed.root_ids.as_ptr().addr() - target.bytes.as_ptr().addr(); let count = parsed.sets[0].len(); @@ -626,11 +635,17 @@ fn character_formatting_preserves_inheritance_and_associated_data() { let mut bytes = target.bytes[..end].to_vec(); bytes[at - 2..at].copy_from_slice(&((count + 1) as u16).to_le_bytes()); bytes.splice(at + count * 4..at + count * 4, 0x40003499_u32.to_le_bytes()); - bytes.extend_from_slice(&1_u32.to_le_bytes()); + let runs: u32 = if variant == 3 { 1 } else { 3 }; + bytes.extend_from_slice(&runs.to_le_bytes()); bytes.extend_from_slice(&0x44001234_u32.to_le_bytes()); - bytes.extend_from_slice(&1_u16.to_le_bytes()); - bytes.extend_from_slice(&0x14001234_u32.to_le_bytes()); - bytes.extend_from_slice(&0xdeadbeef_u32.to_le_bytes()); + for run in 0..runs { + let populated = (variant == 3 || variant == 8) && run == runs - 1; + bytes.extend_from_slice(&u16::from(populated).to_le_bytes()); + if populated { + bytes.extend_from_slice(&0x14001234_u32.to_le_bytes()); + bytes.extend_from_slice(&0xdeadbeef_u32.to_le_bytes()); + } + } bytes.resize(bytes.len().next_multiple_of(8), 0); target.bytes = bytes; } @@ -639,6 +654,34 @@ fn character_formatting_preserves_inheritance_and_associated_data() { Ok(BTreeMap::from([(text, target)])) }) .unwrap(); + if variant == 9 { + assert!(PreparedEdit::text(&fixture, sid, text, 0..0, "x").is_err()); + continue; + } + if variant >= 7 { + let edit = PreparedEdit::text(&fixture, sid, text, 1..3, "longer").unwrap(); + let [before, after] = [&fixture, edit.as_bytes()].map(|bytes| { + let store = Store::parse(bytes).unwrap(); + let index = RevisionIndex::parse(&store).unwrap(); + let document = Document::parse(&index).unwrap(); + let space = &document.spaces[&sid]; + let view = &space.revisions[&space.contexts[&ExGuid::default()]]; + let Kind::RichText { runs, .. } = &view.nodes[&text].kind else { + panic!() + }; + assert_eq!(runs.len(), 3); + format!("{:?}", view.nodes[&text].extra) + }); + assert_eq!(before, after); + assert!(PreparedEdit::text(&fixture, sid, text, 0..4, "x").is_err()); + assert_eq!( + PreparedEdit::text(&fixture, sid, text, 3..5, "x").is_ok(), + variant == 7 + ); + continue; + } + let text_edit = PreparedEdit::text(&fixture, sid, text, 1..5, "x"); + assert_eq!(text_edit.is_ok(), variant == 0); let edit = PreparedEdit::format(&fixture, sid, text, 0..6, &[TextAttribute::Bold(true)]); if matches!(variant, 1 | 2 | 4 | 5 | 6) { assert!(edit.is_err()); diff --git a/crates/onestore/tests/edit.rs b/crates/onestore/tests/edit.rs index 67294f2abd0d8d34c6335d825f9b5151ecd8af13..f810dc59e22494f04049aff333beab50353dbd0f 100644 --- a/crates/onestore/tests/edit.rs +++ b/crates/onestore/tests/edit.rs @@ -368,9 +368,10 @@ fn invalid_text_edits_never_touch_storage() { for (range, replacement) in [ (1..1, "x"), (0..999, "x"), - (0..21, "x"), (0..0, "\n"), (0..0, "\0"), + (0..0, "\u{fffc}"), + (0..0, "\u{fddf}"), ] { let mut disk = Disk { visible: source.clone(), @@ -388,6 +389,179 @@ fn invalid_text_edits_never_touch_storage() { } } +#[test] +fn cross_run_edits_reject_native_fields_before_storage_io() { + let source = include_bytes!("../../../corpus/m6/native-probes-01/notebook/synthetic.one"); + let store = Store::parse(source).unwrap(); + let index = RevisionIndex::parse(&store).unwrap(); + let document = Document::parse(&index).unwrap(); + let mut fields = 0; + for (sid, space) in &document.spaces { + let revision = &space.revisions[&space.contexts[&ExGuid::default()]]; + for (oid, node) in &revision.nodes { + let Kind::RichText { text, runs, .. } = &node.kind else { + continue; + }; + let resolved = revision.text_runs(*oid).unwrap(); + for (run, resolved) in runs.iter().zip(resolved) { + if resolved.format.hyperlink != Some(true) { + continue; + } + fields += 1; + for range in [0..run.end, run.start..text.encode_utf16().count() as u32] { + let mut disk = Disk { + visible: source.to_vec(), + durable: source.to_vec(), + operation: 0, + fail_at: None, + write_limit: 17, + random: 9, + }; + let error = onestore::commit_text(&mut disk, source, *sid, *oid, range, "x") + .unwrap_err(); + assert_eq!(error.state, CommitState::NotCommitted); + assert_eq!(disk.operation, 0); + assert_eq!(disk.durable, source); + } + } + } + } + assert!(fields > 0); +} + +#[test] +fn cross_run_splices_match_a_character_model_and_preserve_history() { + let (sid, oid) = target(SOURCE); + let characters = |source: &[u8]| { + let runs = text_runs(source, sid, oid); + let runs = runs.as_array().unwrap(); + let mut result = Vec::new(); + for run in runs { + result.extend( + run["text"] + .as_str() + .unwrap() + .chars() + .map(|c| (c, run["format"].clone())), + ); + } + result.push(('\0', runs.last().unwrap()["format"].clone())); + result + }; + let before = characters(SOURCE); + let offsets: Vec<_> = std::iter::once(0) + .chain(before[..before.len() - 1].iter().scan(0, |at, (c, _)| { + *at += c.len_utf16() as u32; + Some(*at) + })) + .collect(); + let store = Store::parse(SOURCE).unwrap(); + let index = RevisionIndex::parse(&store).unwrap(); + for a in 0..before.len() { + for b in a..before.len() { + let replacement = ["", "🦀e\u{301}", "日本語"][b % 3]; + let mut expected = before.clone(); + expected.splice(a..b, replacement.chars().map(|c| (c, before[a].1.clone()))); + let edited = + onestore::replace_text(SOURCE, sid, oid, offsets[a]..offsets[b], replacement) + .unwrap(); + assert_eq!(characters(&edited), expected, "characters {a}..{b}"); + let current_store = Store::parse(&edited).unwrap(); + assert_eq!( + current_store.header.transaction_count, + store.header.transaction_count + + usize::from(a != b || !replacement.is_empty()) as u32 + ); + let current = RevisionIndex::parse(¤t_store).unwrap(); + for (space, history) in &index.spaces { + for revision in history.revisions.keys() { + let old = index.resolve(*space, *revision).unwrap(); + let retained = current.resolve(*space, *revision).unwrap(); + assert_eq!(old.roots, retained.roots); + for (id, object) in old.objects { + assert_eq!(object.data, retained.objects[&id].data); + } + } + } + } + } + let cleared = + onestore::replace_text(SOURCE, sid, oid, 0..*offsets.last().unwrap(), "").unwrap(); + let typed = onestore::replace_text(&cleared, sid, oid, 0..0, "new").unwrap(); + let format = &before.last().unwrap().1; + assert_eq!( + characters(&typed), + "new\0" + .chars() + .map(|c| (c, format.clone())) + .collect::>() + ); + let text: String = before[..before.len() - 1].iter().map(|(c, _)| *c).collect(); + assert_eq!( + onestore::replace_text(SOURCE, sid, oid, 0..*offsets.last().unwrap(), &text).unwrap(), + SOURCE + ); +} + +#[test] +#[ignore = "exports cross-run edits for independent native validation"] +fn export_native_cross_run_edits() { + use std::{fs, path::PathBuf}; + let output = PathBuf::from(std::env::var_os("ONESTORE_CROSS_RUN_OUTPUT").unwrap()); + assert!(output.is_absolute()); + fs::create_dir(&output).unwrap(); + let mut manifest = Vec::new(); + for (name, fixture, range, replacement, retype) in [ + ("partial", "native-paragraph", 2..16, "中🦀", false), + ("table", "native-cell", 0..24, "Across 🐈 ", false), + ("clear", "native-paragraph", 0..u32::MAX, "", false), + ( + "replace", + "native-paragraph", + 0..u32::MAX, + "Replacement e\u{301}🦀", + false, + ), + ("retype", "native-paragraph", 0..u32::MAX, "", true), + ("boundary", "native-paragraph", 4..8, "Middle", false), + ] { + let input = format!("../../corpus/formatted-insertion/{fixture}/candidate/synthetic.one"); + let source = fs::read(&input).unwrap(); + let store = Store::parse(&source).unwrap(); + let index = RevisionIndex::parse(&store).unwrap(); + let document = Document::parse(&index).unwrap(); + let (sid, oid, length) = document + .spaces + .iter() + .find_map(|(sid, space)| { + let view = &space.revisions[&space.contexts[&ExGuid::default()]]; + view.nodes.iter().find_map(|(oid, node)| { + let Kind::RichText { text, .. } = &node.kind else { + return None; + }; + text.starts_with("Bold ") + .then(|| (*sid, *oid, text.encode_utf16().count() as u32)) + }) + }) + .unwrap(); + let range = range.start..range.end.min(length); + let mut edited = + onestore::replace_text(&source, sid, oid, range.clone(), replacement).unwrap(); + if retype { + edited = onestore::replace_text(&edited, sid, oid, 0..0, "Retyped 🦀").unwrap(); + } + let candidate = output.join(name).join("candidate"); + fs::create_dir_all(&candidate).unwrap(); + fs::write(candidate.join("synthetic.one"), &edited).unwrap(); + manifest.push(serde_json::json!({"name":name,"source":input,"space":sid,"object":oid,"range":range,"replacement":replacement,"retype":retype})); + } + fs::write( + output.join("manifest.json"), + serde_json::to_vec_pretty(&manifest).unwrap(), + ) + .unwrap(); +} + #[test] fn insertion_at_a_style_boundary_uses_the_following_style() { let (sid, oid) = target(SOURCE); @@ -701,14 +875,14 @@ fn interrupted_text_commits_never_publish_mismatched_run_boundaries() { }; let before = text_runs(source, sid, oid); let mut success = make_disk(None, 1); - onestore::commit_text(&mut success, source, sid, oid, 0..10, "🐈 mixed edit").unwrap(); + onestore::commit_text(&mut success, source, sid, oid, 0..23, "🐈 mixed edit").unwrap(); let after = text_runs(&success.durable, sid, oid); assert_ne!(before, after); for at in source.len().div_ceil(193)..=success.operation { for seed in [1, 42] { let mut disk = make_disk(Some(at), seed); let error = - onestore::commit_text(&mut disk, source, sid, oid, 0..10, "🐈 mixed edit") + onestore::commit_text(&mut disk, source, sid, oid, 0..23, "🐈 mixed edit") .unwrap_err(); let observed = text_runs(&disk.durable, sid, oid); match error.state { diff --git a/fuzz/fuzz_targets/edit_text.rs b/fuzz/fuzz_targets/edit_text.rs index b515803829fa8d87929cc868bc9ee70a8228814e..4cf020c18a9b88b8d6452e8dcba57e526358fd06 100644 --- a/fuzz/fuzz_targets/edit_text.rs +++ b/fuzz/fuzz_targets/edit_text.rs @@ -129,7 +129,7 @@ fuzz_target!(|input: &[u8]| { let (source, sid, oid) = &CASES[usize::from(input.last().copied().unwrap_or(0)) % CASES.len()]; let (sid, oid) = (*sid, *oid); let mut persisted_source = source.clone(); - let mut caches = std::array::from_fn::<_, 6, _>(|_| source.clone()); + let mut caches = std::array::from_fn::<_, 12, _>(|_| source.clone()); for step in input.chunks_exact(8).take(16) { let actor = usize::from(step[6]) % caches.len(); if step[7] % 3 == 0 { @@ -154,7 +154,14 @@ fuzz_target!(|input: &[u8]| { let replacement = ["", "a", "🦀e\u{301}", "日本語", "\n", "\0"][usize::from(step[2]) % 6]; let mut expected = before.clone(); let format = before[start].1.clone(); - expected.splice(start..end, replacement.chars().map(|c| (c, format.clone()))); + if before[start..end] + .iter() + .map(|(c, _)| *c) + .collect::() + != replacement + { + expected.splice(start..end, replacement.chars().map(|c| (c, format.clone()))); + } let mut storage = Disk { visible: persisted_source.clone(), durable: persisted_source.clone(), @@ -173,6 +180,15 @@ fuzz_target!(|input: &[u8]| { replacement, ); let persisted = characters(&storage.durable, sid, oid); + if !replacement.contains(['\n', '\0']) { + assert!( + result + .as_ref() + .err() + .is_none_or(|error| error.error.kind() != std::io::ErrorKind::InvalidData), + "Valid ordinary text edit was rejected: {result:?}" + ); + } match result { Ok(()) => assert_eq!(persisted, expected), Err(error) => match error.state { diff --git a/tools/native_collaboration.py b/tools/native_collaboration.py index e51f7e04d0ba24f45006018565460e6619f7dc17..063727f37b744277cb9f4163e4ee89284c404852 100644 --- a/tools/native_collaboration.py +++ b/tools/native_collaboration.py @@ -54,6 +54,14 @@ def replay(output, server, stress_clients=0, stress_operations=30, sync_every=1, raise ValueError('Choose a new Linux VM name; existing machines are not owned by this run.') output = output.resolve() output.mkdir(parents=True, exist_ok=False) + for profile in (('debug', 'release') if client_profile == 'release' else ('debug',)): + build = ['cargo', 'build', '--locked', '--workspace', '--all-features', '--examples'] + if profile == 'release': build.append('--release') + with (output / f'build-{profile}.log').open('w') as log: + result = subprocess.run(build, cwd=ROOT, env={**os.environ, 'CARGO_TARGET_DIR': str(ROOT / 'target')}, + stdout=log, stderr=subprocess.STDOUT) + (output / f'build-{profile}.json').write_text(json.dumps({'command': build, 'exit': result.returncode}, indent=2)) + result.check_returncode() mount = output / 'mount' mount.mkdir() scripts = output / 'scripts' diff --git a/tools/native_stress.py b/tools/native_stress.py index c82f2a642db999599a2830250e748e37aadd6ef1..dd486844600f9d0035f900f166c8ceba61a18f3b 100644 --- a/tools/native_stress.py +++ b/tools/native_stress.py @@ -96,7 +96,7 @@ def verify_capture(output, capture): if config.get('document_operations'): from offline_document_history import document_history documents = document_history(logs, operations) - expected.extend(document['text'] for document in documents.values()) + expected.extend(''.join(char for char, *_ in list(document['states'].values())[-1]['characters']) for document in documents.values()) page_file, = capture.glob('page-*.xml') page = ET.parse(page_file).getroot() paragraphs = native_characters(page, page.findall('one:Outline', ns)) @@ -111,8 +111,8 @@ def verify_capture(output, capture): checks += 1 if documents: from offline_document_history import verify_native - checks += verify_native(paragraphs, documents) - return {'rust_intents': len(commits), 'document_intents': len(documents)*2, 'native_intents': sum(map(len, native)), + checks += verify_native(paragraphs, (list(document['states'].values())[-1]['characters'] for document in documents.values())) + return {'rust_intents': len(commits), 'document_intents': sum(len(document['states']) for document in documents.values()), 'native_intents': sum(map(len, native)), 'exact_paragraphs': len(expected), 'native_intended_format_checks': checks} @@ -231,7 +231,7 @@ def exercise(output, shared, clients, action, wait_action, wait_text, checkpoint if config.get('document_operations'): from offline_document_history import document_history documents = document_history(rust, operations) - expected.extend(document['text'] for document in documents.values()) + expected.extend(''.join(char for char, *_ in list(document['states'].values())[-1]['characters']) for document in documents.values()) for client in clients: wait_text(client, expected) checkpoint('stress-final') model = json.loads((output / 'stress-final/model/document.json').read_text()) @@ -262,7 +262,7 @@ def exercise(output, shared, clients, action, wait_action, wait_text, checkpoint earliest_end = (native['updated_ticks'] - 621355968000000000) // 10 - high overlap += sum(max(latest_start, rust['started_us']) < min(earliest_end, rust['finished_us']) for rust in commits) result = {'native_writers': len(clients), 'rust_writers': rust_writers, 'rust_readers': rust_readers, - 'sync_every': sync_every, 'edit': edit, 'seed': seed, 'offline': config.get('offline', False), 'native_edits': operations * len(clients), 'rust_commits': len(commits), 'document_commits': len(documents)*2, 'rust_reads': len(reads), + 'sync_every': sync_every, 'edit': edit, 'seed': seed, 'offline': config.get('offline', False), 'native_edits': operations * len(clients), 'rust_commits': len(commits), 'document_commits': sum(len(document['states']) for document in documents.values()), 'rust_reads': len(reads), 'clock_bounded_native_rust_call_overlaps': overlap, 'converged_paragraphs': len(expected)} (output / 'result.json').write_text(json.dumps(result, indent=2)) assert overlap, 'No native/Rust call overlap established within clock uncertainty' diff --git a/tools/offline_document_history.py b/tools/offline_document_history.py index 23f0f8cc469866ebf280ff378958e2451d55bcc6..3edb363b0a141f66068e86d5b6ad21fd4e22c917 100644 --- a/tools/offline_document_history.py +++ b/tools/offline_document_history.py @@ -15,14 +15,21 @@ def characters(observed): return actual +def operation_kinds(events): + kinds = tuple(events[0].get('document_kinds', ('insert', 'format'))) + assert kinds in (('insert', 'format'), ('insert', 'format', 'text')), 'Unknown document workload' + return kinds + + def document_history(logs, operations): documents = {} for actor, events in logs.items(): if not actor.startswith('w'): continue assert events[0].get('document_operations') is True, 'Writer omitted document operations' + kinds = operation_kinds(events) edits = [row for row in events if row['event'] == 'local_document_commit'] assert [(row['operation'], row['kind']) for row in edits] == [ - (i, kind) for i in range(operations) for kind in ('insert', 'format')], 'Missing or duplicate document intent' + (i, kind) for i in range(operations) for kind in kinds], 'Missing or duplicate document intent' ids = [row['id'] for row in edits] assert ids == sorted(set(ids)), 'Document intent IDs are duplicated or unordered' assert not set(ids) & {row['id'] for row in events if row['event'] == 'local_commit'}, 'Text and document intents share an ID' @@ -64,7 +71,8 @@ def document_history(logs, operations): else: assert receipt['revision'] == attempt['revision'] linked[intent['id']] = {**attempt, 'acknowledged_us': receipt['at_us'], 'receipt_revision': receipt['revision']} - for inserted, formatted in zip(edits[::2], edits[1::2], strict=True): + for at in range(0, len(edits), len(kinds)): + inserted, formatted, *replaced = edits[at:at + len(kinds)] number = inserted['operation'] text = f'Document {actor}:{number} 🦀' insertion = inserted['insertion'] @@ -76,7 +84,7 @@ def document_history(logs, operations): assert insertion['placement'] == {'Outline': {'x': 144 + int(actor[1:]) * 240, 'y': 144 + number * 72}}, 'Outline placement differs from intent' else: assert insertion['placement'] == {'Paragraph': {'before': None}} - assert insertion['parent'] == identity(edits[(number-1)*2]['insertion'], 1), 'Paragraph lost its outline parent' + assert insertion['parent'] == identity(edits[(number-1)*len(kinds)]['insertion'], 1), 'Paragraph lost its outline parent' assert formatted['range'] == [1, len(text.encode('utf-16-le')) // 2 - 2] assert formatted['attributes'] == [{'Bold': True}, {'FontSize': 18 + number % 9}, {'Color': [18, 52, 86]}] old = [(char, False, 11, 0xff000000) for char in text] @@ -91,8 +99,20 @@ def document_history(logs, operations): assert created['finished_us'] <= changed['started_us'], 'Formatting preceded its insertion' assert characters(created['documents'][target]) == old, 'Insertion publication differs from its local intent' assert characters(changed['documents'][target]) == new, 'Formatting publication differs from its local intent' - documents[target] = {'text': text, 'insertion': insertion, 'space': inserted['space'], - 'old': old, 'new': new, 'insert': created, 'format': changed} + states = {'insert': {'characters': old, 'attempt': created}, + 'format': {'characters': new, 'attempt': changed}} + if replaced: + replacement, = replaced + end = len(text.encode('utf-16-le')) // 2 + assert replacement['object'] == target and replacement['space'] == inserted['space'], 'Text edit addresses another object' + assert replacement['text'] == text and replacement['range'] == [end-3, end], 'Cross-run text range differs from workload' + assert replacement['replacement'] == ' e\u0301🐈', 'Cross-run replacement differs from workload' + final = new[:-2] + [(char, *new[-2][1:]) for char in replacement['replacement']] + attempt = linked[replacement['id']] + assert changed['finished_us'] <= attempt['started_us'], 'Text replacement preceded its formatting' + assert characters(attempt['documents'][target]) == final, 'Text publication differs from its local intent' + states['text'] = {'characters': final, 'attempt': attempt} + documents[target] = {'insertion': insertion, 'space': inserted['space'], 'states': states} assert documents, 'No document operations were recorded' for actor, events in logs.items(): previous = {} @@ -103,19 +123,20 @@ def document_history(logs, operations): observed = read['documents'] assert set(previous) <= set(observed) <= set(documents), 'Reader lost an object or observed an unrecorded insertion' for target, document in documents.items(): - if read['started_us'] > document['insert']['acknowledged_us']: + states = list(document['states'].values()) + if read['started_us'] > states[0]['attempt']['acknowledged_us']: assert target in observed, 'Reader missed an acknowledged insertion' if target not in observed: continue - assert read['finished_us'] >= document['insert']['started_us'], 'Reader observed a future insertion' actual = characters(observed[target]) - assert actual in (document['old'], document['new']), 'Reader observed partial or invented formatting' - formatted = actual == document['new'] - assert not previous.get(target, False) or formatted, 'Reader reverted acknowledged formatting' - if read['started_us'] > document['format']['acknowledged_us']: - assert formatted, 'Reader missed acknowledged formatting' - if formatted: - assert read['finished_us'] >= document['format']['started_us'], 'Reader observed future formatting' - previous[target] = formatted + matches = [i for i, state in enumerate(states) if actual == state['characters']] + assert len(matches) == 1, 'Reader observed partial or invented document content' + current, = matches + assert current >= previous.get(target, 0), 'Reader reverted document content' + assert read['finished_us'] >= states[current]['attempt']['started_us'], 'Reader observed future document content' + for i, state in enumerate(states): + if read['started_us'] > state['attempt']['acknowledged_us']: + assert current >= i, 'Reader missed acknowledged document content' + previous[target] = current return documents @@ -138,7 +159,8 @@ def verify_model(model, documents): found.add(target) expected = documents[target] insertion = expected['insertion'] - assert sid == expected['space'] and node['kind']['text'] == expected['text'] + final = list(expected['states'].values())[-1]['characters'] + assert sid == expected['space'] and node['kind']['text'] == ''.join(char for char, *_ in final) object_id = identity(insertion, 1) assert object_id in nodes[insertion['parent']]['children'], 'Insertion lost its parent' paragraph = identity(insertion, 3) if 'Outline' in insertion['placement'] else object_id @@ -150,13 +172,13 @@ def verify_model(model, documents): assert found == set(documents), 'Final model omitted an inserted object' -def verify_native(paragraphs, documents): +def verify_native(paragraphs, expected): from PIL import ImageColor by_text = {''.join(char for char, _ in paragraph): paragraph for paragraph in paragraphs} checks = 0 - for document in documents.values(): - actual = by_text[document['text']] - for (char, style), (wanted, bold, size, color) in zip(actual, document['new'], strict=True): + for final in expected: + actual = by_text[''.join(char for char, *_ in final)] + for (char, style), (wanted, bold, size, color) in zip(actual, final, strict=True): assert char == wanted and bool(style.get('bold')) == bold, 'Native text or bold differs from intent' assert style.get('font_size', 11) == size, 'Native font size differs from intent' native_color = style.get('color', 'automatic') diff --git a/tools/offline_outage.py b/tools/offline_outage.py index 1e066857d23636b27b274dbac8aa49015c7f7dec..126413ec7d434b3095e7bc0235b7041f63ca9008 100644 --- a/tools/offline_outage.py +++ b/tools/offline_outage.py @@ -8,6 +8,7 @@ import time from native_runner import windows import linux_vm from verify_smb_overlap import verify +from offline_document_history import operation_kinds def interrupt(output, clients, sequences, processes): @@ -104,7 +105,7 @@ def interrupt(output, clients, sequences, processes): return wait_for(lambda: all((folder / f'offline-paused-{actor}').exists() for actor in writers) and (not config.get('document_operations') or all( - sum(row['event'] == 'local_document_commit' for row in logs()[actor]) == 2 for actor in writers)) + sum(row['event'] == 'local_document_commit' for row in logs()[actor]) == len(operation_kinds(logs()[actor])) for actor in writers)) and all(any(row['event'] == 'read' for row in logs()[actor]) for actor in readers), 'Clients did not reach the pre-publication outage barrier') samples['native_before'] = native_counts('before') @@ -115,7 +116,7 @@ def interrupt(output, clients, sequences, processes): (folder / 'offline-outage-down').touch() wait_for(lambda: all(sum(row['event'] == 'local_commit' for row in events) == 8 for actor, events in logs().items() if actor in writers) and (not config.get('document_operations') or all( - sum(row['event'] == 'local_document_commit' for row in logs()[actor]) == 16 for actor in writers)) + sum(row['event'] == 'local_document_commit' for row in logs()[actor]) == 8 * len(operation_kinds(logs()[actor])) for actor in writers)) and all(sum(row['event'] == 'transport_read_error' for row in logs()[actor]) > samples['reader_errors_before'][actor] for actor in readers), 'Local queues or disconnected readers failed to progress during the outage') time.sleep(3) @@ -185,16 +186,17 @@ def verify_outage(output): receipts = [row for row in events if row['event'] == 'remote_receipt'] assert len(receipts) == config['stress_operations'] and all(row['at_us'] > up for row in receipts) if config.get('document_operations'): + kinds = operation_kinds(events) edits = [row for row in events if row['event'] == 'local_document_commit'] assert [(row['operation'], row['kind']) for row in edits] == [ - (operation, kind) for operation in range(config['stress_operations']) for kind in ('insert', 'format')] - assert all(row['finished_us'] < down for row in edits[:2]), 'Initial document edits missed the outage barrier' + (operation, kind) for operation in range(config['stress_operations']) for kind in kinds] + assert all(row['finished_us'] < down for row in edits[:len(kinds)]), 'Initial document edits missed the outage barrier' queued = [row for row in edits if down < row['started_us'] <= row['finished_us'] < up] assert [(row['operation'], row['kind']) for row in queued] == [ - (operation, kind) for operation in range(1, 8) for kind in ('insert', 'format')], 'Document edits did not persist during the outage' + (operation, kind) for operation in range(1, 8) for kind in kinds], 'Document edits did not persist during the outage' queues[actor] += len(queued) receipts = [row for row in events if row['event'] == 'document_receipt'] - assert len(receipts) == config['stress_operations'] * 2 and all(row['at_us'] > up for row in receipts) + assert len(receipts) == config['stress_operations'] * len(kinds) and all(row['at_us'] > up for row in receipts) trace = [json.loads(line) for line in (output / 'smb-trace.jsonl').read_text().splitlines()] controls = [row['control'] for row in trace if row.get('control', {}).get('phase', '').startswith('offline-')] assert controls == [{'phase': 'offline-down', 'mode': 'down'}, {'phase': 'offline-reconnected'}], 'Unexpected outage control sequence' @@ -233,7 +235,7 @@ def verify_lost_reply(output): receipts = [row for row in logs[actor] if row['event'] in ('remote_receipt', 'document_receipt') and row['revision'] == attempt['revision']] if not receipts and documents: target, = attempt['document_changes'] - confirmed_revision = documents[target]['format']['receipt_revision'] + confirmed_revision = documents[target]['states']['format']['attempt']['receipt_revision'] receipts = [row for row in logs[actor] if row['event'] == 'document_receipt' and row['revision'] == confirmed_revision] assert len(receipts) == 1 and receipts[0]['at_us'] > sample['up_started_us'] peer_progress = {} diff --git a/tools/test_cross_run_edit.py b/tools/test_cross_run_edit.py new file mode 100644 index 0000000000000000000000000000000000000000..245477c7a1335eadc5bbf83ca6d3ca667666310d --- /dev/null +++ b/tools/test_cross_run_edit.py @@ -0,0 +1,25 @@ +import json +from pathlib import Path +import runpy +import shutil +from tempfile import TemporaryDirectory +import unittest + +ROOT = Path(__file__).resolve().parent.parent +FIXTURE = ROOT / 'corpus/cross-run-edit' +compare = runpy.run_path(str(ROOT / 'tools/verify-document.py'))['compare'] + + +class CrossRunEditTest(unittest.TestCase): + def test_cold_native_text_styles_and_structure(self): + for case in json.loads((FIXTURE / 'manifest.json').read_text())['cases']: + with self.subTest(case=case['name']), TemporaryDirectory() as temporary: + fixture = FIXTURE / case['name'] + native = Path(temporary) / 'read' + shutil.copytree(fixture / 'cold/read', native) + compare(fixture / 'candidate', native) + compare(fixture / 'cold/notebook', native) + + +if __name__ == '__main__': + unittest.main() diff --git a/tools/test_native_runner.py b/tools/test_native_runner.py index 3efb3d890b84b6c8b394283699feb11580937164..b48e69836c9e707506555aadea89e6a6e36b7f61 100644 --- a/tools/test_native_runner.py +++ b/tools/test_native_runner.py @@ -1,4 +1,5 @@ import json +import subprocess from pathlib import Path import tempfile import unittest @@ -8,6 +9,26 @@ import native_runner as runner class NativeRunnerTest(unittest.TestCase): + def test_failed_client_build_cannot_start_a_lab_with_stale_binaries(self): + import native_collaboration as collaboration + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + for profile, failed in (('debug', 'debug'), ('release', 'debug'), ('release', 'release')): + output = root / (profile + '-' + failed) + results = [subprocess.CompletedProcess(['cargo'], 1)] + if failed == 'release': results.insert(0, subprocess.CompletedProcess(['cargo'], 0)) + with patch.object(collaboration, 'linux_vm') as linux, \ + patch.object(collaboration.subprocess, 'run', side_effect=results) as build: + linux.instance_path.return_value = root / 'absent' + with self.assertRaises(subprocess.CalledProcessError): + collaboration.replay(output, 'fixture', client_profile=profile) + self.assertEqual(build.call_count, len(results)) + linux.create_instance.assert_not_called() + linux.launch.assert_not_called() + receipt = json.loads((output / f'build-{failed}.json').read_text()) + self.assertEqual(receipt['exit'], 1) + self.assertEqual('--release' in receipt['command'], failed == 'release') + def test_shutdown_timeout_still_removes_the_owned_clone(self): with tempfile.TemporaryDirectory() as temporary: output = Path(temporary) diff --git a/tools/test_offline_confirmation.py b/tools/test_offline_confirmation.py index 47d33448819c4af15e0713a7124068e4c30b247c..c20e9733e75bbb04d9d3cb86927a8214e0fec90f 100644 --- a/tools/test_offline_confirmation.py +++ b/tools/test_offline_confirmation.py @@ -62,14 +62,14 @@ class ConfirmationOracle(unittest.TestCase): markup = 'x]]>' content = xml('Concurrent edits: [w0:0]', 'Native 0:').replace('', markup + '') page.write_text(content) - with patch('offline_document_history.document_history', return_value={'target': {'text': 'x', 'format': {'receipt_revision': 'revision'}}}) as documents: + with patch('offline_document_history.document_history', return_value={'target': {'states': {'format': {'attempt': {'receipt_revision': 'revision'}}}}}) as documents: result = verify(root, cold) self.assertEqual(result['validated_paragraphs'], 3) self.assertEqual(result['native_intended_format_checks'], 3) documents.assert_called_once_with({'w0': rows}, 1) rows[3]['revisions'] = {'space': ['current']} rows[3]['current_revisions'] = {'space': 'current'} - documents.return_value['target']['format']['receipt_revision'] = 'current' + documents.return_value['target']['states']['format']['attempt']['receipt_revision'] = 'current' (root / 'rust/w0.jsonl').write_text('\n'.join(map(json.dumps, rows))) self.assertEqual(verify(root, cold)['confirmed_revision'], 'current') rows[3]['current_revisions'] = {'space': 'unrelated'} diff --git a/tools/test_offline_document_history.py b/tools/test_offline_document_history.py index 4bea175efc829711e9cce266f0558ffde284c3f5..1d51a253ee6ecabb4b75550c963bae971a998871 100644 --- a/tools/test_offline_document_history.py +++ b/tools/test_offline_document_history.py @@ -6,52 +6,92 @@ from unittest.mock import patch from offline_document_history import document_history, identity, verify_model, verify_native +def history(text_edits=False): + logs = {'w0': [{'event': 'ready', 'document_operations': True, 'document_kinds': ['insert', 'format', 'text'] if text_edits else ['insert', 'format']}]} + events = logs['w0'] + observed = {} + previous = None + for operation in range(2): + insertion = {'guid': list(uuid.UUID(int=operation+1).bytes_le), 'text': f'Document w0:{operation} 🦀', + 'parent': 'page' if operation == 0 else identity(previous, 1), 'author': 'Offline document writer', + 'placement': {'Outline': {'x': 144, 'y': 144}} if operation == 0 else {'Paragraph': {'before': None}}} + previous = insertion + target = identity(insertion, 2) + for step, kind in enumerate(('insert', 'format', 'text') if text_edits else ('insert', 'format')): + timestamp = 10 + operation*30 + step*10 + local_id = operation*3 + step + 2 + event = {'event': 'local_document_commit', 'id': local_id, 'operation': operation, 'kind': kind, + 'space': 'space', 'object': target, 'text': insertion['text'], 'insertion': insertion if step == 0 else None, + 'range': [1, len(insertion['text'].encode('utf-16-le'))//2-2], + 'attributes': [{'Bold': True}, {'FontSize': 18+operation}, {'Color': [18, 52, 86]}], + 'started_us': timestamp-2, 'finished_us': timestamp-1} + if kind == 'text': + end = len(insertion['text'].encode('utf-16-le'))//2 + event.update(range=[end-3, end], replacement=' e\u0301🐈') + events.append(event) + value = insertion['text'].replace(' 🦀', ' e\u0301🐈') if kind == 'text' else insertion['text'] + runs = [] + for index, char in enumerate(value): + selected = index > 0 and (kind == 'text' or (kind == 'format' and index < len(value)-1)) + runs.append({'text': char, 'bold': selected, 'size': 18+operation if selected else 11, + 'color': 0x563412 if selected else 0xff000000}) + observed[target] = {'text': value, 'runs': runs} + revision = f'revision-{local_id}' + events.append({'event': 'remote_attempt', 'revision': revision, 'state': 'Committed', + 'document_changes': {target: copy.deepcopy(observed[target])}, + 'documents': copy.deepcopy(observed), 'started_us': timestamp, 'finished_us': timestamp+1}) + events.append({'event': 'document_receipt', 'id': local_id, 'revision': revision, 'at_us': timestamp+2}) + if text_edits: + events.append({'event': 'read', 'started_us': timestamp+3, 'finished_us': timestamp+4, 'documents': copy.deepcopy(observed)}) + events.extend({'event': 'reopened_document_receipt', 'id': row['id'], 'revision': row['revision']} + for row in list(events) if row['event'] == 'document_receipt') + read = {'event': 'read', 'started_us': 100, 'finished_us': 101, 'documents': observed} + events.extend([read, {'event': 'done'}]) + logs['r0'] = [{'event': 'ready'}, copy.deepcopy(read), {'event': 'done'}] + return logs + + class DocumentHistoryTests(unittest.TestCase): def setUp(self): - self.logs = {'w0': [{'event': 'ready', 'document_operations': True}]} - events = self.logs['w0'] - observed = {} - previous = None - for operation in range(2): - insertion = {'guid': list(uuid.UUID(int=operation+1).bytes_le), 'text': f'Document w0:{operation} 🦀', - 'parent': 'page' if operation == 0 else identity(previous, 1), 'author': 'Offline document writer', - 'placement': {'Outline': {'x': 144, 'y': 144}} if operation == 0 else {'Paragraph': {'before': None}}} - previous = insertion - target = identity(insertion, 2) - for step, kind in enumerate(('insert', 'format')): - timestamp = 10 + operation*30 + step*10 - local_id = operation*3 + step + 2 - event = {'event': 'local_document_commit', 'id': local_id, 'operation': operation, 'kind': kind, - 'space': 'space', 'object': target, 'text': insertion['text'], 'insertion': insertion if step == 0 else None, - 'range': [1, len(insertion['text'].encode('utf-16-le'))//2-2], - 'attributes': [{'Bold': True}, {'FontSize': 18+operation}, {'Color': [18, 52, 86]}], - 'started_us': timestamp-2, 'finished_us': timestamp-1} - events.append(event) - runs = [] - for index, char in enumerate(insertion['text']): - selected = kind == 'format' and 0 < index < len(insertion['text'])-1 - runs.append({'text': char, 'bold': selected, 'size': 18+operation if selected else 11, - 'color': 0x563412 if selected else 0xff000000}) - observed[target] = {'text': insertion['text'], 'runs': runs} - revision = f'revision-{local_id}' - events.append({'event': 'remote_attempt', 'revision': revision, 'state': 'Committed', - 'document_changes': {target: copy.deepcopy(observed[target])}, - 'documents': copy.deepcopy(observed), 'started_us': timestamp, 'finished_us': timestamp+1}) - events.append({'event': 'document_receipt', 'id': local_id, 'revision': revision, 'at_us': timestamp+2}) - events.extend({'event': 'reopened_document_receipt', 'id': row['id'], 'revision': row['revision']} - for row in list(events) if row['event'] == 'document_receipt') - read = {'event': 'read', 'started_us': 100, 'finished_us': 101, 'documents': observed} - events.extend([read, {'event': 'done'}]) - self.logs['r0'] = [{'event': 'ready'}, copy.deepcopy(read), {'event': 'done'}] + self.logs = history() def test_document_receipts_and_reader_states_match_the_intents(self): documents = document_history(self.logs, 2) self.assertEqual(len(documents), 2) + expected = [list(row['states'].values())[-1]['characters'] for row in documents.values()] paragraphs = [[(char, {'bold': bold, 'font_size': size, 'color': 'automatic' if color == 0xff000000 else '#123456'}) - for char, bold, size, color in row['new']] for row in documents.values()] - self.assertEqual(verify_native(paragraphs, documents), sum(len(row['new'])*3 for row in documents.values())) + for char, bold, size, color in row] for row in expected] + self.assertEqual(verify_native(paragraphs, expected), sum(len(row)*3 for row in expected)) paragraphs[0][1][1]['font_size'] = 19 - with self.assertRaisesRegex(AssertionError, 'font size'): verify_native(paragraphs, documents) + with self.assertRaisesRegex(AssertionError, 'font size'): verify_native(paragraphs, expected) + + def test_cross_run_text_requires_complete_ordered_states_and_exact_receipts(self): + logs = history(True) + documents = document_history(logs, 2) + for document in documents.values(): + self.assertEqual(list(document['states']), ['insert', 'format', 'text']) + final = document['states']['text']['characters'] + self.assertEqual(''.join(char for char, *_ in final), document['insertion']['text'].replace(' 🦀', ' e\u0301🐈')) + for event, field, value in [('local_document_commit', 'replacement', 'wrong'), + ('local_document_commit', 'range', [0, 1]), + ('document_receipt', 'revision', 'wrong')]: + changed = copy.deepcopy(logs) + row = next(row for row in changed['w0'] if row['event'] == event and + (row.get('kind') == 'text' or row.get('id') == 4)) + row[field] = value + with self.subTest(event=event, field=field), self.assertRaises(AssertionError): + document_history(changed, 2) + changed = copy.deepcopy(logs) + read = changed['r0'][1] + target = next(iter(read['documents'])) + read['documents'][target]['runs'][-1]['bold'] = False + with self.assertRaisesRegex(AssertionError, 'partial or invented'): + document_history(changed, 2) + prior = next(row for row in logs['w0'] if row['event'] == 'read' and row['started_us'] == 23) + changed = copy.deepcopy(logs) + changed['r0'][1]['documents'][target] = copy.deepcopy(prior['documents'][target]) + with self.assertRaisesRegex(AssertionError, 'missed acknowledged'): + document_history(changed, 2) def test_missing_intents_receipts_or_reopen_records_are_rejected(self): for name in ('local_document_commit', 'document_receipt', 'reopened_document_receipt', 'remote_attempt'): @@ -75,10 +115,11 @@ class DocumentHistoryTests(unittest.TestCase): run['size'] = 13.5 run['color'] = 0x665544 documents = document_history(self.logs, 2) + expected = [list(row['states'].values())[-1]['characters'] for row in documents.values()] paragraphs = [[(char, {'bold': bold, 'font_size': size, 'color': '#123456' if color == 0x563412 else '#445566'}) - for char, bold, size, color in row['new']] for row in documents.values()] - self.assertEqual(verify_native(paragraphs, documents), sum(len(row['new'])*3 for row in documents.values())) + for char, bold, size, color in row] for row in expected] + self.assertEqual(verify_native(paragraphs, expected), sum(len(row)*3 for row in expected)) baseline = copy.deepcopy(self.logs) for event, key in [('remote_attempt', 'document_changes'), ('read', 'documents')]: for field, value in [('size', 11), ('color', 0xff000000)]: @@ -102,7 +143,7 @@ class DocumentHistoryTests(unittest.TestCase): events[index:index] = [read, confirmation] result = document_history(self.logs, 2) target, = attempt['document_changes'] - self.assertEqual(result[target]['format']['receipt_revision'], 'current-revision') + self.assertEqual(result[target]['states']['format']['attempt']['receipt_revision'], 'current-revision') for field, value in [('current_revisions', {'space': 'unrelated'}), ('revisions', {'space': ['revision-3', 'current-revision']}), ('state', 'NotCommitted')]: @@ -123,7 +164,7 @@ class DocumentHistoryTests(unittest.TestCase): nodes[outline].update(children=[paragraph, appended], layout={'x': 144, 'y': 144}) for parent, (target, document) in zip([paragraph, appended], documents.items(), strict=True): nodes[parent]['content'] = [target] - nodes[target]['kind'] = {'text': document['text']} + nodes[target]['kind'] = {'text': ''.join(char for char, *_ in list(document['states'].values())[-1]['characters'])} revision = {'nodes': nodes} with patch('offline_document_history.ordered_pages', return_value=[('space', 'revision', revision, 'page')]): verify_model({}, documents) diff --git a/tools/test_offline_outage.py b/tools/test_offline_outage.py index 15ad50c69a0d3f9e62c2f2b86a7c0e8123d1fbf7..d108efb97991ba2fc3590f0b95254249f412e6c2 100644 --- a/tools/test_offline_outage.py +++ b/tools/test_offline_outage.py @@ -85,7 +85,7 @@ class OutageOracle(unittest.TestCase): (self.root / 'rust' / f'{actor}.jsonl').write_text('\n'.join(map(json.dumps, rows))) with patch('offline_history.publication_links') as ledger, patch('offline_document_history.document_history') as documents, patch('offline_outage.verify', return_value={'guarded_pairs': 1}) as overlap: if self.config.get('offline_client_reply'): - documents.return_value = {'target': {'format': {'receipt_revision': 'effect-revision'}}} + documents.return_value = {'target': {'states': {'format': {'attempt': {'receipt_revision': 'effect-revision'}}}}} result = verify_lost_reply(self.root) ledger.assert_called_once_with(self.logs, self.config['stress_operations']) if self.config.get('document_operations'): diff --git a/tools/verify_offline.py b/tools/verify_offline.py index af75cd70087a6ef4fed100d90d9ca4d869237cfb..11016d4ea26eeabc6ee8b9e57282fd57c27ff9d9 100644 --- a/tools/verify_offline.py +++ b/tools/verify_offline.py @@ -32,7 +32,7 @@ def verify(output, max_gap=120): edits = {event['id']: event for event in events if event['event'] in ('local_commit', 'local_document_commit')} receipts = {event['id']: event for event in events if event['event'] in ('remote_receipt', 'document_receipt')} attempts = {event['revision']: event for event in events if event['event'] == 'remote_attempt'} - publication_starts = {id: documents[edit['object']][edit['kind']]['started_us'] if edit['event'] == 'local_document_commit' + publication_starts = {id: documents[edit['object']]['states'][edit['kind']]['attempt']['started_us'] if edit['event'] == 'local_document_commit' else attempts[receipts[id]['revision']]['started_us'] for id, edit in edits.items()} queues[actor] = max(sum(edit['finished_us'] <= at < publication_starts[id] for id, edit in edits.items()) for at in [edit['finished_us'] for edit in edits.values()]) assert queues[actor] >= 2, 'Writer did not establish a durable local queue before publication' @@ -57,7 +57,7 @@ def verify(output, max_gap=120): assert times == sorted(times) progress[f'n{i}'] = max((b-a for a, b in zip(times, times[1:])), default=0) / 10_000_000 assert all(gap <= max_gap for gap in progress.values()), f'Client progress exceeded {max_gap}s: {progress}' - return {'remote_publications': len(commits), 'document_publications': len(documents)*2, 'remote_text_sha256': hashlib.sha256(text.encode()).hexdigest(), 'maximum_progress_gap_seconds': progress, + return {'remote_publications': len(commits), 'document_publications': sum(len(document['states']) for document in documents.values()), 'remote_text_sha256': hashlib.sha256(text.encode()).hexdigest(), 'maximum_progress_gap_seconds': progress, 'queued_before_publication_lower_bound': queues, 'reviewed_placements': sum(event['event'] == 'reviewed_append' for events in logs.values() for event in events), 'caches': caches} diff --git a/tools/verify_offline_confirmation.py b/tools/verify_offline_confirmation.py index 2ae74fb22e13fd771d2656f69eff2f3db983777d..8e8ea4823da3bc79a8e96827a048773007f06f56 100644 --- a/tools/verify_offline_confirmation.py +++ b/tools/verify_offline_confirmation.py @@ -68,9 +68,8 @@ def verify(output, cold, *, partial=False): paragraphs.remove(expected) if observed: from offline_document_history import characters, verify_native - expected_documents = {target: {'text': documents[target]['text'], 'new': characters(value)} for target, value in observed.items()} - format_checks += verify_native(formatted, expected_documents) - for document in expected_documents.values(): + format_checks += verify_native(formatted, (characters(value) for value in observed.values())) + for document in observed.values(): assert paragraphs.count(document['text']) == 1, 'Confirmation duplicated a document paragraph' paragraphs.remove(document['text']) for index, versions in enumerate(native): @@ -78,7 +77,7 @@ def verify(output, cold, *, partial=False): assert len(selected) == 1 and selected[0] in versions, 'Native confirmation image contains an unrecorded edit or loses a prefix' checks += len(native) + 1 + len(observed) assert json.loads((cold / 'teardown.json').read_text()) == {'absent': True} - confirmed_revision = documents[next(iter(attempt['document_changes']))]['format']['receipt_revision'] if documents else attempt['revision'] + confirmed_revision = documents[next(iter(attempt['document_changes']))]['states']['format']['attempt']['receipt_revision'] if documents else attempt['revision'] return {'complete_workload': not partial, 'attempted_revision': attempt['revision'], 'confirmed_revision': confirmed_revision, 'native_images': len(results), 'exact_rust_paragraphs': len(results), 'validated_paragraphs': checks, 'native_intended_format_checks': format_checks, 'maximum_native_export_seconds': max(row['seconds'] for row in results)} -- 2.54.0