1#![cfg(feature = "live")]
2
3use notebook::{
4 Replica,
5 live::share::{HostedRemote, Sharing},
6};
7use onestore::op::{Edit, Op, PageOp};
8use std::sync::{Arc, Barrier};
9
10#[path = "support/live.rs"]
11mod live;
12use live::*;
13
14#[test]
15fn a_lost_batch_receipt_is_confirmed_without_repeating_its_edit() {
16 use notebook::{PendingEdit, Remote};
17 use onestore::{CommitError, CommitState, ExGuid, Stamp, Transaction};
18 use std::{collections::BTreeMap, io};
19
20 struct Lost(HostedRemote, bool);
21 impl Remote for Lost {
22 fn read(&mut self) -> io::Result<Vec<u8>> {
23 self.0.read()
24 }
25 fn stamp(&mut self) -> io::Result<Stamp> {
26 self.0.stamp()
27 }
28 fn publish(&mut self, transaction: &Transaction) -> Result<(), CommitError> {
29 self.0.publish(transaction)
30 }
31 fn confirm(&mut self, stamp: &Stamp) -> Result<(), CommitError> {
32 self.0.confirm(stamp)
33 }
34 fn accepts_edits(&self) -> bool {
35 true
36 }
37 fn publish_edits(
38 &mut self,
39 transaction: &Transaction,
40 edits: &[PendingEdit],
41 revisions: &BTreeMap<ExGuid, ExGuid>,
42 ) -> Result<(), CommitError> {
43 self.0.publish_edits(transaction, edits, revisions)?;
44 if std::mem::take(&mut self.1) {
45 return Err(CommitError {
46 state: CommitState::Unknown,
47 error: io::ErrorKind::BrokenPipe.into(),
48 });
49 }
50 Ok(())
51 }
52 }
53 let directory = tempfile::tempdir().unwrap();
54 let folder = notebook(directory.path());
55 let url = relay(Default::default());
56 let sharing = Sharing::new("").unwrap();
57 let host = host(&folder, &directory.path().join("host"), &sharing, &url);
58 let (guest, _) = guest("Alice", &code(&host), &url, &directory.path().join("alice"));
59 let file = folder.join("Garden.one");
60 let image = std::fs::read(&file).unwrap();
61 let (space, text, _) = server::text(&image);
62 let replica = Replica::open_or_create(directory.path().join("replica.sqlite"), None, || {
63 Ok(image.clone())
64 })
65 .unwrap();
66 let id = replica
67 .apply(
68 "Alice",
69 Edit {
70 at: 134_000_000_000_000_000,
71 ops: vec![Op::Page {
72 space,
73 op: PageOp::Text {
74 text,
75 range: 13..13,
76 with: "X".into(),
77 },
78 }],
79 },
80 )
81 .unwrap();
82 let mut remote = Lost(HostedRemote::new(&guest, "Garden.one"), true);
83 assert!(matches!(
84 replica.sync_once(&mut remote),
85 Err(notebook::Error::Remote(CommitError {
86 state: CommitState::Unknown,
87 ..
88 }))
89 ));
90 replica.sync_once(&mut remote).unwrap();
91 assert!(matches!(
92 replica.status(id).unwrap(),
93 Some(notebook::EditStatus::Published { .. })
94 ));
95 assert_eq!(
96 server::text(&std::fs::read(file).unwrap()).2,
97 "Original textX"
98 );
99 assert!(replica.pending().unwrap().is_empty());
100}
101
102#[test]
103fn concurrent_batches_keep_receipts_and_rebase_later_keystrokes() {
104 let directory = tempfile::tempdir().unwrap();
105 let folder = notebook(directory.path());
106 let url = relay(Default::default());
107 let sharing = Sharing::new("").unwrap();
108 let host = host(&folder, &directory.path().join("host"), &sharing, &url);
109 let code = code(&host);
110 let (alice, _) = guest("Alice", &code, &url, &directory.path().join("alice"));
111 let (bob, _) = guest("Bob", &code, &url, &directory.path().join("bob"));
112 let file = folder.join("Garden.one");
113 let original = std::fs::read(&file).unwrap();
114 let (space, text, _) = server::text(&original);
115 let edit = |with: &str, at| Edit {
116 at: 134_000_000_000_000_000,
117 ops: vec![Op::Page {
118 space,
119 op: PageOp::Text {
120 text,
121 range: at..at,
122 with: with.into(),
123 },
124 }],
125 };
126 let a = Arc::new(
127 Replica::open_or_create(directory.path().join("a.sqlite"), None, || {
128 Ok(original.clone())
129 })
130 .unwrap(),
131 );
132 let b = Arc::new(
133 Replica::open_or_create(directory.path().join("b.sqlite"), None, || {
134 Ok(original.clone())
135 })
136 .unwrap(),
137 );
138 let first = a.apply("Alice", edit("A", 13)).unwrap();
139 let second = b.apply("Bob", edit("B", 0)).unwrap();
140 let barrier = Arc::new(Barrier::new(2));
141 std::thread::scope(|scope| {
142 for (replica, guest) in [(&a, &alice), (&b, &bob)] {
143 let barrier = Arc::clone(&barrier);
144 scope.spawn(move || {
145 barrier.wait();
146 replica
147 .sync_once(&mut HostedRemote::new(guest, "Garden.one"))
148 .unwrap();
149 });
150 }
151 });
152 assert!(matches!(
153 a.status(first).unwrap(),
154 Some(notebook::EditStatus::Published { .. })
155 ));
156 assert!(matches!(
157 b.status(second).unwrap(),
158 Some(notebook::EditStatus::Published { .. })
159 ));
160 let next = server::text(&a.snapshot().unwrap()).2.find('A').unwrap() as u32 + 1;
161 let third = a.apply("Alice", edit("a", next)).unwrap();
162 let mut remote = HostedRemote::new(&alice, "Garden.one");
163 for _ in 0..5 {
164 a.sync_once(&mut remote).unwrap();
165 if matches!(
166 a.status(third).unwrap(),
167 Some(notebook::EditStatus::Published { .. })
168 ) {
169 break;
170 }
171 }
172 let result = std::fs::read(&file).unwrap();
173 let result_text = server::text(&result).2;
174 assert!(result_text.contains("Aa"), "{result_text}");
175 assert!(result_text.contains('B'), "{result_text}");
176 let arena = onestore::Arena::default();
177 let mut section = onestore::Section::open(&arena, result).unwrap();
178 assert!(section.conflicts().unwrap().is_empty());
179}