xtask/tests/the_published_line.rs
//! **The crates other repositories take from crates.io are ONE version line, above every
//! version crates.io already has** (A3, R5 prep; the H20 unblock in p4-rest.md and p4p5.md).
//!
//! Gunnar's lone clone (gunnar-server, gunnar-grpc, gunnar-repair) and Holger's (holger-fleet,
//! server-lib) take these crates from crates.io. Before 0.3.0 there were two lines: the tree
//! said 0.1.3 while crates.io had monetize-embed 0.2.0 (published 2026-10-04 from 30b5756f,
//! without `fleet.rs`, `sold.rs` and `testing`), and monetize-product 0.2.0 (the old API). A
//! requirement `^0.2` could not be met by a 0.1.3 sibling, and `^0.1.3` resolved to an embed
//! without the fleet forms. One line, numbered above all of that, ends both.
//!
//! What it asserts, read from the manifests (xtask has no TOML dependency, so the reader is a
//! small line scanner):
//! 1. every crate in [`LINE`] is `publish = true` and carries the workspace version, or its own
//! PATCH on the same line (monetize-proto 0.3.1 since PUB-R5D, 2026-10-27: published on its
//! own, a patch above the workspace's 0.3.0, so every `^0.3.0` requirement takes it);
//! 2. that version is above the highest version crates.io has for each of them ([`ON_CRATES_IO`]);
//! 3. every `version = "…"` written beside a `path` to a [`LINE`] crate, in any manifest of this
//! repository, asks for a version on that line the crate has (the workspace's, up to its own
//! patch: [`asks_the_line`]), so a published crate never names the old line.
use std::path::{Path, PathBuf};
use xtask::release_version::workspace_version;
/// The crates.io line: what Gunnar and Holger take from the registry.
const LINE: [(&str, &str); 6] = [
("monetize-product", "crates/monetize-product"),
("monetize-product-guest", "crates/monetize-product-guest"),
("monetize-embed", "crates/monetize-embed"),
("monetize-proto", "crates/monetize-proto"),
("monetize-estate-side", "crates/monetize-estate-side"),
("monetize-mail", "crates/monetize-mail"),
];
/// The highest version crates.io had of each, read from index.crates.io on 2026-10-26.
const ON_CRATES_IO: [(&str, &str); 4] = [
("monetize-product", "0.2.0"),
("monetize-product-guest", "0.2.0"),
("monetize-embed", "0.2.0"),
("monetize-proto", "0.1.3"),
];
fn root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).parent().expect("xtask/ has a parent").to_path_buf()
}
fn read(p: &Path) -> String {
std::fs::read_to_string(p).unwrap_or_else(|e| panic!("read {}: {e}", p.display()))
}
fn triple(v: &str) -> (u64, u64, u64) {
let mut it = v.split('.').map(|n| n.parse::<u64>().unwrap_or_else(|_| panic!("not a version: {v}")));
(it.next().unwrap(), it.next().unwrap(), it.next().unwrap())
}
/// The value of `key = …` under `[package]`, `None` if absent.
fn package_key(manifest: &str, key: &str) -> Option<String> {
let mut in_package = false;
for line in manifest.lines() {
let t = line.trim();
if t.starts_with('[') {
in_package = t == "[package]";
continue;
}
if !in_package {
continue;
}
if let Some(rest) = t.strip_prefix(key) {
let rest = rest.trim_start();
if let Some(rhs) = rest.strip_prefix('=') {
return Some(rhs.trim().trim_matches('"').to_string());
}
if rest.starts_with(".workspace") {
return Some("<workspace>".into());
}
}
}
None
}
fn manifests(dir: &Path, out: &mut Vec<PathBuf>) {
for e in std::fs::read_dir(dir).unwrap().flatten() {
let p = e.path();
let name = e.file_name();
let name = name.to_string_lossy();
if p.is_dir() {
if name == "target" || name.starts_with('.') {
continue;
}
manifests(&p, out);
} else if name == "Cargo.toml" {
out.push(p);
}
}
}
/// A crate is on the line when it carries the workspace version, or its own patch above it on
/// the same 0.x line (same major and minor, patch at or above the workspace's): a `^ws`
/// requirement then takes it, so it is still the one line. A lower patch or another minor is not.
fn on_the_line(v: &str, ws: &str) -> bool {
let (a, w) = (triple(v), triple(ws));
a.0 == w.0 && a.1 == w.1 && a.2 >= w.2
}
/// A requirement beside a `path` asks for the line when it names a version on it that the crate
/// HAS: the workspace's, or up to the crate's own patch (MHUNT-4, 2026-10-28: monetize-proto is
/// 0.3.2 on its own and monetize-site-buy asks `0.3.2`, since crates.io's 0.3.1 lacks the A32-29
/// words; rule 1 allowed the patch and rule 3 refused the requirement that needs it).
fn asks_the_line(req: &str, ws: &str, own: &str) -> bool {
let req = req.trim_start_matches(['^', '=']);
on_the_line(req, ws) && triple(req) <= triple(own)
}
#[test]
fn a_requirement_asks_the_line_up_to_the_crates_own_patch() {
assert!(asks_the_line("0.3.0", "0.3.0", "0.3.2"), "the workspace's version");
assert!(asks_the_line("0.3.2", "0.3.0", "0.3.2"), "the crate's own patch (monetize-site-buy → monetize-proto)");
assert!(asks_the_line("^0.3.1", "0.3.0", "0.3.2"), "a patch below its own");
assert!(!asks_the_line("0.3.3", "0.3.0", "0.3.2"), "a patch the crate does not have");
assert!(!asks_the_line("0.2.0", "0.3.0", "0.3.2"), "the old line");
assert!(!asks_the_line("0.3.1", "0.3.0", "0.3.0"), "a patch above a crate on the workspace's version");
}
#[test]
fn a_crate_carrying_its_own_published_patch_is_on_the_line_and_another_line_is_not() {
assert!(on_the_line("0.3.0", "0.3.0"), "the workspace version itself");
assert!(on_the_line("0.3.1", "0.3.0"), "its own patch above the workspace's (monetize-proto, PUB-R5D)");
assert!(!on_the_line("0.2.9", "0.3.0"), "the old line");
assert!(!on_the_line("0.4.0", "0.3.0"), "a line the workspace has not reached");
assert!(!on_the_line("0.3.0", "0.3.1"), "a patch below the workspace's");
assert!(!on_the_line("1.3.0", "0.3.0"), "another major");
}
#[test]
fn the_crates_io_line_is_one_version_above_everything_crates_io_has() {
let root = root();
let ws = workspace_version(&root);
let mut wrong = Vec::new();
for (name, dir) in LINE {
let m = read(&root.join(dir).join("Cargo.toml"));
assert_eq!(package_key(&m, "name").as_deref(), Some(name), "{dir} is {name}");
let v = match package_key(&m, "version").as_deref() {
Some("<workspace>") => ws.clone(),
Some(v) => v.to_string(),
None => panic!("{name} has no version"),
};
if !on_the_line(&v, &ws) {
wrong.push(format!("{name} is {v}, not on the workspace's line {ws}"));
}
if package_key(&m, "publish").as_deref() != Some("true") {
wrong.push(format!("{name} is not `publish = true`"));
}
if let Some((_, top)) = ON_CRATES_IO.iter().find(|(c, _)| *c == name) {
// 0.x: a new line is a new MINOR above the highest published one.
let (a, b) = (triple(&v), triple(top));
if !(a.0 > b.0 || (a.0 == b.0 && a.1 > b.1)) {
wrong.push(format!("{name} {v} is not a new line above crates.io's {top}"));
}
}
}
assert!(wrong.is_empty(), "the crates.io line is not one line:\n {}", wrong.join("\n "));
}
#[test]
fn every_requirement_on_the_line_asks_for_the_line() {
let root = root();
let ws = workspace_version(&root);
let mut all = Vec::new();
manifests(&root, &mut all);
assert!(all.len() > 20, "found only {} manifests under {}", all.len(), root.display());
// Each crate's own version (its patch, or the workspace's).
let own = |dir: &str| match package_key(&read(&root.join(dir).join("Cargo.toml")), "version").as_deref() {
Some("<workspace>") | None => ws.clone(),
Some(v) => v.to_string(),
};
let mut wrong = Vec::new();
let mut seen = 0;
for p in &all {
for (no, line) in read(p).lines().enumerate() {
let t = line.trim_start();
for (name, dir) in LINE {
let Some(rest) = t.strip_prefix(name) else { continue };
let rest = rest.trim_start();
if !rest.starts_with('=') || !rest.contains("path") {
continue;
}
let Some(at) = rest.find("version") else { continue };
let req = rest[at..].split('"').nth(1).unwrap_or("");
seen += 1;
if !asks_the_line(req, &ws, &own(dir)) {
wrong.push(format!("{}:{}: {name} asks {req:?}, the line is {ws} (the crate is {})", p.strip_prefix(&root).unwrap().display(), no + 1, own(dir)));
}
}
}
}
assert!(seen >= 4, "found only {seen} versioned path requirements on the line, so this checked nothing");
assert!(wrong.is_empty(), "a requirement names another line:\n {}", wrong.join("\n "));
}