Oregami
Repositories/oxedyne/ore

oxedyne/ore/cli/tests/imports.rs

9.9 KiB, 1 run

created by r2848102244:742, 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//! What `ore import` does about a credential in the git history it is reading.
2//!
3//! Every key here is spelled in two pieces and joined at run time, so that the scanners which read
4//! this very file -- the git pre-commit hook, and the guard under test -- find nothing in it. The
5//! same trick, and for the same reason, as `guard.rs` beside it.
6//!
7//! The two answers are different because the two situations are: a credential still at a branch
8//! tip is live and the import is refused before anything is written, and a credential only the
9//! history carries is already wherever it got to and is reported instead. So each test asserts
10//! what the store holds afterwards as well as what was said, since a refusal that still wrote and
11//! a report that refused would both pass a test that only read the message.
12
13mod support;
14
15use support::{
16 git,
17 git_init,
18 ore,
19 write,
20 Scratch,
21};
22
23use oxedyne_fe2o3_core::prelude::*;
24
25use std::fs;
26use std::path::Path;
27
28
29// A key of a shape that is a credential and nothing else, in two pieces.
30const KEY: (&str, &str) = ("fw", "_3ZjKq81mAbCdEfGhIjKlMnOpQrSt");
31
32/// The whole of a key, as it stands in a file.
33fn key(pieces: (&str, &str)) -> String {
34 fmt!("{}{}", pieces.0, pieces.1)
35}
36
37/// A line of source assigning one.
38fn line(pieces: (&str, &str)) -> String {
39 fmt!("let key = \"{}\";\n", key(pieces))
40}
41
42/// Reads a directory, recursing, looking for the bytes.
43///
44/// Asked of the bytes on disk rather than through a verb, because there is no verb that does not
45/// capture: asking one would write.
46fn found(dir: &Path, needle: &[u8])
47 -> Outcome<bool>
48{
49 for entry in res!(fs::read_dir(dir)) {
50 let entry = res!(entry);
51 let kind = res!(entry.file_type());
52 if kind.is_dir() {
53 if res!(found(&entry.path(), needle)) {
54 return Ok(true);
55 }
56 continue;
57 }
58 if !kind.is_file() {
59 continue;
60 }
61 let bytes = res!(fs::read(entry.path()));
62 if bytes.windows(needle.len()).any(|w| w == needle) {
63 return Ok(true);
64 }
65 }
66 Ok(false)
67}
68
69/// How many bytes of operations the log holds.
70///
71/// Zero is what a refused import must leave, and it is asked of the segments themselves rather
72/// than of a verb's word for it.
73fn logged(root: &Path)
74 -> Outcome<u64>
75{
76 let log = root.join(".ore").join("log");
77 if !log.is_dir() {
78 return Ok(0);
79 }
80 let mut total = 0;
81 for entry in res!(fs::read_dir(&log)) {
82 let entry = res!(entry);
83 if res!(entry.file_type()).is_file() {
84 total += res!(entry.metadata()).len();
85 }
86 }
87 Ok(total)
88}
89
90/// Commits a file's contents under a message, in a repository already started.
91fn commit(src: &Path, name: &str, holds: &str, said: &str)
92 -> Outcome<()>
93{
94 res!(write(src, name, holds.as_bytes()));
95 res!(git(src, &["add", "."]));
96 res!(git(src, &["commit", "--quiet", "--no-verify", "-m", said]));
97 Ok(())
98}
99
100
101/// A key added and then scrubbed is reported by mark, and the import goes through.
102///
103/// It is in the history whatever anybody does now, and refusing would only mean a repository that
104/// cannot be imported at all while protecting nothing. What did not exist before is being told,
105/// at the moment of the import, which commit put it there.
106#[test]
107fn a_credential_scrubbed_before_the_tip_is_reported_and_imported() -> Outcome<()> {
108 let scratch = res!(Scratch::new("import_reported"));
109 let src = res!(scratch.sub("src"));
110 let into = res!(scratch.sub("ore"));
111 res!(git_init(&src));
112 res!(commit(&src, "example.rs", "fn main() {}\n", "the example"));
113 res!(commit(&src, "example.rs",
114 &fmt!("fn main() {{}}\n{}", line(KEY)), "wire the example up"));
115 res!(commit(&src, "example.rs", "fn main() {}\n", "scrub the key"));
116
117 res!(res!(ore(&into, &["init"])).good("init"));
118 let ran = res!(ore(&into, &["import", &fmt!("{}", src.display())]));
119 assert!(ran.ok, "the import went through: {}{}", ran.out, ran.err);
120 assert!(ran.err.contains("example.rs"),
121 "stderr names the file: {}", ran.err);
122 assert!(ran.err.contains("line 2"),
123 "and the line: {}", ran.err);
124 assert!(ran.err.contains("Fireworks key"),
125 "and what shape it is: {}", ran.err);
126 assert!(ran.err.contains("wire the example up"),
127 "and the mark that carries it: {}", ran.err);
128 assert!(!ran.err.contains(&key(KEY)),
129 "and never the value itself: {}", ran.err);
130 // Not stderr alone. Somebody who sent stdout to a file and never read stderr still has this.
131 assert!(ran.out.contains("credential finding"),
132 "stdout carries the count: {}", ran.out);
133 assert!(!ran.out.contains(&key(KEY)),
134 "and it does not carry the value either: {}", ran.out);
135 // The tip is clean, which is why this was a report and not a refusal.
136 let tip = res!(fs::read(into.join("example.rs")));
137 assert!(!tip.windows(key(KEY).len()).any(|w| w == key(KEY).as_bytes()),
138 "the working copy the import wrote holds no key");
139 // And the history does hold it, which is the thing being reported rather than prevented. A
140 // test that skipped this would pass against an import that silently dropped the commit.
141 assert!(res!(found(&into.join(".ore"), key(KEY).as_bytes())),
142 "the history carries what the report says it carries");
143 Ok(())
144}
145
146/// The same history without the scrub is refused, and nothing at all is written.
147#[test]
148fn a_credential_at_the_tip_refuses_the_import() -> Outcome<()> {
149 let scratch = res!(Scratch::new("import_refused"));
150 let src = res!(scratch.sub("src"));
151 let into = res!(scratch.sub("ore"));
152 res!(git_init(&src));
153 res!(commit(&src, "example.rs", "fn main() {}\n", "the example"));
154 res!(commit(&src, "example.rs",
155 &fmt!("fn main() {{}}\n{}", line(KEY)), "wire the example up"));
156
157 res!(res!(ore(&into, &["init"])).good("init"));
158 let before = res!(logged(&into));
159 let ran = res!(ore(&into, &["import", &fmt!("{}", src.display())]));
160 assert!(!ran.ok, "the import was refused: {}{}", ran.out, ran.err);
161 assert!(ran.err.contains("example.rs:2"),
162 "and it names the file and the line: {}", ran.err);
163 assert!(ran.err.contains("Fireworks key"),
164 "and what it found: {}", ran.err);
165 assert!(!ran.err.contains(&key(KEY)),
166 "and never the value itself: {}", ran.err);
167 assert_eq!(res!(logged(&into)), before,
168 "not one byte of operations was written");
169 assert!(!res!(found(&into.join(".ore"), key(KEY).as_bytes())),
170 "and nothing of the key reached the store");
171 assert!(!into.join("example.rs").exists(),
172 "and no working copy was laid down");
173 Ok(())
174}
175
176/// A long answer is grouped rather than listed, and the totals are still exact.
177///
178/// Forty commits each carrying the same line of the same file is one thing to fix and forty
179/// findings. Listed a line each it is a wall somebody scrolls past, which is the same as not
180/// having been told.
181#[test]
182fn a_long_report_is_grouped_and_still_counts_right() -> Outcome<()> {
183 let scratch = res!(Scratch::new("import_long"));
184 let src = res!(scratch.sub("src"));
185 let into = res!(scratch.sub("ore"));
186 res!(git_init(&src));
187 res!(commit(&src, "example.rs", "fn main() {}\n", "the example"));
188 for n in 0..40 {
189 res!(commit(&src, "example.rs",
190 &fmt!("fn main() {{}}\n{}// edit {}\n", line(KEY), n),
191 &fmt!("edit number {}", n)));
192 }
193 res!(commit(&src, "example.rs", "fn main() {}\n", "scrub the key"));
194
195 res!(res!(ore(&into, &["init"])).good("init"));
196 let ran = res!(ore(&into, &["import", &fmt!("{}", src.display())]));
197 assert!(ran.ok, "the import went through: {}{}", ran.out, ran.err);
198 assert!(ran.err.contains("40 findings over 1 file and 40 marks"),
199 "the totals are exact: {}", ran.err);
200 assert!(ran.err.contains("in 40 marks, from \"edit number 0\" to \"edit number 39\""),
201 "and the span is one line naming both ends: {}", ran.err);
202 // Grouped, not listed: forty findings must not be forty lines.
203 let said = ran.err.lines().filter(|l| l.contains("Fireworks key")).count();
204 assert_eq!(said, 1, "one line for the forty: {}", ran.err);
205 assert!(ran.err.lines().count() < 10,
206 "and the whole report is short enough to read: {}", ran.err);
207 assert!(!ran.err.contains(&key(KEY)),
208 "and never the value itself: {}", ran.err);
209 Ok(())
210}
211
212/// A tip big enough to fill both pipes is still scanned.
213///
214/// The scan hands git a list of object names down one pipe and reads the answers back up another.
215/// Written whole and then read, that deadlocks -- but only when both are full at once, which is
216/// why it took the real repository to find. git drains the list while it can; its answers fill the
217/// 64 kB output pipe and it stops reading; and only then does an unwritten remainder larger than
218/// the 64 kB input pipe leave both ends waiting on each other.
219///
220/// So the fixture needs **many objects and large ones**: 1,800 names is 74 kB of list, over the
221/// input pipe, and a kilobyte apiece fills the output pipe after the first sixty-odd. Eighteen
222/// hundred tiny files does not do it -- git reads the whole list before its answers fill anything
223/// -- and neither does four hundred large ones, whose list fits the buffer. fe2o3 has 1,785 files
224/// averaging 17 kB and hung for ever.
225#[test]
226fn a_tip_big_enough_to_fill_both_pipes_is_scanned() -> Outcome<()> {
227 let scratch = res!(Scratch::new("import_wide"));
228 let src = res!(scratch.sub("src"));
229 let into = res!(scratch.sub("ore"));
230 res!(git_init(&src));
231 let filler = "x".repeat(1000);
232 for n in 0..1800 {
233 res!(write(&src, &fmt!("f{:04}.rs", n),
234 fmt!("// {} {}\n{}", n, filler, if n == 1250 { line(KEY) } else { fmt!("") })
235 .as_bytes()));
236 }
237 res!(git(&src, &["add", "."]));
238 res!(git(&src, &["commit", "--quiet", "--no-verify", "-m", "the wide commit"]));
239
240 res!(res!(ore(&into, &["init"])).good("init"));
241 let before = res!(logged(&into));
242 let ran = res!(ore(&into, &["import", &fmt!("{}", src.display())]));
243 assert!(!ran.ok, "the import was refused: {}{}", ran.out, ran.err);
244 assert!(ran.err.contains("f1250.rs:2"),
245 "and it found the one file in eighteen hundred that holds it: {}", ran.err);
246 assert!(!ran.err.contains(&key(KEY)),
247 "and never the value itself: {}", ran.err);
248 assert_eq!(res!(logged(&into)), before, "not one byte of operations was written");
249 assert!(!res!(found(&into.join(".ore"), key(KEY).as_bytes())),
250 "and nothing of the key reached the store");
251 Ok(())
252}