xtask/tests/cargo_install_gunnar.rs
//! **`cargo install gunnar` installs a startable Gunnar server** (owner order 2026-10-22).
//!
//! Three properties, each red when broken:
//!
//! 1. `crates/gunnar` is a workspace member named `gunnar` that may be published and
//! ships a `gunnar` binary, so `cargo install gunnar` has something to install.
//! 2. **The default build compiles no monetize.** `cargo tree -p gunnar -e normal` with
//! the default features names no crate whose name contains `monetize`: no
//! monetize-impl, no open monetize crate, no metered or shop feature. Metered is
//! an opt-in feature and stays one.
//! 3. The crate's README documents the first start: `cargo install gunnar`, the
//! command, and the URL that answers health.
use std::path::{Path, PathBuf};
use std::process::Command;
fn root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("xtask has a parent")
.to_path_buf()
}
fn cargo() -> String {
std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string())
}
#[test]
fn the_gunnar_crate_is_a_publishable_server_binary() {
let manifest = root().join("crates/gunnar/Cargo.toml");
let text = std::fs::read_to_string(&manifest).unwrap_or_else(|e| {
panic!(
"{} is missing ({e}): `cargo install gunnar` installs nothing",
manifest.display()
)
});
let lines: Vec<&str> = text
.lines()
.map(str::trim)
.filter(|l| !l.starts_with('#'))
.collect();
assert!(
lines.contains(&"name = \"gunnar\""),
"crates/gunnar is not the package `gunnar`"
);
assert!(
lines.contains(&"publish = true"),
"crates/gunnar must say `publish = true` by name"
);
assert!(
text.contains("[[bin]]") && text.contains("path = \"src/main.rs\""),
"crates/gunnar ships no binary"
);
}
/// Pure: the crate names in `cargo tree --prefix none` output that contain `monetize`.
fn monetize_in_tree(tree: &str) -> Vec<String> {
let mut out: Vec<String> = tree
.lines()
.filter_map(|l| l.split_whitespace().next())
.filter(|name| name.contains("monetize"))
.map(str::to_string)
.collect();
out.sort();
out.dedup();
out
}
#[test]
fn monetize_in_tree_sees_every_spelling() {
let tree = "gunnar v1.5.0\nmonetize-embed v0.1.3\nmonetize-impl v0.1.0 (/x)\ngunnar-monetize-guest v1.5.0\nserde v1.0.0\n";
assert_eq!(
monetize_in_tree(tree),
vec!["gunnar-monetize-guest", "monetize-embed", "monetize-impl"]
);
assert!(monetize_in_tree("gunnar v1.5.0\nserde v1.0.0\n").is_empty());
}
#[test]
fn the_default_gunnar_build_compiles_no_monetize() {
let out = Command::new(cargo())
.args(["tree", "-p", "gunnar", "-e", "normal", "--prefix", "none"])
.current_dir(root())
.output()
.expect("run `cargo tree`");
assert!(
out.status.success(),
"`cargo tree -p gunnar -e normal` failed: {}",
String::from_utf8_lossy(&out.stderr)
);
let tree = String::from_utf8_lossy(&out.stdout);
assert!(
tree.lines().any(|l| l.starts_with("gunnar-server ")),
"the default gunnar build has no server in it:\n{tree}"
);
let found = monetize_in_tree(&tree);
assert!(
found.is_empty(),
"the DEFAULT `gunnar` build compiles monetize crates: {found:?}"
);
}
#[test]
fn the_readme_documents_the_first_start() {
let readme = std::fs::read_to_string(root().join("crates/gunnar/README.md"))
.expect("crates/gunnar/README.md");
for needle in [
"cargo install gunnar",
"\ngunnar\n",
"http://127.0.0.1:8080/healthz",
"--features",
] {
assert!(
readme.contains(needle),
"crates/gunnar/README.md does not document `{}`",
needle.trim()
);
}
}