xtask/tests/one_dependency.rs
//! **xtask has ONE dependency: the recipe** (THE LAW: "xtask is a THIN DOOR onto
//! the build thing … ONE dependency in xtask/Cargo.toml: the repo's own recipe").
//!
//! Until 2026-09-30 it had twelve, then eleven: the verbs that build nothing
//! (publish-pip, publish-holger, verify-install, streamlit, test-demo, demo-wasm,
//! deploy-pages, panel-wasm) pulled draupnir, zip, sha2, base64, 4× znippy,
//! nornir-pages, nornir-xtask-support and facett-wasm-demo. They moved to
//! `facett-ops` (`cargo ops <verb>`); see `.nornir/xtask-thin-door-audit.md` §7.
//!
//! Held twice, because the two readers fail differently: the MANIFEST (every
//! `[dependencies]` / `[target.*.dependencies]` table — a target-gated second dep is
//! still a second dep), and cargo's own resolved graph (`cargo tree -p xtask -e normal
//! --depth 1`, the owner's measurement), which also catches a dep smuggled in by
//! some other spelling.
use std::path::PathBuf;
fn root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).parent().unwrap().to_path_buf()
}
/// The one dependency the law allows.
const THE_RECIPE: &str = "facett-build-thing";
/// Every normal dependency the manifest names, across every normal-deps table.
fn manifest_deps() -> Vec<String> {
parse_deps(&std::fs::read_to_string(root().join("xtask/Cargo.toml")).unwrap())
}
/// The normal dependencies a manifest text names.
fn parse_deps(text: &str) -> Vec<String> {
let mut deps = Vec::new();
let mut in_normal = false;
for line in text.lines() {
let t = line.trim();
if t.starts_with('[') {
let h = t.trim_matches(|c| c == '[' || c == ']');
// `[dependencies]`, `[target.'cfg(…)'.dependencies]`; not dev/build.
in_normal = h == "dependencies" || (h.starts_with("target.") && h.ends_with(".dependencies"));
// `[dependencies.foo]` spells one dep as a table.
if let Some(name) = h.strip_prefix("dependencies.") {
deps.push(name.to_string());
}
continue;
}
if in_normal && !t.is_empty() && !t.starts_with('#') && let Some((name, _)) = t.split_once('=') {
deps.push(name.trim().to_string());
}
}
deps
}
#[test]
fn the_manifest_names_the_recipe_and_nothing_else() {
let deps = manifest_deps();
assert_eq!(
deps,
vec![THE_RECIPE.to_string()],
"xtask/Cargo.toml must name ONE normal dependency, the recipe. A verb that needs \
more is not a build — it belongs in facett-ops (`cargo ops <verb>`)."
);
}
#[test]
fn cargo_tree_shows_exactly_one_normal_dependency() {
let cargo = std::env::var_os("CARGO").unwrap_or_else(|| "cargo".into());
let out = std::process::Command::new(cargo)
.current_dir(root())
.args(["tree", "-p", "xtask", "-e", "normal", "--depth", "1", "--prefix", "depth", "--offline"])
.output()
.expect("spawn cargo tree");
let text = String::from_utf8_lossy(&out.stdout);
assert!(out.status.success(), "cargo tree failed:\n{}", String::from_utf8_lossy(&out.stderr));
// `--prefix depth`: `0xtask v…` then one `1<name> v…` line per direct dep.
let direct: Vec<&str> = text
.lines()
.filter_map(|l| l.strip_prefix('1'))
.map(|l| l.split_whitespace().next().unwrap_or(""))
.collect();
assert_eq!(direct, vec![THE_RECIPE], "cargo tree -p xtask -e normal --depth 1:\n{text}");
}
/// The red half: the parser actually sees a second dep, in each spelling a manifest
/// can use — a guard that could not go red would hold nothing.
#[test]
fn a_second_dependency_would_be_seen() {
let parse = parse_deps;
assert_eq!(parse("[dependencies]\na = \"1\"\nb = \"1\"\n[dev-dependencies]\nc = \"1\"\n"), ["a", "b"]);
assert_eq!(parse("[dependencies]\na = \"1\"\n[target.'cfg(unix)'.dependencies]\nz = \"1\"\n"), ["a", "z"]);
assert_eq!(parse("[dependencies]\na = \"1\"\n[dependencies.q]\nversion = \"1\"\n"), ["a", "q"]);
}