| 387 |
387 |
|
})
|
| 388 |
388 |
|
.is_some_and(|derive| derive.contains("Default"))
|
| 389 |
389 |
|
}
|
|
390 |
+ |
|
|
391 |
+ |
/// The language whitelist lives in three files, and nothing bound them.
|
|
392 |
+ |
///
|
|
393 |
+ |
/// The Containerfile validates `LANGS` against a `case` arm, `Lang::ALL` in
|
|
394 |
+ |
/// image.rs is what the builder TUI offers, and build/preflight.py refuses a
|
|
395 |
+ |
/// recipe naming anything else. Adding `c` on 2026-09-07 touched the first two
|
|
396 |
+ |
/// and missed the third, and the gate caught it at mint time, after the recipe
|
|
397 |
+ |
/// was already written and a build had been asked for.
|
|
398 |
+ |
///
|
|
399 |
+ |
/// That is the cheapest possible version of this failure and it still cost a
|
|
400 |
+ |
/// mint. The three are one fact and this is what makes them agree.
|
|
401 |
+ |
#[test]
|
|
402 |
+ |
fn the_language_whitelist_agrees_across_all_three_files() {
|
|
403 |
+ |
let text = containerfile();
|
|
404 |
+ |
let from_containerfile: Vec<String> = text
|
|
405 |
+ |
.lines()
|
|
406 |
+ |
.find_map(|line| {
|
|
407 |
+ |
let line = line.trim();
|
|
408 |
+ |
let body = line
|
|
409 |
+ |
.strip_suffix(") ;; \\")
|
|
410 |
+ |
.or_else(|| line.strip_suffix(") ;;"))?;
|
|
411 |
+ |
body.contains("rust").then(|| {
|
|
412 |
+ |
body.split('|')
|
|
413 |
+ |
.map(|s| s.trim().to_string())
|
|
414 |
+ |
.collect::<Vec<_>>()
|
|
415 |
+ |
})
|
|
416 |
+ |
})
|
|
417 |
+ |
.expect("no LANGS case arm in the Containerfile");
|
|
418 |
+ |
|
|
419 |
+ |
let image =
|
|
420 |
+ |
std::fs::read_to_string(PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src/image.rs"))
|
|
421 |
+ |
.expect("cannot read image.rs");
|
|
422 |
+ |
let from_enum: Vec<String> = image
|
|
423 |
+ |
.split_once("const ALL: [Lang;")
|
|
424 |
+ |
.and_then(|(_, rest)| rest.split_once('['))
|
|
425 |
+ |
.and_then(|(_, rest)| rest.split_once(']'))
|
|
426 |
+ |
.map(|(list, _)| {
|
|
427 |
+ |
list.split(',')
|
|
428 |
+ |
.filter_map(|e| e.trim().strip_prefix("Lang::").map(str::to_lowercase))
|
|
429 |
+ |
.collect()
|
|
430 |
+ |
})
|
|
431 |
+ |
.expect("no Lang::ALL in image.rs");
|
|
432 |
+ |
|
|
433 |
+ |
let preflight = std::fs::read_to_string(
|
|
434 |
+ |
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../build/preflight.py"),
|
|
435 |
+ |
)
|
|
436 |
+ |
.expect("cannot read preflight.py");
|
|
437 |
+ |
let from_preflight: Vec<String> = preflight
|
|
438 |
+ |
.lines()
|
|
439 |
+ |
.find_map(|line| {
|
|
440 |
+ |
let rest = line.trim().strip_prefix("if lang not in (")?;
|
|
441 |
+ |
Some(
|
|
442 |
+ |
rest.trim_end_matches("):")
|
|
443 |
+ |
.split(',')
|
|
444 |
+ |
.map(|s| s.trim().trim_matches('"').to_string())
|
|
445 |
+ |
.filter(|s| !s.is_empty())
|
|
446 |
+ |
.collect(),
|
|
447 |
+ |
)
|
|
448 |
+ |
})
|
|
449 |
+ |
.expect("no language tuple in preflight.py");
|
|
450 |
+ |
|
|
451 |
+ |
let sorted = |mut v: Vec<String>| {
|
|
452 |
+ |
v.sort();
|
|
453 |
+ |
v
|
|
454 |
+ |
};
|
|
455 |
+ |
let (a, b, c) = (
|
|
456 |
+ |
sorted(from_containerfile),
|
|
457 |
+ |
sorted(from_enum),
|
|
458 |
+ |
sorted(from_preflight),
|
|
459 |
+ |
);
|
|
460 |
+ |
assert_eq!(
|
|
461 |
+ |
a, b,
|
|
462 |
+ |
"the Containerfile validator and Lang::ALL name different languages",
|
|
463 |
+ |
);
|
|
464 |
+ |
assert_eq!(
|
|
465 |
+ |
b, c,
|
|
466 |
+ |
"Lang::ALL and build/preflight.py name different languages",
|
|
467 |
+ |
);
|
|
468 |
+ |
}
|