xtask/tests/readme_version.rs
//! The front page may not name a version this tree cannot produce.
//!
//! ⚠ A KNOWN TWIN — `monetize/xtask/tests/readme_version.rs`, and gunnar's.
//! `nornir mimir analyze duplicates` measured 38 diff lines across 166 on
//! 2026-09-10, and every code difference is the crate name. It stays for the
//! reason `../src/release_version.rs` records at length: an xtask here has ZERO
//! dependencies by a repository rule, so it cannot reach the shared leaf that
//! would hold it (`edda/crates/nornir-xtask-support`).
//!
//! # What it asserts
//!
//! Every `gunnar-ui <version>` literal in the root `README.md` equals
//! `[workspace.package] version`.
//!
//! # Why it also counts what it found
//!
//! A scanner that matches nothing passes. Deleting the sentence — or renaming
//! the binary in it — would turn this guard off silently, which is the exact
//! disease it was written for. So it asserts a **non-zero census** and names
//! the count.
//!
//! This test is one of the three steps `cargo xtask publish` runs against the
//! PUBLIC worktree, so the sentence has to survive into the published tree too.
use std::path::{Path, PathBuf};
use xtask::release_version::{disagreements, scan, triple, workspace_version};
fn workspace_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("xtask/ has a parent")
.to_path_buf()
}
fn read(root: &Path, rel: &str) -> String {
std::fs::read_to_string(root.join(rel)).unwrap_or_else(|e| panic!("read {rel}: {e}"))
}
#[test]
fn the_readme_names_the_workspace_version() {
let root = workspace_root();
let version = workspace_version(&root);
let text = read(&root, "README.md");
let census = scan(&text).found;
assert!(
!census.is_empty(),
"README.md contains no `gunnar-ui <version>` literal at all, so this guard checked \
nothing. That is how it would be turned off by accident: the sentence naming the \
binary was removed or reworded. Restore a line that names the version, or delete \
this guard on purpose."
);
let wrong = disagreements(&text, &version);
assert!(
wrong.is_empty(),
"[workspace.package] version is `{version}`, but README.md names {wrong:?}. The \
front page of the release advertises a build this tree cannot produce."
);
}
/// The scanner must be able to say NO, and must be seen finding the thing it is
/// looking for.
///
/// Driven against synthetic inputs, because the live tree is (by the test
/// above) the matching case — and a scanner that answered "no disagreements" to
/// every input would satisfy it and be worth nothing.
#[test]
fn the_scanner_finds_a_wrong_version_and_is_not_satisfied_by_an_empty_file() {
// ── red ─────────────────────────────────────────────────────────────────
let stale = "This page documents\n**gunnar-ui 0.0.1** — the version it prints.\n";
let wrong = disagreements(stale, "0.1.0");
assert_eq!(wrong.len(), 1, "the stale line must be caught: {wrong:?}");
assert_eq!(wrong[0].version, "0.0.1");
assert_eq!(wrong[0].line, 2, "the line number must name where to look");
// ── green, and for the right reason: it FOUND one and it agreed ──────────
let fixed = "This page documents\n**gunnar-ui 0.1.0** — the version it prints.\n";
assert!(disagreements(fixed, "0.1.0").is_empty());
assert_eq!(
scan(fixed).found.len(),
1,
"the fixed line must still be SEEN, or the green above is vacuous"
);
// ── the census, seen empty ──────────────────────────────────────────────
let deleted = "One binary is the BFF.\n";
assert!(disagreements(deleted, "0.1.0").is_empty());
assert!(
scan(deleted).found.is_empty(),
"and the census must report zero, so the test above can refuse it"
);
// ── every spelling of the claim ─────────────────────────────────────────
for token in [
"gunnar-ui 0.0.1",
"gunnar-ui-server 0.0.1",
"gunnar-ui-web 0.0.1",
] {
assert_eq!(
disagreements(token, "0.1.0").len(),
1,
"`{token}` names a stale version and was not caught"
);
}
// ── the package names are not read as the bare workspace name ───────────
// `gunnar-ui ` requires the space, so `gunnar-ui-server 0.0.1` is ONE
// literal and not two.
assert_eq!(scan("gunnar-ui-server 0.0.1").found.len(), 1);
// ── two-component versions are the same release ─────────────────────────
assert!(disagreements("gunnar-ui 0.1", "0.1.0").is_empty());
assert_eq!(triple("0.1"), triple("0.1.0"));
// ── things that are NOT a claim about gunnar-ui's version ───────────────
// The README quotes other projects. Flagging them would get the guard
// switched off within a day.
for quiet in [
"measured against leptos 0.8 and axum 0.8",
"git clone ssh://git.gunnar.rs:2222/gunnar/gunnar-ui",
"cargo build --release -p gunnar-ui-server",
"the gunnar-ui 1 release",
] {
assert!(
scan(quiet).found.is_empty(),
"`{quiet}` is not a version claim about this workspace, but was scanned as \
one: {:?}",
scan(quiet).found
);
}
// ── trailing punctuation is not a fourth component ──────────────────────
let sentence = "built by gunnar-ui 0.1.0.";
assert_eq!(scan(sentence).found[0].version, "0.1.0");
assert!(disagreements(sentence, "0.1.0").is_empty());
// ── more than one literal on one line, both judged ──────────────────────
let two = "gunnar-ui 0.0.1 is served by gunnar-ui-server 0.1.0";
assert_eq!(scan(two).found.len(), 2);
assert_eq!(disagreements(two, "0.1.0").len(), 1);
}
/// `workspace_version` must read the real field, not the first `version =` it
/// trips over.
///
/// The root manifest carries `version = "…"` lines under
/// `[workspace.dependencies]` too, so a reader that took the first match would
/// answer whatever those happened to say.
#[test]
fn the_workspace_version_comes_from_the_workspace_package_table() {
let root = workspace_root();
let version = workspace_version(&root);
assert!(
triple(&version).is_some(),
"[workspace.package] version `{version}` is not a numeric release name"
);
let text = read(&root, "Cargo.toml");
let after_table: String = text
.split("[workspace.package]")
.nth(1)
.expect("the root manifest has a [workspace.package] table")
.lines()
.take_while(|l| !l.trim_start().starts_with('['))
.collect::<Vec<_>>()
.join("\n");
assert!(
after_table.contains(&format!("version = \"{version}\"")),
"`{version}` was not read out of the [workspace.package] table itself"
);
}