crates/gunnar-recipe/tests/plain_cargo_cells.rs
//! **The Build Thing's client and server cells are plain cargo builds.**
//!
//! Owner order 2026-10-12: the client (`gunnar-cli` since GUN-PRODUCTS) and core `gunnar-server` must
//! build with ordinary cargo AND as Build Thing cells, from the same code. A
//! cell is `cargo build -p <package> --bin <bin> [--no-default-features]
//! --features …`; this file holds the cells to exactly the commands
//! `crates/gunnar-cli/README.md` ("Building with plain cargo") prints, so the
//! two ways of building cannot drift into two programs. The graph half — what
//! those commands link — is `crates/gunnar-cli/tests/plain_cargo.rs`.
use gunnar_recipe::variants::{by_name, Variant, VARIANTS};
/// The cargo arguments a cell's build passes (target and profile aside).
fn cargo_args(v: &Variant) -> String {
let mut args = vec![format!(
"-p {}",
v.package.expect("a cargo cell names its package")
)];
if let Some(bin) = v.bin {
args.push(format!("--bin {bin}"));
}
if v.no_default_features {
args.push("--no-default-features".into());
}
if !v.features.is_empty() {
args.push(format!("--features {}", v.features.join(",")));
}
args.join(" ")
}
#[test]
fn the_client_cells_are_the_plain_cargo_client_build() {
// GUN-PRODUCTS (2026-10-30): the command-line tool is ONE program, `gunnar-cli`, and
// every cell that delivers it is the product's build (its defaults, named out loud).
let documented =
"-p gunnar-cli --bin gunnar-cli --no-default-features --features client,control";
for name in ["gunnar-cli", "gunnar-cli-musl"] {
let v = by_name(name).unwrap_or_else(|| panic!("the {name} cell is declared"));
assert_eq!(
cargo_args(v),
documented,
"the {name} cell drifted from plain cargo"
);
}
let cli_bins: Vec<&Variant> = VARIANTS
.iter()
.filter(|v| v.bin == Some("gunnar-cli"))
.collect();
assert!(
cli_bins.len() >= 2,
"{} cells deliver `gunnar-cli`",
cli_bins.len()
);
for v in cli_bins {
assert_eq!(
cargo_args(v),
documented,
"cell {} builds a different `gunnar-cli`",
v.name
);
}
// No cell delivers a `gunnar` from gunnar-cli any more: that file is the server's.
assert!(
!VARIANTS
.iter()
.any(|v| v.bin == Some("gunnar") && v.package == Some("gunnar-cli")),
"a gunnar-cli cell still delivers `gunnar`, the server's file name"
);
}
#[test]
fn the_server_cells_build_core_gunnar_server_and_only_metered_ones_add_money() {
let server: Vec<&Variant> = VARIANTS
.iter()
.filter(|v| v.bin == Some("gunnar-server"))
.collect();
assert!(server.len() >= 3, "{} gunnar-server cells", server.len());
for v in server {
assert_eq!(
v.package,
Some("gunnar-server"),
"cell {} builds another package",
v.name
);
let money = v.features.contains(&"metered");
assert_eq!(
money,
v.name.contains("metered"),
"cell {}: `metered` (monetize) must be on exactly the cells named for it",
v.name
);
for f in v.features {
assert!(
!f.contains("ui") && !f.contains("shop") && !f.starts_with("monetize"),
"cell {} turns on {f}: core gunnar-server has no UI or shop",
v.name
);
}
}
}