Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_ore/tests/format_registry.rs

27.8 KiB, 25 runs

created by r1870400018:35249, 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 format registry, checked against the code it describes.
2//!
3//! `format.jdat` beside this crate's manifest holds the version constants, the
4//! wire codes, the kind bytes and the golden byte tests as data. Everything here
5//! reads it and asks the tree whether it is still true.
6//!
7//! It exists because the prose contract it replaces went stale without anybody
8//! noticing. Its table named `snapshot::VERSION` at a line in a file that had
9//! been deleted four days earlier, and listed a golden test that had gone with
10//! it; both were found by a person reading carefully, and only because a person
11//! was asked to look. A registry a test reads cannot go stale quietly, which is
12//! the whole of the argument for moving these facts out of the document.
13
14use oxedyne_fe2o3_ore::{
15 op,
16 segment,
17 sync::msg,
18};
19
20use oxedyne_fe2o3_core::prelude::*;
21use oxedyne_fe2o3_jdat::prelude::*;
22
23use std::{
24 collections::BTreeSet,
25 fs,
26 path::{
27 Path,
28 PathBuf,
29 },
30};
31
32
33// The registry, as it is written.
34
35#[derive(Clone, Debug, Default, FromDatMap)]
36struct Registry {
37 about: String,
38 constants: Vec<Dat>,
39 files: Vec<Dat>,
40 goldens: Vec<Dat>,
41 open: Vec<Dat>,
42 removed: Vec<Dat>,
43 settled: Vec<Dat>,
44 versions: Vec<Dat>,
45}
46
47#[derive(Clone, Debug, Default, FromDatMap)]
48struct Constant {
49 axis: String,
50 file: String,
51 holds: String,
52 line: u64,
53 name: String,
54 value: Dat,
55}
56
57#[derive(Clone, Debug, Default, FromDatMap)]
58struct FileRow {
59 exempt: Vec<String>,
60 path: String,
61 role: String,
62}
63
64#[derive(Clone, Debug, Default, FromDatMap)]
65struct VersionRow {
66 highest_code: String,
67 note: String,
68 version: u64,
69}
70
71#[derive(Clone, Debug, Default, FromDatMap)]
72struct Golden {
73 file: String,
74 holds: String,
75 magic: String,
76 name: String,
77 pins: Vec<Dat>,
78}
79
80#[derive(Clone, Debug, Default, FromDatMap)]
81struct Pin {
82 at: u64,
83 is: String,
84}
85
86#[derive(Clone, Debug, Default, FromDatMap)]
87struct Removed {
88 commit: String,
89 file: String,
90 gone: String,
91 name: String,
92 why: String,
93}
94
95#[derive(Clone, Debug, Default, FromDatMap)]
96struct Settled {
97 answer: String,
98 answered: String,
99 names: Vec<String>,
100 note: String,
101 question: String,
102 refused: Vec<String>,
103}
104
105#[derive(Clone, Debug, Default, FromDatMap)]
106struct Open {
107 asked: String,
108 guard: Vec<String>,
109 note: String,
110 question: String,
111 why: String,
112}
113
114// The sections the registry is expected to carry. An unknown key would otherwise
115// be read past in silence, so a section renamed by a typo would take its whole
116// contents out of the checking with nothing to show for it.
117const SECTIONS: usize = 8;
118
119// The axes a name may sit on. Which axis a name belongs to decides whether a
120// version moves for it, so a new one is a design decision and belongs in a
121// commit rather than in a string nobody read.
122const AXES: [&str; 5] = ["container", "convention", "field", "frame", "vocabulary"];
123
124
125/// The value this crate compiles for a name the registry declares.
126///
127/// A registry entry with no arm here fails, so a constant cannot be written into
128/// the registry without the compiler agreeing that it exists and is public. The
129/// arms name the constants and never their values, so this is a bridge and not a
130/// second copy of the registry.
131fn compiled(name: &str) -> Option<Dat> {
132 Some(match name {
133 "segment::MAGIC" => magic(&segment::MAGIC),
134 "segment::VERSION" => Dat::U8(segment::VERSION),
135 "segment::VERSION_MIN" => Dat::U8(segment::VERSION_MIN),
136 "segment::KIND_BARE" => Dat::U8(segment::KIND_BARE),
137 "segment::KIND_SEALED" => Dat::U8(segment::KIND_SEALED),
138 "segment::KIND_VEILED" => Dat::U8(segment::KIND_VEILED),
139 "segment::KIND_PACKED" => Dat::U8(segment::KIND_PACKED),
140 "segment::PACKED_MAX" => Dat::U64(segment::PACKED_MAX as u64),
141 "op::CODE_FILE_CREATE" => Dat::U8(op::CODE_FILE_CREATE),
142 "op::CODE_FILE_DELETE" => Dat::U8(op::CODE_FILE_DELETE),
143 "op::CODE_FILE_RENAME" => Dat::U8(op::CODE_FILE_RENAME),
144 "op::CODE_MARK" => Dat::U8(op::CODE_MARK),
145 "op::CODE_SPLICE" => Dat::U8(op::CODE_SPLICE),
146 "op::CODE_MOVE" => Dat::U8(op::CODE_MOVE),
147 "op::CODE_NOTE" => Dat::U8(op::CODE_NOTE),
148 "op::CODE_FILE_MODE" => Dat::U8(op::CODE_FILE_MODE),
149 "op::CODE_MARK_TIMED" => Dat::U8(op::CODE_MARK_TIMED),
150 "op::CODE_PROPOSAL" => Dat::U8(op::CODE_PROPOSAL),
151 "op::CODE_SAID" => Dat::U8(op::CODE_SAID),
152 "op::CODE_SETTLED" => Dat::U8(op::CODE_SETTLED),
153 "op::CODE_REVERTS" => Dat::U8(op::CODE_REVERTS),
154 "op::CODE_AMENDED" => Dat::U8(op::CODE_AMENDED),
155 "op::CODE_FORGET" => Dat::U8(op::CODE_FORGET),
156 "op::CODE_FORGOTTEN" => Dat::U8(op::CODE_FORGOTTEN),
157 "op::MODE_NORMAL" => Dat::U8(op::MODE_NORMAL),
158 "op::MODE_EXECUTABLE" => Dat::U8(op::MODE_EXECUTABLE),
159 "op::MODE_SYMLINK" => Dat::U8(op::MODE_SYMLINK),
160 "op::SETTLED_OPEN" => Dat::U8(op::SETTLED_OPEN),
161 "op::SETTLED_ACCEPTED" => Dat::U8(op::SETTLED_ACCEPTED),
162 "op::SETTLED_DECLINED" => Dat::U8(op::SETTLED_DECLINED),
163 "op::SETTLED_DONE" => Dat::U8(op::SETTLED_DONE),
164 "op::AUTO_MARK_PREFIX" => Dat::Str(op::AUTO_MARK_PREFIX.to_string()),
165 "op::AUTHOR_TRAILER" => Dat::Str(op::AUTHOR_TRAILER.to_string()),
166 "sync::msg::MAGIC" => magic(&msg::MAGIC),
167 "sync::msg::VERSION" => Dat::U8(msg::VERSION),
168 "sync::msg::VERSION_MIN" => Dat::U8(msg::VERSION_MIN),
169 "sync::msg::KIND_HELLO" => Dat::U8(msg::KIND_HELLO),
170 "sync::msg::KIND_SKETCH" => Dat::U8(msg::KIND_SKETCH),
171 "sync::msg::KIND_SEND" => Dat::U8(msg::KIND_SEND),
172 "sync::msg::KIND_DONE" => Dat::U8(msg::KIND_DONE),
173 "sync::msg::KIND_PART" => Dat::U8(msg::KIND_PART),
174 "sync::msg::KIND_FORGOTTEN" => Dat::U8(msg::KIND_FORGOTTEN),
175 "sync::msg::KIND_RESUME" => Dat::U8(msg::KIND_RESUME),
176 "sync::msg::PART_MAX" => Dat::U64(msg::PART_MAX as u64),
177 // Not a constant, and the one pin that cannot be: a message is stamped
178 // with the version it NEEDS, so a part says 2 while sync::msg::VERSION
179 // says 3, and the byte a golden freezes is what the encoder would write.
180 // Asked of the function the encoder itself asks, so the pin moves only if
181 // the rule does.
182 "sync::msg::version_for(KIND_PART)"
183 => Dat::U8(msg::version_for(msg::KIND_PART)),
184 "sync::msg::version_for(KIND_FORGOTTEN)"
185 => Dat::U8(msg::version_for(msg::KIND_FORGOTTEN)),
186 _ => return None,
187 })
188}
189
190fn magic(bytes: &[u8]) -> Dat {
191 Dat::Str(String::from_utf8_lossy(bytes).into_owned())
192}
193
194
195// Reading the registry, and reading the tree.
196
197fn root() -> PathBuf {
198 PathBuf::from(env!("CARGO_MANIFEST_DIR"))
199}
200
201fn slurp(path: &Path) -> Outcome<String> {
202 match fs::read_to_string(path) {
203 Ok(s) => Ok(s),
204 Err(e) => Err(err!(e,
205 "While reading {} for the format registry.", path.display();
206 IO, File, Read)),
207 }
208}
209
210fn registry() -> Outcome<Registry> {
211 let path = root().join("format.jdat");
212 let dat = res!(Dat::decode_string(res!(slurp(&path)))).normalise();
213 let map = match dat {
214 Dat::Map(m) => m,
215 other => return Err(err!(
216 "The format registry at {} decodes to a {:?}, not a map.",
217 path.display(), other.kind(); Input, Invalid)),
218 };
219 if map.len() != SECTIONS {
220 let keys: Vec<String> = map.keys().map(|k| fmt!("{:?}", k)).collect();
221 return Err(err!(
222 "The format registry carries {} top level keys and this test knows {}. \
223 A section this test does not read is a section nothing checks. Found: {}.",
224 map.len(), SECTIONS, keys.join(", "); Input, Invalid));
225 }
226 Registry::from_datmap(map)
227}
228
229fn rows<T: FromDatMap>(list: &[Dat], section: &str) -> Outcome<Vec<T>> {
230 let mut out = Vec::new();
231 for (i, d) in list.iter().enumerate() {
232 match d {
233 Dat::Map(m) => out.push(res!(T::from_datmap(m.clone()))),
234 other => return Err(err!(
235 "Entry {} of the registry's '{}' section is a {:?}, not a map.",
236 i, section, other.kind(); Input, Invalid)),
237 }
238 }
239 Ok(out)
240}
241
242fn constants() -> Outcome<Vec<Constant>> {
243 let reg = res!(registry());
244 rows(&reg.constants, "constants")
245}
246
247fn code_of(line: &str) -> &str {
248 match line.find("//") {
249 Some(i) => &line[..i],
250 None => line,
251 }
252}
253
254/// The name of the screaming case constant this line declares, where it declares
255/// one. `pub const fn` is not one, which is why the name is required to be
256/// screaming case rather than merely to follow the keyword.
257fn declared_const(line: &str) -> Option<&str> {
258 let code = code_of(line).trim_start();
259 let rest = match code.strip_prefix("pub const ") {
260 Some(r) => r,
261 None => return None,
262 };
263 let end = rest
264 .find(|c: char| !(c.is_ascii_uppercase() || c.is_ascii_digit() || c == '_'))
265 .unwrap_or(rest.len());
266 if end == 0 {
267 return None;
268 }
269 let name = &rest[..end];
270 match rest[end..].trim_start().starts_with(':') {
271 true => Some(name),
272 false => None,
273 }
274}
275
276/// The last component of a registry name, which is what the source declares.
277fn short(name: &str) -> &str {
278 match name.rfind("::") {
279 Some(i) => &name[i + 2..],
280 None => name,
281 }
282}
283
284/// Every `.rs` file under `src`, so that a check over the tree cannot be fooled
285/// by a name moving to a module the check did not know about.
286fn sources() -> Outcome<Vec<PathBuf>> {
287 let mut out = Vec::new();
288 let mut stack = vec![root().join("src")];
289 while let Some(dir) = stack.pop() {
290 let entries = match fs::read_dir(&dir) {
291 Ok(e) => e,
292 Err(e) => return Err(err!(e,
293 "While walking {} for the format registry.", dir.display();
294 IO, File, Read)),
295 };
296 for entry in entries {
297 let entry = res!(entry);
298 let path = entry.path();
299 if path.is_dir() {
300 stack.push(path);
301 } else if path.extension().and_then(|e| e.to_str()) == Some("rs") {
302 out.push(path);
303 }
304 }
305 }
306 out.sort();
307 Ok(out)
308}
309
310/// The bytes a golden test freezes, read out of its source as hexadecimal
311/// tokens. The arrays these tests carry are written one byte to a token with the
312/// prose beside them in comments, so the tokens of the function body in order are
313/// the frozen bytes in order.
314fn frozen_bytes(src: &str, test: &str) -> Outcome<Vec<u8>> {
315 let want = fmt!("fn {}(", test);
316 let lines: Vec<&str> = src.lines().collect();
317 let start = match lines.iter().position(|l| l.contains(&want)) {
318 Some(i) => i,
319 None => return Err(err!(
320 "The golden test '{}' is not in the source it was looked for in.",
321 test; Missing)),
322 };
323 let indent: String = lines[start]
324 .chars()
325 .take_while(|c| c.is_whitespace())
326 .collect();
327 let close = fmt!("{}}}", indent);
328 let mut out = Vec::new();
329 for line in &lines[start + 1..] {
330 if *line == close {
331 return Ok(out);
332 }
333 let code = code_of(line);
334 let mut rest = code;
335 while let Some(i) = rest.find("0x") {
336 let tail = &rest[i + 2..];
337 let end = tail
338 .find(|c: char| !c.is_ascii_hexdigit())
339 .unwrap_or(tail.len());
340 if end == 2 {
341 match u8::from_str_radix(&tail[..2], 16) {
342 Ok(b) => out.push(b),
343 Err(e) => return Err(err!(e,
344 "While reading a frozen byte of '{}'.", test; Input, Invalid)),
345 }
346 }
347 rest = &tail[end..];
348 }
349 }
350 Err(err!(
351 "The golden test '{}' has no closing brace at its own indent, so its \
352 frozen bytes could not be read.", test; Input, Invalid))
353}
354
355fn byte_of(name: &str) -> Outcome<u8> {
356 match compiled(name) {
357 Some(Dat::U8(v)) => Ok(v),
358 Some(other) => Err(err!(
359 "The registry pins a byte to '{}', which is a {:?} and not a byte.",
360 name, other.kind(); Input, Mismatch)),
361 None => Err(err!(
362 "The registry names '{}', which this crate does not declare.",
363 name; Missing)),
364 }
365}
366
367
368// What the registry claims, and whether it is still true.
369
370/// Every constant is declared in the file and at the line the registry gives.
371///
372/// This is the check that the deleted `snapshot.rs` would have tripped: the file
373/// named is opened, and a missing one is the failure rather than a silent pass.
374#[test]
375fn every_constant_is_where_the_registry_says_it_is() -> Outcome<()> {
376 for c in res!(constants()) {
377 let path = root().join(&c.file);
378 if !path.exists() {
379 return Err(err!(
380 "The registry says {} is declared in {}, and there is no such file. \
381 Either the constant moved and the registry did not, or the format it \
382 belongs to was taken out and its row belongs under 'removed'.",
383 c.name, c.file; Missing, File));
384 }
385 let src = res!(slurp(&path));
386 let mut found = None;
387 for (i, line) in src.lines().enumerate() {
388 if declared_const(line) == Some(short(&c.name)) {
389 found = Some(i + 1);
390 break;
391 }
392 }
393 match found {
394 Some(line) if line as u64 == c.line => {},
395 Some(line) => return Err(err!(
396 "The registry says {} is declared at {}:{}, and it is at line {}.",
397 c.name, c.file, c.line, line; Mismatch)),
398 None => return Err(err!(
399 "The registry says {} is declared in {}, and that file declares no \
400 public constant of that name.", c.name, c.file; Missing)),
401 }
402 }
403 Ok(())
404}
405
406/// The axis check is here rather than on its own because an axis nobody knows is
407/// a value nobody checked, which is the same failure as a value that has moved.
408#[test]
409fn every_constant_has_the_value_the_registry_declares() -> Outcome<()> {
410 for c in res!(constants()) {
411 let got = match compiled(&c.name) {
412 Some(v) => v,
413 None => return Err(err!(
414 "The registry declares {} and this test has no arm for it, so its \
415 value is a claim nothing checks. Add the arm, or the constant is not \
416 one this crate declares.", c.name; Missing)),
417 };
418 if got != c.value {
419 return Err(err!(
420 "The registry declares {} as {:?} and this crate compiles {:?}. \
421 A reader elsewhere is holding the registry's number.",
422 c.name, c.value, got; Mismatch));
423 }
424 if !AXES.contains(&c.axis.as_str()) {
425 return Err(err!(
426 "The registry puts {} on the axis '{}', which this test does not \
427 know. The axis a name sits on decides whether a version moves for \
428 it, so it is a decision and not a label. Known: {}.",
429 c.name, c.axis, AXES.join(", "); Input, Invalid));
430 }
431 }
432 Ok(())
433}
434
435/// Every public constant in a format bearing file is in the registry.
436///
437/// Without this the registry would only ever be as complete as whoever last
438/// remembered it, which is the failure it was written to end: a shared name
439/// nobody registered is a name each lane satisfies in isolation.
440#[test]
441fn every_public_constant_in_a_format_file_is_registered() -> Outcome<()> {
442 let reg = res!(registry());
443 let files: Vec<FileRow> = res!(rows(&reg.files, "files"));
444 let known: BTreeSet<String> = res!(rows::<Constant>(&reg.constants, "constants"))
445 .iter()
446 .map(|c| fmt!("{}::{}", c.file, short(&c.name)))
447 .collect();
448 for f in files {
449 let path = root().join(&f.path);
450 let src = res!(slurp(&path));
451 for line in src.lines() {
452 if let Some(name) = declared_const(line) {
453 if f.exempt.iter().any(|e| e == name) {
454 continue;
455 }
456 let key = fmt!("{}::{}", f.path, name);
457 if !known.contains(&key) {
458 return Err(err!(
459 "{} declares the public constant {}, which is not in the \
460 registry. {} carries {}, so a name in it is a name another \
461 reader has to agree on. Register it, or exempt it and say why.",
462 f.path, name, f.path, f.role; Missing));
463 }
464 }
465 }
466 }
467 Ok(())
468}
469
470/// The registry names every golden byte test there is, and no others.
471///
472/// Both halves matter. The contract this replaces said each format had one
473/// golden test when there were four, and went on listing a fifth that had been
474/// deleted with the format it froze.
475#[test]
476fn the_registry_names_every_golden_test_and_no_others() -> Outcome<()> {
477 let reg = res!(registry());
478 let goldens: Vec<Golden> = res!(rows(&reg.goldens, "goldens"));
479 let claimed: BTreeSet<String> = goldens.iter().map(|g| g.name.clone()).collect();
480
481 let mut present = BTreeSet::new();
482 for path in res!(sources()) {
483 let src = res!(slurp(&path));
484 for line in src.lines() {
485 let code = code_of(line).trim_start();
486 if let Some(rest) = code.strip_prefix("fn ") {
487 let end = rest.find('(').unwrap_or(0);
488 let name = &rest[..end];
489 if name.ends_with("_bytes_are_frozen") {
490 present.insert(name.to_string());
491 }
492 }
493 }
494 }
495
496 if let Some(name) = claimed.difference(&present).next() {
497 return Err(err!(
498 "The registry names the golden test {}, and the crate has no such test. \
499 A format nothing freezes can change by accident and orphan every store \
500 already written in it.", name; Missing));
501 }
502 if let Some(name) = present.difference(&claimed).next() {
503 return Err(err!(
504 "The crate has the golden test {} and the registry does not name it, so \
505 what it freezes is not written down anywhere a reader elsewhere can find \
506 it.", name; Missing));
507 }
508 Ok(())
509}
510
511/// Every golden test freezes the constants the registry says it pins.
512///
513/// This is the check that turns the contract's sentence about a version bump --
514/// index 6, `0x03` to `0x04`, and nothing else -- into an instruction the failure
515/// prints for itself.
516#[test]
517fn every_golden_test_freezes_the_constants_it_pins() -> Outcome<()> {
518 let reg = res!(registry());
519 for g in res!(rows::<Golden>(&reg.goldens, "goldens")) {
520 let path = root().join(&g.file);
521 if !path.exists() {
522 return Err(err!(
523 "The registry says the golden test {} is in {}, and there is no such \
524 file.", g.name, g.file; Missing, File));
525 }
526 let bytes = res!(frozen_bytes(&res!(slurp(&path)), &g.name));
527
528 let magic = match compiled(&g.magic) {
529 Some(Dat::Str(s)) => s,
530 _ => return Err(err!(
531 "The registry says {} begins with {}, which this crate does not \
532 declare as a magic.", g.name, g.magic; Missing)),
533 };
534 let head: String = bytes
535 .iter()
536 .take(magic.len())
537 .map(|b| *b as char)
538 .collect();
539 if head != magic {
540 return Err(err!(
541 "{} freezes bytes beginning {:?}, and the registry says it freezes \
542 {}, which is {:?}.", g.name, head, g.magic, magic; Mismatch));
543 }
544
545 for p in res!(rows::<Pin>(&g.pins, "pins")) {
546 let want = res!(byte_of(&p.is));
547 let at = p.at as usize;
548 if at >= bytes.len() {
549 return Err(err!(
550 "The registry pins byte {} of {} to {}, and that test freezes \
551 only {} bytes.", p.at, g.name, p.is, bytes.len(); Mismatch));
552 }
553 if bytes[at] != want {
554 return Err(err!(
555 "{} freezes byte {} as {:#04x}, and {} is {:#04x}. The frozen \
556 array has not been moved, so the bytes this test calls the \
557 format are not the bytes this crate writes.",
558 g.name, p.at, bytes[at], p.is, want; Mismatch));
559 }
560 }
561 }
562 Ok(())
563}
564
565/// The three claims here are separable: what a version admits, that the table
566/// reaches as far as `VERSION`, and that the vocabulary only ever grew.
567#[test]
568fn the_version_table_agrees_with_highest_code() -> Outcome<()> {
569 let reg = res!(registry());
570 let table: Vec<VersionRow> = res!(rows(&reg.versions, "versions"));
571
572 let mut want = segment::VERSION_MIN as u64;
573 let mut last = 0u8;
574 for row in &table {
575 if row.version != want {
576 return Err(err!(
577 "The registry's version table goes to version {} where {} was next. \
578 It must cover every version from segment::VERSION_MIN ({}) to \
579 segment::VERSION ({}), because that range is what this crate promises \
580 to read.",
581 row.version, want, segment::VERSION_MIN, segment::VERSION;
582 Mismatch));
583 }
584 let top = res!(byte_of(&row.highest_code));
585 let got = segment::highest_code(row.version as u8);
586 if got != top {
587 return Err(err!(
588 "The registry says a version {} segment carries up to {} ({}), and \
589 segment::highest_code says {}. A writer continuing somebody else's \
590 segment asks highest_code, so the registry is telling a reader \
591 something no writer obeys.",
592 row.version, row.highest_code, top, got; Mismatch));
593 }
594 if top < last {
595 return Err(err!(
596 "The registry says version {} carries up to {} and the version \
597 before it carried up to {}. The vocabulary only ever grows upwards; \
598 a version admitting less than its predecessor breaks the subset \
599 promise VERSION_MIN rests on.",
600 row.version, top, last; Mismatch));
601 }
602 last = top;
603 want += 1;
604 }
605 if want != segment::VERSION as u64 + 1 {
606 return Err(err!(
607 "segment::VERSION is {} and the registry's version table stops at {}. \
608 A version rose without a row saying which operation codes it admits, so \
609 highest_code has gained a branch nothing checks.",
610 segment::VERSION, want - 1; Missing));
611 }
612
613 // A code above what the current version admits could never be written, so it
614 // is a code somebody added without moving the version with it.
615 let top = segment::highest_code(segment::VERSION);
616 for c in res!(rows::<Constant>(&reg.constants, "constants")) {
617 if c.name.starts_with("op::CODE_") {
618 let code = res!(byte_of(&c.name));
619 if code > top {
620 return Err(err!(
621 "{} is {} and a segment at segment::VERSION ({}) admits at most \
622 {}, so nothing could ever write it: Store::append refuses it and \
623 no fallback catches that.",
624 c.name, code, segment::VERSION, top; Mismatch));
625 }
626 }
627 }
628 Ok(())
629}
630
631/// No message kind sits above what the message version admits.
632///
633/// The mirror of the last block of the test above, for the other vocabulary.
634/// `sync::msg::highest_kind` is the rule the message kinds grow by and nothing
635/// checked it: a kind added without the version moving is a message no peer
636/// could ever legally stamp, and a version moving with no kind above the old top
637/// is a version that bought nothing and refuses old peers for no reason.
638#[test]
639fn no_message_kind_sits_above_what_its_version_admits() -> Outcome<()> {
640 let top = msg::highest_kind(msg::VERSION);
641 for c in res!(constants()) {
642 if c.name.starts_with("sync::msg::KIND_") {
643 let kind = res!(byte_of(&c.name));
644 if kind > top {
645 return Err(err!(
646 "{} is {} and a peer at sync::msg::VERSION ({}) may send up to \
647 kind {}, so nothing could ever stamp it: Message::decode refuses \
648 a kind its declared version does not admit.",
649 c.name, kind, msg::VERSION, top; Mismatch));
650 }
651 }
652 }
653 let was = msg::highest_kind(msg::VERSION_MIN);
654 if msg::VERSION != msg::VERSION_MIN && was >= top {
655 return Err(err!(
656 "sync::msg::VERSION is {} and sync::msg::VERSION_MIN is {}, and \
657 highest_kind admits up to kind {} at both. A version that adds no kind \
658 refuses every older peer and buys nothing for it.",
659 msg::VERSION, msg::VERSION_MIN, top; Mismatch));
660 }
661 Ok(())
662}
663
664/// A name the registry records as removed really is gone.
665///
666/// The inversion is the point. The row that named a deleted file sat in the live
667/// table for four days; here the same row asserts the deletion, so it reddens if
668/// the name comes back rather than if it does not.
669#[test]
670fn every_removed_name_is_really_gone() -> Outcome<()> {
671 let reg = res!(registry());
672 for r in res!(rows::<Removed>(&reg.removed, "removed")) {
673 // A bare `VERSION` means one thing in each module that declares one, so a
674 // removed constant is looked for only in the module it was removed from.
675 // A test function's name is its own, so that is looked for everywhere.
676 let paths = match r.name.contains("::") {
677 true => {
678 let path = root().join(&r.file);
679 match path.exists() {
680 true => vec![path],
681 false => continue,
682 }
683 },
684 false => res!(sources()),
685 };
686 let name = short(&r.name);
687 for path in paths {
688 let src = res!(slurp(&path));
689 for (i, line) in src.lines().enumerate() {
690 let code = code_of(line).trim_start();
691 let hit = declared_const(line) == Some(name)
692 || code.strip_prefix("fn ").map(|s| s.starts_with(name)) == Some(true);
693 if hit {
694 return Err(err!(
695 "The registry records {} as removed on {} in {}, and {}:{} \
696 declares it. If it is back, its row belongs in the live \
697 section where a test checks its value.",
698 r.name, r.gone, r.commit,
699 path.display(), i + 1; Mismatch));
700 }
701 }
702 }
703 }
704 Ok(())
705}
706
707/// No name the owner has not yet fixed has been invented.
708///
709/// A lane that needs a name the registry does not carry is required to report the
710/// seam rather than invent one, and until now nothing enforced that. Each open
711/// question guards the spellings whose appearance would mean somebody acted: a
712/// hit is either an answer the registry was not told about, or a name invented in
713/// passing.
714#[test]
715fn no_open_name_has_been_invented() -> Outcome<()> {
716 let reg = res!(registry());
717 let open: Vec<Open> = res!(rows(&reg.open, "open"));
718 let mut haystack: Vec<(String, String)> = Vec::new();
719 for path in res!(sources()) {
720 haystack.push((fmt!("{}", path.display()), res!(slurp(&path))));
721 }
722 let manifest = root().join("Cargo.toml");
723 haystack.push((fmt!("{}", manifest.display()), res!(slurp(&manifest))));
724
725 for o in open {
726 for name in &o.guard {
727 for (where_, src) in &haystack {
728 for (i, line) in src.lines().enumerate() {
729 if !introduces(line, name) {
730 continue;
731 }
732 return Err(err!(
733 "The registry records '{}' as open, put to the owner on {} \
734 and unanswered, and {}:{} introduces '{}'. Either it was \
735 answered and the registry was not told, or a name was \
736 invented that the reasoning in {} says cannot be guessed: {}.",
737 o.question, o.asked, where_, i + 1, name, o.note, o.why;
738 Mismatch));
739 }
740 }
741 }
742 }
743 Ok(())
744}
745
746/// A question the owner has answered still refuses what the answer ruled out.
747///
748/// The inversion the `removed` section makes, made again. While a question is
749/// open every candidate spelling is guarded so that nobody quietly picks one;
750/// once it is answered, the spellings the answer did NOT pick have to stay
751/// picked-against, or the second lane to arrive invents the alternative that was
752/// considered and rejected and nothing says it was.
753///
754/// The answer itself is checked too, where it names a constant: a settled row
755/// pointing at a name the registry does not carry is a decision recorded and
756/// never built.
757#[test]
758fn every_settled_question_still_refuses_what_it_ruled_out() -> Outcome<()> {
759 let reg = res!(registry());
760 let known: BTreeSet<String> = res!(constants()).into_iter().map(|c| c.name).collect();
761 let settled: Vec<Settled> = res!(rows(&reg.settled, "settled"));
762 if settled.is_empty() {
763 return Err(err!(
764 "The registry carries no settled questions. Every one that has been \
765 answered belongs here, or the reasoning behind it lives only in whatever \
766 document nobody opens."; Missing));
767 }
768 let mut haystack: Vec<(String, String)> = Vec::new();
769 for path in res!(sources()) {
770 haystack.push((fmt!("{}", path.display()), res!(slurp(&path))));
771 }
772 let manifest = root().join("Cargo.toml");
773 haystack.push((fmt!("{}", manifest.display()), res!(slurp(&manifest))));
774
775 for row in settled {
776 for name in &row.names {
777 if !known.contains(name) {
778 return Err(err!(
779 "The registry says '{}' was answered on {} by {}, and the constants \
780 section does not carry that name. A decision recorded and never \
781 built is worse than one still open, because nothing looks for it.",
782 row.question, row.answered, name; Missing));
783 }
784 }
785 for name in &row.refused {
786 for (where_, src) in &haystack {
787 for (i, line) in src.lines().enumerate() {
788 if !introduces(line, name) {
789 continue;
790 }
791 return Err(err!(
792 "'{}' was answered on {} -- {} -- and {}:{} introduces '{}', \
793 which the answer ruled out. Either the answer has changed and \
794 the registry was not told, or the alternative that was \
795 considered and rejected has been built beside the one that was \
796 chosen. The reasoning is in {}.",
797 row.question, row.answered, row.answer, where_, i + 1, name,
798 row.note; Mismatch));
799 }
800 }
801 }
802 }
803 Ok(())
804}
805
806/// Does this line introduce the given name, rather than merely mention it?
807///
808/// A local binding is not an introduction; a declaration, a field, a module and a
809/// dependency are. Comments are stripped first, so a note about compression in
810/// prose is not a name somebody fixed.
811fn introduces(line: &str, name: &str) -> bool {
812 let code = code_of(line).trim();
813 for kw in ["const ", "fn ", "struct ", "enum ", "static ", "mod ", "use "] {
814 if let Some(i) = code.find(kw) {
815 let rest = &code[i + kw.len()..];
816 if rest.strip_prefix(name).map(ends_name) == Some(true) {
817 return true;
818 }
819 }
820 }
821 // A struct field, a literal's field, or a dependency in the manifest.
822 match code.strip_prefix(name) {
823 Some(rest) => {
824 let rest = rest.trim_start();
825 rest.starts_with(':') || rest.starts_with('=')
826 },
827 None => false,
828 }
829}
830
831fn ends_name(rest: &str) -> bool {
832 match rest.chars().next() {
833 Some(c) => !(c.is_alphanumeric() || c == '_'),
834 None => true,
835 }
836}