authorclover caruso <git@paperclover.net> 2026-10-15 09:37:12-07:00
committerclover caruso <git@paperclover.net> 2026-10-15 09:52:49-07:00
log08dbdad9e4fb203c874a7035cc77f5e4db5bc7b8
treef88b2d70bf82dcc2e1ca2c4dc9c691858c059ba7
parent171ffc3acdb21c0fe1b49e13481ce7f7f83fbec1
signature Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU (~clover)

test(mobile): count each page arrangement as one durable queued edit

pages_drag_indent_and_undo_as_one_edit_each read the replica queue straight after an arrange. Since edits are written by the section thread, the page list can answer from memory before the burst that holds the edit is written, so under load the queue still read empty; a sync step publishing the edit could drain it as well. The test now works offline from the start and waits for written() before counting, and counts every arrangement and the no-op rather than only the first. notebook_open takes the offline flag so the section never opens online, which also closes the same window in unpublished_edit, which went offline only after its session had started a sync step. Under load the old test failed 7 times in 1200 runs; the new one passed 2400 of 2400. Assisted-by: claude-opus-5.5

1 files changed, 31 insertions(+), 24 deletions(-)

crates/mobile/src/tests.rs+31-24
...@@ -1173,10 +1173,14 @@ fn a_share_notebook_names_its_files_from_the_share_s_top() {...@@ -1173,10 +1173,14 @@ fn a_share_notebook_names_its_files_from_the_share_s_top() {
1173 );1173 );
1174}1174}
11751175
1176/// A notebook copy with its library, and a section of it open for editing.1176/// A notebook copy with its library, and a section of it open for editing. Working
1177fn notebook_open(path: &str) -> (tempfile::TempDir, PathBuf, Arc<Library>, Section) {1177/// `offline` from the start, no sync step can publish the section's edits.
1178fn notebook_open(path: &str, offline: bool) -> (tempfile::TempDir, PathBuf, Arc<Library>, Section) {
1178 let (directory, root) = copy("m6/native-features-01/notebook");1179 let (directory, root) = copy("m6/native-features-01/notebook");
1179 let library = Arc::new(Library::open(&root, &directory.path().join("cache"), true).unwrap());1180 let library = Arc::new(Library::open(&root, &directory.path().join("cache"), true).unwrap());
1181 if offline {
1182 sb_library_set_offline(&library, true);
1183 }
1180 let section = open(&library, path);1184 let section = open(&library, path);
1181 (directory, root, library, section)1185 (directory, root, library, section)
1182}1186}
...@@ -1195,7 +1199,7 @@ fn eventually(mut ready: impl FnMut() -> bool) -> bool {...@@ -1195,7 +1199,7 @@ fn eventually(mut ready: impl FnMut() -> bool) -> bool {
11951199
1196#[test]1200#[test]
1197fn sync_status_lists_every_section_and_work_offline_holds_edits_until_sync_now() {1201fn sync_status_lists_every_section_and_work_offline_holds_edits_until_sync_now() {
1198 let (_directory, root, library, section) = notebook_open("Features.one");1202 let (_directory, root, library, section) = notebook_open("Features.one", false);
1199 // Every readable section, the open one's from its session.1203 // Every readable section, the open one's from its session.
1200 assert!(eventually(|| {1204 assert!(eventually(|| {
1201 let status = library.sync_status();1205 let status = library.sync_status();
...@@ -1499,8 +1503,7 @@ fn the_accent_pen_and_shapes_draw_as_the_desktop_and_a_cancelled_sweep_stores_no...@@ -1499,8 +1503,7 @@ fn the_accent_pen_and_shapes_draw_as_the_desktop_and_a_cancelled_sweep_stores_no
1499/// Queues an offline edit to Features.one and closes its session, leaving the edit to the1503/// Queues an offline edit to Features.one and closes its session, leaving the edit to the
1500/// background; the replicas in the cache, and the text edited in.1504/// background; the replicas in the cache, and the text edited in.
1501fn unpublished_edit() -> (tempfile::TempDir, PathBuf, Arc<Library>, ExGuid) {1505fn unpublished_edit() -> (tempfile::TempDir, PathBuf, Arc<Library>, ExGuid) {
1502 let (directory, root, library, section) = notebook_open("Features.one");1506 let (directory, root, library, section) = notebook_open("Features.one", true);
1503 sb_library_set_offline(&library, true);
1504 let mut canvas = canvas(&section, "Paragraph controls");1507 let mut canvas = canvas(&section, "Paragraph controls");
1505 focus(&mut canvas, "Collapsed parent");1508 focus(&mut canvas, "Collapsed parent");
1506 canvas.insert("Unpublished ".into()).unwrap();1509 canvas.insert("Unpublished ".into()).unwrap();
...@@ -1699,34 +1702,38 @@ fn levels(section: &Section) -> Vec<(ExGuid, u32)> {...@@ -1699,34 +1702,38 @@ fn levels(section: &Section) -> Vec<(ExGuid, u32)> {
16991702
1700#[test]1703#[test]
1701fn pages_drag_indent_and_undo_as_one_edit_each() {1704fn pages_drag_indent_and_undo_as_one_edit_each() {
1702 let (_directory, _root, _library, section) = notebook_open("Features.one");1705 let (_directory, _root, _library, section) = notebook_open("Features.one", true);
1706 let queued = || {
1707 section.shared.section.written().unwrap();
1708 section.shared.section.pending().unwrap().len()
1709 };
1703 let before = levels(&section);1710 let before = levels(&section);
1704 assert!(before.len() > 2);1711 assert!(before.len() > 2);
1705 let last = before.last().unwrap().0;1712 let last = before.last().unwrap().0;
1706 let mut dragged = before.clone();1713 let mut dragged = before.clone();
1707 let moved = dragged.pop().unwrap();1714 let moved = dragged.pop().unwrap();
1708 dragged.insert(0, moved);1715 dragged.insert(0, moved);
1709 let revisions = section.shared.section.pending().unwrap().len();1716 let mut edits = queued();
1710 assert!(section.arrange(&dragged, &[last]).unwrap());1717 let mut arrange = |order: &[(ExGuid, u32)], moved: ExGuid| {
1711 assert_eq!(levels(&section), dragged);1718 assert!(section.arrange(order, &[moved]).unwrap());
1712 assert_eq!(1719 assert_eq!(levels(&section), order);
1713 section.shared.section.pending().unwrap().len(),1720 edits += 1;
1714 revisions + 11721 assert_eq!(queued(), edits);
1715 );1722 };
1723 arrange(&dragged, last);
1716 let second = dragged[1].0;1724 let second = dragged[1].0;
1717 let mut indented = dragged.clone();1725 let mut indented = dragged.clone();
1718 indented[1].1 = 2;1726 indented[1].1 = 2;
1719 assert!(section.arrange(&indented, &[second]).unwrap());1727 arrange(&indented, second);
1720 assert_eq!(levels(&section), indented);1728 arrange(&dragged, second);
1721 assert!(section.arrange(&dragged, &[second]).unwrap());1729 arrange(&before, last);
1722 assert!(section.arrange(&before, &[last]).unwrap());
1723 assert_eq!(levels(&section), before);
1724 assert!(!section.arrange(&before, &[ExGuid::default()]).unwrap());1730 assert!(!section.arrange(&before, &[ExGuid::default()]).unwrap());
1731 assert_eq!(queued(), edits);
1725}1732}
17261733
1727#[test]1734#[test]
1728fn a_page_moves_to_another_section_and_back_where_it_was() {1735fn a_page_moves_to_another_section_and_back_where_it_was() {
1729 let (_directory, root, library, features) = notebook_open("Features.one");1736 let (_directory, root, library, features) = notebook_open("Features.one", false);
1730 let before = levels(&features);1737 let before = levels(&features);
1731 let (space, level) = before[1];1738 let (space, level) = before[1];
1732 let title = features.shared.section.page(space).unwrap().title;1739 let title = features.shared.section.page(space).unwrap().title;
...@@ -1785,7 +1792,7 @@ fn a_page_moves_to_another_section_and_back_where_it_was() {...@@ -1785,7 +1792,7 @@ fn a_page_moves_to_another_section_and_back_where_it_was() {
1785fn a_page_edited_while_it_moves_stays_with_its_edit() {1792fn a_page_edited_while_it_moves_stays_with_its_edit() {
1786 library::sb_set_coordinator(coordinator);1793 library::sb_set_coordinator(coordinator);
1787 for edited in [true, false] {1794 for edited in [true, false] {
1788 let (_directory, root, library, features) = notebook_open("Features.one");1795 let (_directory, root, library, features) = notebook_open("Features.one", false);
1789 assert!(eventually(|| {1796 assert!(eventually(|| {
1790 let status = library.sync_status();1797 let status = library.sync_status();
1791 !status.is_empty()1798 !status.is_empty()
...@@ -1861,7 +1868,7 @@ fn a_page_its_section_cannot_retire_stays_beside_its_copy() {...@@ -1861,7 +1868,7 @@ fn a_page_its_section_cannot_retire_stays_beside_its_copy() {
1861 let _exclusive = vfs::EXCLUSIVE1868 let _exclusive = vfs::EXCLUSIVE
1862 .lock()1869 .lock()
1863 .unwrap_or_else(std::sync::PoisonError::into_inner);1870 .unwrap_or_else(std::sync::PoisonError::into_inner);
1864 let (directory, root, library, empty) = notebook_open("Empty.one");1871 let (directory, root, library, empty) = notebook_open("Empty.one", false);
1865 let features = open(&library, "Features.one");1872 let features = open(&library, "Features.one");
1866 let space = space(&features, "Task controls");1873 let space = space(&features, "Task controls");
1867 drop(features);1874 drop(features);
...@@ -1908,7 +1915,7 @@ fn a_page_its_section_cannot_retire_stays_beside_its_copy() {...@@ -1908,7 +1915,7 @@ fn a_page_its_section_cannot_retire_stays_beside_its_copy() {
1908/// A page the section it moves to refuses stays where it was, and the move fails.1915/// A page the section it moves to refuses stays where it was, and the move fails.
1909#[test]1916#[test]
1910fn a_page_the_other_section_refuses_stays() {1917fn a_page_the_other_section_refuses_stays() {
1911 let (_directory, _root, library, features) = notebook_open("Features.one");1918 let (_directory, _root, library, features) = notebook_open("Features.one", false);
1912 let before = levels(&features);1919 let before = levels(&features);
1913 let (space, _) = before[1];1920 let (space, _) = before[1];
1914 let page = features.shared.section.page(space).unwrap();1921 let page = features.shared.section.page(space).unwrap();
...@@ -1938,7 +1945,7 @@ fn a_page_the_other_section_refuses_stays() {...@@ -1938,7 +1945,7 @@ fn a_page_the_other_section_refuses_stays() {
19381945
1939#[test]1946#[test]
1940fn a_sections_last_page_moving_leaves_a_page_that_undo_takes_out() {1947fn a_sections_last_page_moving_leaves_a_page_that_undo_takes_out() {
1941 let (_directory, _root, library, features) = notebook_open("Features.one");1948 let (_directory, _root, library, features) = notebook_open("Features.one", false);
1942 let date = ["Thursday, October 1, 2026", "9:02 AM"];1949 let date = ["Thursday, October 1, 2026", "9:02 AM"];
1943 let page = features.new_page(None, date[0], date[1]).unwrap();1950 let page = features.new_page(None, date[0], date[1]).unwrap();
1944 let arrived: ExGuid = library1951 let arrived: ExGuid = library
...@@ -1989,7 +1996,7 @@ fn a_sections_last_page_moving_leaves_a_page_that_undo_takes_out() {...@@ -1989,7 +1996,7 @@ fn a_sections_last_page_moving_leaves_a_page_that_undo_takes_out() {
19891996
1990#[test]1997#[test]
1991fn sections_reorder_and_move_between_groups_and_back() {1998fn sections_reorder_and_move_between_groups_and_back() {
1992 let (_directory, root, library, features) = notebook_open("Features.one");1999 let (_directory, root, library, features) = notebook_open("Features.one", false);
1993 drop(features);2000 drop(features);
1994 let top = |library: &Library| -> Vec<String> {2001 let top = |library: &Library| -> Vec<String> {
1995 let tabs = library.tabs().unwrap();2002 let tabs = library.tabs().unwrap();