xtask/tests/open_crates.rs
//! **`cargo xtask open-crates`** end to end (A28 H22, M27; lane N4): the built verb over a sibling
//! layout made here, and the forge's own `release` words it hands on.
//!
//! The forge is stood in for by `$DWARVES_BIN` = `echo`, so the test sees exactly the words
//! `dwarves` would be handed and nothing reaches crates.io.
use std::path::{Path, PathBuf};
use std::process::Command;
/// The crates and where they live (the verb's own list must name these).
const LAYOUT: &[(&str, &str, &str)] = &[
("tunnr", "crates/korp-installer", "korp-installer"),
("edda", "crates/appliance-grow", "appliance-grow"),
("edda", "crates/nornir-auth-policy", "nornir-auth-policy"),
("edda", "crates/nornir-xtask-support", "nornir-xtask-support"),
("monetize", "crates/monetize-proto", "monetize-proto"),
("monetize", "crates/estate-svg", "nordisk-estate-svg"),
("monetize", "crates/monetize-product-guest", "monetize-product-guest"),
("Skidbladnir", "crates/installer/nordisk-pairing", "nordisk-pairing"),
("Skidbladnir", "crates/installer/nordisk-twin", "nordisk-twin"),
];
fn version_of(name: &str) -> &'static str {
match name {
"korp-installer" => "0.2.7",
"nornir-auth-policy" | "monetize-product-guest" => "0.1.5",
"nornir-xtask-support" => "0.1.4",
"monetize-proto" => "0.1.3",
"nordisk-pairing" => "0.5.26",
"nordisk-twin" => "0.1.2",
_ => "0.1.0",
}
}
/// A sibling layout with every crate at the version needed, except `stale` (one patch below).
fn layout(tag: &str, stale: Option<&str>) -> PathBuf {
let d = std::env::temp_dir().join(format!("open-crates-{tag}-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&d);
for (repo, dir, name) in LAYOUT {
let at = d.join(repo).join(dir);
std::fs::create_dir_all(&at).unwrap();
let v = if Some(*name) == stale { "0.1.4" } else { version_of(name) };
let manifest = if *name == "monetize-proto" { format!("[package]\nname = \"{name}\"\nversion.workspace = true\n") } else { format!("[package]\nname = \"{name}\"\nversion = \"{v}\"\n") };
std::fs::write(at.join("Cargo.toml"), manifest).unwrap();
}
std::fs::write(d.join("monetize/Cargo.toml"), "[workspace]\nmembers = []\n[workspace.package]\nversion = \"0.1.3\"\n").unwrap();
d
}
fn xtask(args: &[&str], sib: &Path) -> (i32, String, String) {
let o = Command::new(env!("CARGO_BIN_EXE_xtask")).arg("open-crates").args(args).arg("--siblings").arg(sib).env("DWARVES_BIN", "echo").output().unwrap();
(o.status.code().unwrap_or(-1), String::from_utf8_lossy(&o.stdout).into_owned(), String::from_utf8_lossy(&o.stderr).into_owned())
}
/// ★ The plan runs nothing and names ONE `dwarves release` over the four repositories.
#[test]
fn the_plan_names_one_release_line_and_runs_nothing() {
let sib = layout("plan", None);
let (code, out, err) = xtask(&[], &sib);
assert_eq!(code, 0, "{out}{err}");
let s = sib.display();
assert!(out.contains(&format!(" dwarves release {s}/tunnr {s}/edda {s}/monetize {s}/Skidbladnir --only korp-installer,appliance-grow,nornir-auth-policy,nornir-xtask-support,monetize-proto,nordisk-estate-svg,monetize-product-guest,nordisk-pairing,nordisk-twin\n")), "{out}");
assert!(out.contains("PLAN ONLY"), "{out}");
assert!(!out.contains("\nrelease "), "the forge was not run in plan mode: {out}");
let _ = std::fs::remove_dir_all(&sib);
}
/// ★ `--live-dry-run` hands the forge exactly its own words; a HOLD crate is left out by name,
/// and `--execute` with one is refused before the forge is asked anything.
#[test]
fn the_dry_run_hands_the_forge_its_words_and_execute_refuses_a_hold() {
let sib = layout("hold", Some("monetize-product-guest"));
let (code, out, err) = xtask(&["--live-dry-run"], &sib);
assert_eq!(code, 0, "{out}{err}");
assert!(out.contains("monetize-product-guest") && out.contains("HOLD: the tree says 0.1.4; bump it to 0.1.5 first"), "{out}");
let echoed = out.lines().find(|l| l.starts_with("release ")).unwrap_or_else(|| panic!("the forge was handed nothing: {out}"));
assert!(echoed.ends_with(" --live-dry-run") && !echoed.contains("monetize-product-guest,") && !echoed.contains(",monetize-product-guest"), "{echoed}");
let (code, out, err) = xtask(&["--execute"], &sib);
assert_eq!(code, 1, "{out}{err}");
assert!(err.contains("REFUSED [open-crates-hold] monetize-product-guest"), "{err}");
assert!(!out.lines().any(|l| l.starts_with("release ")), "nothing was handed the forge: {out}");
let _ = std::fs::remove_dir_all(&sib);
}
/// ★ Across crates: the words are the ones the forge's own `dwarves release` takes — several
/// repository paths, `--only`, `--live-dry-run` (read-only) and `--execute` — and it refuses
/// monetize-impl itself.
#[test]
fn the_words_are_the_forges_own_release_words() {
let cli = std::fs::read_to_string(Path::new(env!("CARGO_MANIFEST_DIR")).join("../crates/dwarves/src/main.rs")).unwrap();
let release = &cli[cli.find(" Release {").expect("dwarves' `release` verb")..];
let release = &release[..release.find(" },").unwrap()];
for field in ["path: Vec<PathBuf>", "execute: bool", "only: Vec<String>", "live_dry_run: bool"] {
assert!(release.contains(field), "dwarves release takes {field}");
}
assert!(cli.contains("dwarves::release::refuse_never_published(only)"), "the forge refuses monetize-impl by itself");
}