Oregami
Repositories/oxedyne/ore

oxedyne/ore/cli/tests/review.rs

13.8 KiB, 1 run

created by r2848102244:136, 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//! The review surface: where a flag's content is, what the last sync brought,
2//! and which flags have been put down.
3//!
4//! Each of the three answers something the self-hosting trial found missing --
5//! flag coordinates a reviewer could act on, a delta since the last sync, and a
6//! reviewed state so that the flag list stops growing
7//! (`self_hosting_trial.md` §2.2, §2.5, §3 item 3).
8//!
9//! # Coordinates are asserted by construction
10//!
11//! Every file a coordinate test uses is written here, line by line, so the line
12//! a flag should name is known before the tool is run rather than read off what
13//! the tool said. A test that copied the output would pass over a coordinate
14//! that was off by one in every case.
15
16mod support;
17
18use support::{
19 ore,
20 write,
21 Ran,
22 Scratch,
23};
24
25use oxedyne_fe2o3_core::prelude::*;
26
27use std::fs;
28use std::path::{
29 Path,
30 PathBuf,
31};
32
33
34/// Five lines, so that the third is unambiguously the third.
35const FIVE: &[u8] = b"line one\nline two\nline three\nline four\nline five\n";
36
37/// The same five lines with the third replaced, as one replica writes it.
38const FIVE_A: &[u8] = b"line one\nline two\nTHIRD FROM A\nline four\nline five\n";
39
40/// And as the other writes it.
41const FIVE_B: &[u8] = b"line one\nline two\nthird from b\nline four\nline five\n";
42
43
44/// Runs `ore sync <other>` and fails the test if it did not succeed.
45fn synced(dir: &Path, other: &Path)
46 -> Outcome<String>
47{
48 let out = res!(ore(dir, &["sync", &fmt!("{}", other.display())]));
49 Ok(fmt!("{}", res!(out.good("sync"))))
50}
51
52/// Runs a verb and fails the test if it did not succeed.
53fn ran(dir: &Path, args: &[&str])
54 -> Outcome<String>
55{
56 let out: Ran = res!(ore(dir, args));
57 Ok(fmt!("{}", res!(out.good(&args.join(" ")))))
58}
59
60/// Makes two replicas of one repository holding `text` at `file`.
61fn twinned(scratch: &Scratch, file: &str, text: &[u8])
62 -> Outcome<(PathBuf, PathBuf)>
63{
64 let a = res!(scratch.sub("a"));
65 let b = res!(scratch.sub("b"));
66 res!(res!(ore(&a, &["init"])).good("init"));
67 res!(write(&a, file, text));
68 res!(res!(ore(&a, &["mark", "base"])).good("mark"));
69 res!(res!(ore(&b, &["init"])).good("init"));
70 res!(synced(&b, &a));
71 Ok((a, b))
72}
73
74
75/// A flag says where in the file its content is, and the line it names is the
76/// line the edit was planted on.
77///
78/// Two replicas replace the third of five lines. One prevails and the other
79/// yields, and the reviewer's question is where to look: the answer is the
80/// third line of the file that is on the disk, whichever of them prevailed.
81#[test]
82fn a_flag_names_the_line_its_content_is_on() -> Outcome<()> {
83 let scratch = res!(Scratch::new("review_line"));
84 let (a, b) = res!(twinned(&scratch, "src/util.rs", FIVE));
85 res!(write(&a, "src/util.rs", FIVE_A));
86 res!(write(&b, "src/util.rs", FIVE_B));
87 res!(synced(&b, &a));
88
89 let text = res!(ran(&b, &["flags"]));
90 assert!(text.contains("yielded"), "one of the two edits yielded: {}", text);
91 assert!(text.contains("the region as the file now holds it is at src/util.rs:3"),
92 "the contended region is the third line, which is where it was planted: {}", text);
93 // And the file really does hold one writer's third line there, so the
94 // coordinate is not merely a plausible number.
95 let held = res!(fs::read(b.join("src/util.rs")));
96 let third = match String::from_utf8_lossy(&held).lines().nth(2) {
97 Some(l) => fmt!("{}", l),
98 None => return Err(err!("The file has no third line."; Test, Missing)),
99 };
100 assert!(third == "THIRD FROM A" || third == "third from b",
101 "the third line is one writer's whole line, not a coordinate pointing at \
102 nothing: {:?}", third);
103
104 // The other end says the same thing, a flag being a function of the operation
105 // set and a coordinate a function of the render.
106 let theirs = res!(ran(&a, &["flags"]));
107 assert!(theirs.contains("the region as the file now holds it is at src/util.rs:3"),
108 "both ends put the region in the same place: {}", theirs);
109 Ok(())
110}
111
112/// Content that renders nowhere is said to render nowhere, rather than being
113/// omitted or given the location of a file it is not in.
114///
115/// Two authors who rewrite one region both delete the old bytes, so the content
116/// their overlap reports is dead. The flag has no file to be filed under, and the
117/// coordinate has no line to give.
118#[test]
119fn dead_content_says_no_file_shows_it() -> Outcome<()> {
120 let scratch = res!(Scratch::new("review_dead"));
121 let (a, b) = res!(twinned(&scratch, "src/util.rs", FIVE));
122 res!(write(&a, "src/util.rs", FIVE_A));
123 res!(write(&b, "src/util.rs", FIVE_B));
124 res!(synced(&b, &a));
125
126 let text = res!(ran(&b, &["flags"]));
127 assert!(text.contains("overlap"), "the raw fact is reported: {}", text);
128 // The overlap names the old bytes, which both replicas deleted.
129 let mut said: Option<&str> = None;
130 let mut lines = text.lines();
131 while let Some(line) = lines.next() {
132 if line.trim_start().starts_with("overlap ") {
133 said = lines.next();
134 break;
135 }
136 }
137 let said = match said {
138 Some(s) => s.trim(),
139 None => return Err(err!(
140 "No line follows the overlap flag: {}", text; Test, Missing)),
141 };
142 assert_eq!(said, "names bytes no file shows",
143 "the flag's content is dead, and that is what is said of it");
144 Ok(())
145}
146
147/// An edit that landed in a concurrently deleted file is given the line it
148/// landed on, and told that the file is gone.
149#[test]
150fn an_edit_in_a_deleted_file_keeps_its_line() -> Outcome<()> {
151 let scratch = res!(Scratch::new("review_deleted"));
152 let (a, b) = res!(twinned(&scratch, "doomed.txt", FIVE));
153 res!(fs::remove_file(a.join("doomed.txt")));
154 res!(res!(ore(&a, &["mark", "gone"])).good("mark"));
155 res!(write(&b, "doomed.txt", FIVE_B));
156 res!(synced(&b, &a));
157
158 let text = res!(ran(&b, &["flags"]));
159 assert!(text.contains("swallowed"), "the race is flagged: {}", text);
160 assert!(text.contains("at doomed.txt:3, in a file that has been deleted"),
161 "the edit was planted on the third line, and the file it is in is gone: {}", text);
162 Ok(())
163}
164
165/// A file holding bytes no line number would mean anything about is given a
166/// byte range instead.
167#[test]
168fn a_file_with_no_lines_is_given_a_byte_range() -> Outcome<()> {
169 let scratch = res!(Scratch::new("review_binary"));
170 // Sixteen bytes with a NUL in them, so the file has no lines to count.
171 let base: &[u8] = b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f";
172 let (a, b) = res!(twinned(&scratch, "blob.bin", base));
173 let mut mine = base.to_vec();
174 mine[8] = 0xaa;
175 let mut theirs = base.to_vec();
176 theirs[8] = 0xbb;
177 res!(write(&a, "blob.bin", &mine));
178 res!(write(&b, "blob.bin", &theirs));
179 res!(synced(&b, &a));
180
181 let text = res!(ran(&b, &["flags"]));
182 assert!(text.contains("yielded"), "the two writes collide: {}", text);
183 assert!(text.contains("blob.bin bytes "),
184 "a file with no lines is given bytes: {}", text);
185 assert!(!text.contains("blob.bin:"),
186 "and is given no line number at all: {}", text);
187 Ok(())
188}
189
190
191/// After a sync, `log --arrived` names exactly what that sync delivered, and
192/// nothing else.
193#[test]
194fn arrived_names_what_the_last_sync_delivered() -> Outcome<()> {
195 let scratch = res!(Scratch::new("review_arrived"));
196 let (a, b) = res!(twinned(&scratch, "shared.txt", FIVE));
197
198 // Work at each end, none of it seen by the other.
199 res!(write(&a, "from-a.txt", b"a line only A wrote\n"));
200 res!(res!(ore(&a, &["mark", "a-work"])).good("mark"));
201 res!(write(&b, "from-b.txt", b"a line only B wrote\n"));
202 res!(res!(ore(&b, &["mark", "b-work"])).good("mark"));
203 res!(synced(&b, &a));
204
205 // B asked for the sync, so what arrived there is A's work and only A's.
206 let text = res!(ran(&b, &["log", "--arrived"]));
207 assert!(text.contains("it brought 3 operations"),
208 "a file created, the line written into it, and the mark: {}", text);
209 assert!(text.contains("from-a.txt"), "the file A created is named: {}", text);
210 assert!(!text.contains("from-b.txt"),
211 "and B's own work is not something that arrived: {}", text);
212 assert!(text.contains("\"a-work\""), "the mark that crossed is named: {}", text);
213 assert!(!text.contains("\"b-work\""), "and B's own mark is not: {}", text);
214 assert!(text.contains("1 file created, 1 edit, 1 mark"),
215 "the operations are counted by kind: {}", text);
216 assert!(text.contains("flags none"),
217 "two files nobody else touched raise nothing: {}", text);
218
219 // A was synced with rather than syncing, and it absorbed B's work, so its
220 // answer is the mirror image.
221 let theirs = res!(ran(&a, &["log", "--arrived"]));
222 assert!(theirs.contains("it brought 3 operations"), "both ends absorb: {}", theirs);
223 assert!(theirs.contains("from-b.txt") && !theirs.contains("from-a.txt"),
224 "and each end's answer is about what it did not already hold: {}", theirs);
225
226 // A sync with nothing to carry says so, which is the answer that stops a
227 // reader reading the last one twice.
228 res!(synced(&b, &a));
229 let again = res!(ran(&b, &["log", "--arrived"]));
230 assert!(again.contains("it brought nothing"),
231 "a no-op sync delivered nothing: {}", again);
232 assert!(!again.contains("from-a.txt"),
233 "and the previous sync's delta is not repeated: {}", again);
234 assert!(again.contains("sync this repository has recorded, and only the last is kept"),
235 "and the reader is told that only the last is kept: {}", again);
236
237 // Deleting the record costs the answer and nothing else.
238 res!(fs::remove_file(b.join(".ore").join("arrived")));
239 let bare = res!(ran(&b, &["log", "--arrived"]));
240 assert!(bare.contains("no sync has brought anything into this repository"),
241 "the record is deletable: {}", bare);
242 Ok(())
243}
244
245/// The delta names the flags the arrival raised, and not the ones the renderer
246/// was already raising.
247#[test]
248fn arrived_names_the_flags_the_arrival_raised() -> Outcome<()> {
249 let scratch = res!(Scratch::new("review_arrived_flags"));
250 let (a, b) = res!(twinned(&scratch, "src/util.rs", FIVE));
251 res!(write(&a, "src/util.rs", FIVE_A));
252 res!(write(&b, "src/util.rs", FIVE_B));
253 res!(synced(&b, &a));
254
255 let text = res!(ran(&b, &["log", "--arrived"]));
256 assert!(text.contains("flags 2 raised by the arrival"),
257 "the collision is what the arrival raised: {}", text);
258 assert!(text.contains("overlap") && text.contains("yielded"),
259 "and both flags are named: {}", text);
260 assert!(text.contains("the region as the file now holds it is at src/util.rs:3"),
261 "with the coordinate the flag list gives: {}", text);
262 Ok(())
263}
264
265
266/// A flag can be put down: `--reviewed` marks it, `--new` stops showing it, and
267/// deleting the record brings it back.
268#[test]
269fn a_reviewed_flag_stops_being_new() -> Outcome<()> {
270 let scratch = res!(Scratch::new("review_new"));
271 let (a, b) = res!(twinned(&scratch, "src/util.rs", FIVE));
272 res!(write(&a, "src/util.rs", FIVE_A));
273 res!(write(&b, "src/util.rs", FIVE_B));
274 res!(synced(&b, &a));
275
276 // Everything is new until somebody says otherwise.
277 let text = res!(ran(&b, &["flags", "--new"]));
278 assert!(text.contains("2 new flags, and 0 marked reviewed"),
279 "nothing has been put down yet: {}", text);
280
281 // The operation that yielded names both of its flags, which is what a person
282 // means by naming it.
283 let which = match yielded_op(&res!(ran(&b, &["flags"]))) {
284 Some(id) => id,
285 None => return Err(err!(
286 "No yielded flag names an operation."; Test, Missing)),
287 };
288 let text = res!(ran(&b, &["flags", "--reviewed", &which]));
289 assert!(text.contains("marked 2 flags reviewed, 2 of 2 in all"),
290 "naming the edit marks every flag about it: {}", text);
291 assert!(text.contains("(reviewed)"),
292 "and the full list says which of them are down: {}", text);
293
294 let text = res!(ran(&b, &["flags", "--new"]));
295 assert!(text.contains("nothing new: all 2 flags are marked reviewed"),
296 "and --new shows nothing: {}", text);
297
298 // A re-render does not raise them again: the record names the operations the
299 // flags name, so it survives anything that does not change those.
300 res!(write(&b, "unrelated.txt", b"more work, elsewhere\n"));
301 res!(res!(ore(&b, &["mark", "later"])).good("mark"));
302 let text = res!(ran(&b, &["flags", "--new"]));
303 assert!(text.contains("nothing new"),
304 "work elsewhere does not make an old flag new: {}", text);
305
306 // And the record is this working copy's own: deleting it makes everything new
307 // again, which is the whole of what it costs.
308 res!(fs::remove_file(b.join(".ore").join("reviewed")));
309 let text = res!(ran(&b, &["flags", "--new"]));
310 assert!(text.contains("2 new flags"),
311 "deleting the record makes every flag new again: {}", text);
312
313 // The other replica has an opinion of its own, and this one's does not reach
314 // it: reviewing is the tool's business and syncs with nothing.
315 let theirs = res!(ran(&a, &["flags", "--new"]));
316 assert!(theirs.contains("2 new flags"),
317 "a reviewed flag is not something a peer inherits: {}", theirs);
318 assert!(!a.join(".ore").join("reviewed").is_file(),
319 "and nothing wrote a reviewed record there");
320 Ok(())
321}
322
323/// Marking every flag at once is one word, and naming an operation no flag names
324/// says so rather than claiming to have done something.
325#[test]
326fn all_marks_every_flag_and_a_stranger_marks_none() -> Outcome<()> {
327 let scratch = res!(Scratch::new("review_all"));
328 let (a, b) = res!(twinned(&scratch, "src/util.rs", FIVE));
329 res!(write(&a, "src/util.rs", FIVE_A));
330 res!(write(&b, "src/util.rs", FIVE_B));
331 res!(synced(&b, &a));
332
333 let text = res!(ran(&b, &["flags", "--reviewed", "r1:99999"]));
334 assert!(text.contains("nothing was marked reviewed"),
335 "an operation no flag names marks nothing: {}", text);
336 assert!(text.contains("2 flags in all"),
337 "and the flags are still all there: {}", text);
338
339 let text = res!(ran(&b, &["flags", "--reviewed", "all"]));
340 assert!(text.contains("marked 2 flags reviewed"), "all means all: {}", text);
341 let text = res!(ran(&b, &["flags", "--new"]));
342 assert!(text.contains("nothing new"), "and nothing is left new: {}", text);
343 Ok(())
344}
345
346
347/// Returns the operation a yielded flag names, as `ore flags` prints it.
348fn yielded_op(text: &str) -> Option<String> {
349 for line in text.lines() {
350 let rest = match line.trim_start().strip_prefix("yielded ") {
351 Some(r) => r,
352 None => continue,
353 };
354 return rest.split_whitespace().next().map(|s| fmt!("{}", s));
355 }
356 None
357}