ci/verbs/tests/ci_image_has_clippy_and_rustfmt.rs
//! Guard: the CI test image carries clippy AND rustfmt for the PINNED toolchain (gate lane,
//! 2026-10-25: some lanes' `nordisk-cargo-toolchain:ci2-53fec13a5c1c7f2c` had no clippy for
//! 1.97.1, and none has rustfmt: `cargo fmt` answers "'cargo-fmt' is not installed for the
//! toolchain '1.97.1-x86_64-unknown-linux-gnu'").
//!
//! A bare `rustup component add clippy` lands on whatever toolchain is active while the image
//! builds; the Containerfile names the pinned one, and proves both tools answer before the
//! image is tagged. The runner builds its image only when the tag is missing, so a change to
//! what the image carries moves the tag too.
use std::path::Path;
/// The toolchain dwarves pins (`[build.toolchain] rust_version`).
const PINNED: &str = "1.97.1";
fn read(rel: &str) -> String {
let p = Path::new(env!("CARGO_MANIFEST_DIR")).join("../..").join(rel);
std::fs::read_to_string(&p).unwrap_or_else(|e| panic!("{}: {e}", p.display()))
}
/// The Containerfile's non-comment text, with its line continuations joined.
fn containerfile() -> String {
let s = read("ci/ci-toolchain.Containerfile");
let code: Vec<&str> = s.lines().filter(|l| !l.trim_start().starts_with('#')).collect();
code.join("\n").replace("\\\n", " ")
}
#[test]
fn the_pin_this_guard_names_is_the_declared_one() {
let v = read(".nornir/build-variants.toml");
let table = v.split("\n[build.toolchain]\n").nth(1).expect("dwarves declares [build.toolchain]");
assert!(table.contains(&format!("rust_version = \"{PINNED}\"")), "the declared toolchain is {PINNED}");
}
#[test]
fn the_ci_image_adds_clippy_and_rustfmt_for_the_pinned_toolchain() {
let c = containerfile();
let add = c
.split("rustup component add")
.skip(1)
.find(|rest| rest.split("&&").next().unwrap_or("").contains(&format!("--toolchain {PINNED}")))
.unwrap_or_else(|| panic!("a `rustup component add --toolchain {PINNED} …`:\n{c}"));
let add = add.split("&&").next().unwrap();
assert!(add.contains("clippy"), "clippy for {PINNED}: {add}");
assert!(add.contains("rustfmt"), "rustfmt for {PINNED}: {add}");
assert!(c.contains(&format!("cargo +{PINNED} clippy --version")), "the build proves clippy answers");
assert!(c.contains(&format!("cargo +{PINNED} fmt --version")), "the build proves rustfmt answers");
}
#[test]
fn the_runner_builds_the_image_under_a_tag_that_carries_the_tools() {
let r = read("ci/runner.sh");
let line = r.lines().find(|l| l.starts_with("CI_IMAGE=")).expect("runner.sh sets CI_IMAGE");
assert!(
!line.contains(":ci2-") && !line.contains(":ci-"),
"ci and ci2 were built without rustfmt (and in some lanes without clippy); the runner \
builds only a missing tag, so it must name a new one: {line}"
);
assert!(r.contains("ci/ci-toolchain.Containerfile"), "the runner builds it from the Containerfile");
}