Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_ore/src/seq/forget_tests.rs

10.5 KiB, 3 runs

created by r1870400018:38194, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! Forgetting: an operation loses its content and keeps its shape, and every
2//! replica renders the same whether it still holds the bytes or only the stub.
3//!
4//! The property under test is the one the whole design rests on -- that the
5//! render is a function of the operation set, forgets included, and of nothing
6//! else. So every case is run under every delivery order, and the case that
7//! matters most puts an original on one replica and a repack's stub on another
8//! and asks for byte equality.
9
10use crate::id::{
11 OpId,
12 ReplicaId,
13};
14use crate::op::{
15 Header,
16 Op,
17 Placing,
18 Stub,
19};
20use crate::seq::tests::{
21 case,
22 converge,
23 seed,
24 Replica,
25};
26use crate::seq::Sequence;
27
28use oxedyne_fe2o3_core::prelude::*;
29
30
31/// A forget of an operation, as a command would author it: the shape read off
32/// the operation itself.
33fn forget_of(rep: &mut Replica, targets: &[(OpId, &Op)])
34 -> Outcome<(Header, Op)>
35{
36 let mut of: Vec<Stub> = Vec::new();
37 for (id, op) in targets {
38 let placing = res!(op.stub_of().ok_or_else(|| err!(
39 "{} has nothing to forget", op.name(); Test)));
40 of.push(Stub { id: *id, placing });
41 }
42 of.sort_by_key(|s| s.id);
43 rep.author(Op::Forget { of, reason: b"a test".to_vec(), time: 1_755_000_000 })
44}
45
46/// A forgotten insertion's bytes are gone from every state, and an edit made
47/// inside it afterwards still renders where it was made.
48#[test]
49fn a_forgotten_insertion_is_buried_and_what_was_anchored_in_it_survives() -> Outcome<()> {
50 let mut st = res!(seed(b"one two three\n", 1));
51 let rep = &mut st.reps[0];
52 // The secret goes in, and a word is then written inside it.
53 let secret = res!(rep.insert(4, b"SECRET "));
54 let inside = res!(rep.insert(4 + 3, b"[x]"));
55 assert_eq!(res!(rep.view()).text_lossy(), "one SEC[x]RET two three\n");
56 let forget = res!(forget_of(rep, &[(secret.0.id(), &secret.1)]));
57 let mut ops = st.ops.clone();
58 ops.extend([secret, inside, forget]);
59 // The secret's bytes are gone; the word written inside it stands where the
60 // secret stood, since its anchors still name the buried bytes.
61 let got = res!(case(st.file, "one [x]two three\n", &ops));
62 assert!(!got.bytes().windows(6).any(|w| w == b"SECRET"), "the bytes are gone");
63 Ok(())
64}
65
66/// A replica holding the original beside the forget and one holding only the
67/// stub a repack wrote render byte for byte the same, in every delivery order.
68#[test]
69fn a_stub_and_an_original_render_the_same() -> Outcome<()> {
70 let mut st = res!(seed(b"alpha beta\n", 1));
71 let rep = &mut st.reps[0];
72 let secret = res!(rep.insert(6, b"KEY=abc "));
73 let after = res!(rep.insert(6 + 8, b"gamma "));
74 let forget = res!(forget_of(rep, &[(secret.0.id(), &secret.1)]));
75 let placing = res!(secret.1.stub_of().ok_or_else(|| err!("no shape"; Test)));
76
77 let mut with_original = st.ops.clone();
78 with_original.extend([secret.clone(), after.clone(), forget.clone()]);
79 let mut with_stub = st.ops.clone();
80 with_stub.extend([
81 (secret.0.clone(), Op::Forgotten { placing }),
82 after,
83 forget,
84 ]);
85 let a = res!(case(st.file, "alpha gamma beta\n", &with_original));
86 let b = res!(case(st.file, "alpha gamma beta\n", &with_stub));
87 assert_eq!(a.bytes(), b.bytes());
88 assert_eq!(a.runs(), b.runs(), "and the same provenance runs");
89 Ok(())
90}
91
92/// A forgotten file has no path and is not live, its content is gone, and an
93/// edit into it afterwards is complete rather than an error.
94#[test]
95fn a_forgotten_file_is_dead_from_birth() -> Outcome<()> {
96 let mut st = res!(seed(b"public\n", 1));
97 let rep = &mut st.reps[0];
98 let create = res!(rep.author(Op::FileCreate { path: b"keys/prod.pem".to_vec() }));
99 let key = create.0.id();
100 let fill = {
101 let repo = res!(rep.seq.render());
102 let f = res!(repo.file(key).ok_or_else(|| err!("no file"; Test)));
103 let op = res!(f.splice(0, 0, b"-----BEGIN".to_vec()));
104 res!(rep.author(op))
105 };
106 let forget = res!(forget_of(rep, &[(key, &create.1), (fill.0.id(), &fill.1)]));
107 let late = {
108 let repo = res!(rep.seq.render());
109 let f = res!(repo.file(key).ok_or_else(|| err!("no file after forget"; Test)));
110 let op = res!(f.splice(0, 0, b"late".to_vec()));
111 res!(rep.author(op))
112 };
113 let mut ops = st.ops.clone();
114 ops.extend([create, fill, forget, late]);
115 let repo = res!(converge(&ops));
116 let f = res!(repo.file(key).ok_or_else(|| err!("the file is still a file"; Test)));
117 assert!(!f.is_live(), "and not a live one");
118 assert!(f.path().is_empty(), "with no path to be laid out under");
119 assert!(!f.bytes().windows(5).any(|w| w == b"BEGIN"), "its content gone");
120 let public = res!(repo.file(st.file).ok_or_else(|| err!("no public file"; Test)));
121 assert_eq!(public.text_lossy(), "public\n", "the other file untouched");
122 Ok(())
123}
124
125/// A forget arriving before the operation it names holds the shape ready, and
126/// the original arriving later is held in that shape and not as itself.
127#[test]
128fn a_forget_arriving_first_still_shapes_what_comes_after() -> Outcome<()> {
129 let mut st = res!(seed(b"ab\n", 1));
130 let rep = &mut st.reps[0];
131 let secret = res!(rep.insert(1, b"XX"));
132 let forget = res!(forget_of(rep, &[(secret.0.id(), &secret.1)]));
133 let mut seq = Sequence::new();
134 for (h, o) in &st.ops {
135 res!(seq.apply(h.clone(), o.clone()));
136 }
137 res!(seq.apply(forget.0.clone(), forget.1.clone()));
138 res!(seq.apply(secret.0.clone(), secret.1.clone()));
139 match seq.get(&secret.0.id()) {
140 Some(Op::Forgotten { .. }) => (),
141 other => return Err(err!(
142 "the original is held as {:?}, not as its stub", other.map(|o| o.name()); Test)),
143 }
144 let repo = res!(seq.render());
145 let f = res!(repo.file(st.file).ok_or_else(|| err!("no file"; Test)));
146 assert_eq!(f.text_lossy(), "ab\n");
147 Ok(())
148}
149
150/// Two forgets disagreeing about the shape one operation keeps are refused,
151/// because a forgotten operation keeps one shape everywhere.
152#[test]
153fn two_forgets_disagreeing_on_a_shape_are_refused() -> Outcome<()> {
154 let mut st = res!(seed(b"ab\n", 2));
155 let secret = res!(st.reps[0].insert(1, b"XX"));
156 res!(st.reps[1].recv(secret.clone()));
157 let honest = res!(forget_of(&mut st.reps[0], &[(secret.0.id(), &secret.1)]));
158 // The second replica claims a different shape for the same operation.
159 let dishonest = res!(st.reps[1].author(Op::Forget {
160 of: vec![Stub { id: secret.0.id(), placing: Placing::Void }],
161 reason: Vec::new(),
162 time: 0,
163 }));
164 let mut seq = Sequence::new();
165 for (h, o) in st.ops.iter().chain([&secret, &honest]) {
166 res!(seq.apply(h.clone(), o.clone()));
167 }
168 assert!(seq.apply(dishonest.0.clone(), dishonest.1.clone()).is_err());
169 // And the other way about, through absorb.
170 let mut other = Sequence::new();
171 for (h, o) in st.ops.iter().chain([&secret, &dishonest]) {
172 res!(other.apply(h.clone(), o.clone()));
173 }
174 assert!(seq.absorb(&other).is_err());
175 Ok(())
176}
177
178/// The source of a move can be forgotten, and the moved bytes are gone from the
179/// destination they were carried to, in every delivery order.
180#[test]
181fn qa_forgetting_a_moved_ranges_source_buries_it_at_the_destination() -> Outcome<()> {
182 let mut st = res!(seed(b"one two three\n", 1));
183 let rep = &mut st.reps[0];
184 // The secret goes in, is then carried to the front, and is then forgotten.
185 let secret = res!(rep.insert(4, b"SECRET "));
186 assert_eq!(res!(rep.view()).text_lossy(), "one SECRET two three\n");
187 let mv = {
188 let op = res!(res!(rep.view()).move_range(4, 7, 0));
189 res!(rep.author(op))
190 };
191 assert_eq!(res!(rep.view()).text_lossy(), "SECRET one two three\n");
192 let forget = res!(forget_of(rep, &[(secret.0.id(), &secret.1)]));
193 let mut ops = st.ops.clone();
194 ops.extend([secret, mv, forget]);
195 // The bytes are buried wherever they came to rest, so the file is what it was.
196 let got = res!(case(st.file, "one two three\n", &ops));
197 assert!(!got.bytes().windows(6).any(|w| w == b"SECRET"), "the bytes are gone");
198 Ok(())
199}
200
201/// A deletion that spans the junction of a forgotten insertion and the text after
202/// it keeps the part of the cut that fell outside the buried bytes.
203#[test]
204fn qa_a_cut_across_a_forgotten_boundary_still_removes_the_outside_byte() -> Outcome<()> {
205 let mut st = res!(seed(b"abc\n", 1));
206 let rep = &mut st.reps[0];
207 let secret = res!(rep.insert(1, b"SECRET"));
208 assert_eq!(res!(rep.view()).text_lossy(), "aSECRETbc\n");
209 // Cut "Tb": the last byte of the secret and the first original byte after it.
210 let cut = {
211 let op = res!(res!(rep.view()).splice(6, 2, Vec::new()));
212 res!(rep.author(op))
213 };
214 assert_eq!(res!(rep.view()).text_lossy(), "aSECREc\n");
215 let forget = res!(forget_of(rep, &[(secret.0.id(), &secret.1)]));
216 let mut ops = st.ops.clone();
217 ops.extend([secret, cut, forget]);
218 // The secret is buried whole; the cut's removal of the original 'b' still
219 // stands, since forgetting bytes does not bring back what a later edit took.
220 res!(case(st.file, "ac\n", &ops));
221 Ok(())
222}
223
224/// The bytes two concurrent moves both claim can be forgotten, and conservation
225/// holds although one move yields the run to the other, in every delivery order.
226#[test]
227fn qa_forgetting_a_run_two_moves_contend_for_conserves() -> Outcome<()> {
228 let mut st = res!(seed(b"- Eggs\n- Milk\n- Cheese\n", 2));
229 let seed_op = st.ops[1].clone(); // the splice that wrote the list
230 let (r1, r2) = st.reps.split_at_mut(1);
231 // Both replicas move "- Milk\n" (seed bytes 7..14); one wins, one yields.
232 let lost = {
233 let op = res!(res!(r1[0].view()).move_range(7, 7, 0));
234 res!(r1[0].author(op))
235 };
236 let won = {
237 let op = res!(res!(r2[0].view()).move_range(7, 7, 22));
238 res!(r2[0].author(op))
239 };
240 let forget = res!(forget_of(&mut r1[0], &[(seed_op.0.id(), &seed_op.1)]));
241 let mut ops = st.ops.clone();
242 ops.extend([lost, won, forget]);
243 // Every byte the seed wrote is buried, so the file is empty; the overlap of the
244 // two moves must still balance under conservation, which `case` checks in every
245 // delivery order.
246 let got = res!(case(st.file, "", &ops));
247 assert!(got.bytes().is_empty(), "the whole list is buried");
248 Ok(())
249}
250
251/// What each kind of operation keeps when forgotten, and which have nothing to
252/// forget.
253#[test]
254fn each_kind_keeps_the_shape_the_rule_says() -> Outcome<()> {
255 let r = ReplicaId::new(1);
256 let file = OpId::new(r, 1);
257 for op in crate::op::tests::samples() {
258 let kept = op.stub_of();
259 match &op {
260 Op::FileCreate { .. } => assert_eq!(kept, Some(Placing::File), "{}", op.name()),
261 Op::Splice { left, right, remove, insert } => assert_eq!(kept, Some(Placing::Splice {
262 left: left.clone(), right: right.clone(), remove: remove.clone(),
263 len: insert.len() as u64,
264 }), "{}", op.name()),
265 Op::FileRename { .. } | Op::Mark { .. } | Op::Note { .. } | Op::Proposal { .. }
266 | Op::Said { .. } | Op::Amended { .. }
267 => assert_eq!(kept, Some(Placing::Void), "{}", op.name()),
268 _ => assert_eq!(kept, None, "{} has nothing to forget", op.name()),
269 }
270 }
271 let _ = file;
272 Ok(())
273}