xtask/tests/both_ways.rs
//! **Gunnar builds BOTH ways, and `cargo xtask ci` proves it on every push** (owner
//! order 2026-10-26: G-BOTH-1 plain cargo, G-BOTH-2 the Build Thing; lane GUN-INSTALL).
//!
//! The network halves are `cargo xtask crates-io-install [--stage]` and
//! `cargo xtask lone-clone [--stage]`; the measured twins are `cargo xtask twins`. These
//! tests hold the wiring and the one manifest fact the E0063 of 2026-10-26 came from.
use std::path::{Path, PathBuf};
fn root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("xtask has a parent")
.to_path_buf()
}
/// The `version = "…"` of a `name = { version = "…" … }` line in a manifest's text.
fn requirement(manifest: &str, name: &str) -> Option<String> {
manifest
.lines()
.map(str::trim)
.filter(|l| !l.starts_with('#'))
.find(|l| l.starts_with(&format!("{name} = ")))
.and_then(|l| l.split("version = \"").nth(1))
.and_then(|r| r.split('"').next())
.map(str::to_string)
}
/// The version the lock holds for a package (the first entry of that name).
fn locked(lock: &str, name: &str) -> Option<String> {
lock.split("[[package]]")
.find(|b| b.lines().any(|l| l.trim() == format!("name = \"{name}\"")))
.and_then(|b| {
b.lines().find_map(|l| {
l.trim()
.strip_prefix("version = \"")
.and_then(|r| r.strip_suffix('"'))
.map(str::to_string)
})
})
}
#[test]
fn requirement_and_locked_read_what_they_say() {
assert_eq!(
requirement(
"[x]\nnornir-build-thing = { version = \"0.2.5\", default-features = false }\n",
"nornir-build-thing"
)
.as_deref(),
Some("0.2.5")
);
assert_eq!(locked("[[package]]\nname = \"a\"\nversion = \"1.0.0\"\n\n[[package]]\nname = \"b\"\nversion = \"2.0.0\"\n", "b").as_deref(), Some("2.0.0"));
}
/// ★ **The E0063 of 2026-10-26.** gunnar-recipe names every field of edda's
/// `InstallerBuild`, `IsoBuild` and `ContainerBuild` in struct literals (on purpose: a
/// field added upstream gets a decision here). So the recipe compiles against exactly
/// the field set of the version it was written for, and its requirement must say that
/// version: `^0.2.5` let a 0.2.6 that the recipe had been changed for be swapped for
/// 0.2.5, and let the published 0.1.0 (written for 0.2.5) take 0.2.6. The requirement
/// is the version the lock builds.
#[test]
fn the_build_thing_requirement_is_the_version_the_recipe_compiles_against() {
let manifest = std::fs::read_to_string(root().join("Cargo.toml")).expect("root Cargo.toml");
let lock = std::fs::read_to_string(root().join("Cargo.lock")).expect("Cargo.lock");
let req = requirement(&manifest, "nornir-build-thing")
.expect("[workspace.dependencies] nornir-build-thing");
let held = locked(&lock, "nornir-build-thing").expect("the lock holds nornir-build-thing");
assert_eq!(
req, held,
"[workspace.dependencies] asks nornir-build-thing \"{req}\" while the lock builds {held}: gunnar-recipe's struct literals name the fields of {held}, so a resolution to any other 0.2.x does not compile (E0063, `cargo install gunnar` on 2026-10-26)"
);
}
/// ★ The lone clone runs on every `cargo xtask ci` (it was SKIPPED since 2026-10-08).
#[test]
fn the_lone_clone_row_is_not_paused() {
assert_eq!(
xtask::paid_guard::LONE_CLONE_PAUSED,
None,
"the lone-clone row is still paused: G-BOTH-1 needs it on every push"
);
}
/// ★ `cargo xtask ci` runs the both-ways steps by name.
#[test]
fn ci_runs_the_both_ways_steps() {
let main =
std::fs::read_to_string(root().join("xtask/src/main.rs")).expect("xtask/src/main.rs");
for step in [
"lone-clone",
"lone-clone-staged",
"crates-io-install",
"crates-io-install-staged",
"twins",
] {
assert!(
main.contains(&format!("ci_step(&mut ran, \"{step}\")")),
"`cargo xtask ci` has no `{step}` step"
);
}
for verb in [
"Some(\"crates-io-install\")",
"Some(\"twins\")",
"Some(\"lone-clone\")",
] {
assert!(main.contains(verb), "`cargo xtask` has no verb {verb}");
}
}
/// The README tells a user the line the crates.io step runs.
#[test]
fn the_readme_line_is_the_crates_io_step() {
let readme = std::fs::read_to_string(root().join("crates/gunnar/README.md"))
.expect("crates/gunnar/README.md");
let line = format!(
"cargo install {} --features {}",
xtask::both_ways::CRATES_IO_PACKAGE,
xtask::both_ways::CRATES_IO_FEATURES
);
assert!(
readme.contains(&line),
"crates/gunnar/README.md does not show `{line}`"
);
}