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 | |
| 14 | use oxedyne_fe2o3_ore::{ |
| 15 | op, |
| 16 | segment, |
| 17 | sync::msg, |
| 18 | }; |
| 19 | |
| 20 | use oxedyne_fe2o3_core::prelude::*; |
| 21 | use oxedyne_fe2o3_jdat::prelude::*; |
| 22 | |
| 23 | use 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)] |
| 36 | struct 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)] |
| 48 | struct 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)] |
| 58 | struct FileRow { |
| 59 | exempt: Vec<String>, |
| 60 | path: String, |
| 61 | role: String, |
| 62 | } |
| 63 | |
| 64 | #[derive(Clone, Debug, Default, FromDatMap)] |
| 65 | struct VersionRow { |
| 66 | highest_code: String, |
| 67 | note: String, |
| 68 | version: u64, |
| 69 | } |
| 70 | |
| 71 | #[derive(Clone, Debug, Default, FromDatMap)] |
| 72 | struct 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)] |
| 81 | struct Pin { |
| 82 | at: u64, |
| 83 | is: String, |
| 84 | } |
| 85 | |
| 86 | #[derive(Clone, Debug, Default, FromDatMap)] |
| 87 | struct Removed { |
| 88 | commit: String, |
| 89 | file: String, |
| 90 | gone: String, |
| 91 | name: String, |
| 92 | why: String, |
| 93 | } |
| 94 | |
| 95 | #[derive(Clone, Debug, Default, FromDatMap)] |
| 96 | struct 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)] |
| 106 | struct 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. |
| 117 | const 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. |
| 122 | const 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. |
| 131 | fn 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 | |
| 190 | fn 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 | |
| 197 | fn root() -> PathBuf { |
| 198 | PathBuf::from(env!("CARGO_MANIFEST_DIR")) |
| 199 | } |
| 200 | |
| 201 | fn 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 | |
| 210 | fn 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 | |
| 229 | fn 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 | |
| 242 | fn constants() -> Outcome<Vec<Constant>> { |
| 243 | let reg = res!(registry()); |
| 244 | rows(®.constants, "constants") |
| 245 | } |
| 246 | |
| 247 | fn 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. |
| 257 | fn 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. |
| 277 | fn 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. |
| 286 | fn 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. |
| 314 | fn 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 | |
| 355 | fn 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] |
| 375 | fn 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] |
| 409 | fn 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] |
| 441 | fn every_public_constant_in_a_format_file_is_registered() -> Outcome<()> { |
| 442 | let reg = res!(registry()); |
| 443 | let files: Vec<FileRow> = res!(rows(®.files, "files")); |
| 444 | let known: BTreeSet<String> = res!(rows::<Constant>(®.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] |
| 476 | fn the_registry_names_every_golden_test_and_no_others() -> Outcome<()> { |
| 477 | let reg = res!(registry()); |
| 478 | let goldens: Vec<Golden> = res!(rows(®.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] |
| 517 | fn every_golden_test_freezes_the_constants_it_pins() -> Outcome<()> { |
| 518 | let reg = res!(registry()); |
| 519 | for g in res!(rows::<Golden>(®.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] |
| 568 | fn the_version_table_agrees_with_highest_code() -> Outcome<()> { |
| 569 | let reg = res!(registry()); |
| 570 | let table: Vec<VersionRow> = res!(rows(®.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>(®.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] |
| 639 | fn 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] |
| 670 | fn every_removed_name_is_really_gone() -> Outcome<()> { |
| 671 | let reg = res!(registry()); |
| 672 | for r in res!(rows::<Removed>(®.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] |
| 715 | fn no_open_name_has_been_invented() -> Outcome<()> { |
| 716 | let reg = res!(registry()); |
| 717 | let open: Vec<Open> = res!(rows(®.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] |
| 758 | fn 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(®.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. |
| 811 | fn 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 | |
| 831 | fn ends_name(rest: &str) -> bool { |
| 832 | match rest.chars().next() { |
| 833 | Some(c) => !(c.is_alphanumeric() || c == '_'), |
| 834 | None => true, |
| 835 | } |
| 836 | } |