xtask/tests/readme_building.rs
//! **The README's build line works where the README says it does** (OX-VARIANTS
//! 2026-10-28: "make sure ordinary cargo build works for the variants in the docs").
//!
//! The root workspace's members take some crates by path from sibling checkouts
//! (`../../../edda/...`), so `git clone monetize && cargo build -p monetize-server`
//! alone fails ("failed to read `../edda/crates/guest-host/Cargo.toml`", measured in a
//! lone clone on oden). The `## Building` section must name every sibling checkout the
//! root members reach, so the documented line is the one that builds. Zero
//! dependencies (the xtask rule): the manifests are read as text.
use std::collections::BTreeSet;
use std::path::{Component, Path, PathBuf};
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()))
}
/// The quoted strings of the root manifest's `members = [ ... ]`.
fn members(manifest: &str) -> Vec<String> {
let start = manifest.find("members = [").expect("the root manifest lists its members");
let body = &manifest[start + "members = [".len()..];
let body = &body[..body.find(']').expect("the member list ends")];
body.lines()
.map(|l| l.split('#').next().unwrap_or(""))
.flat_map(|l| l.split('"').skip(1).step_by(2).map(str::to_string).collect::<Vec<_>>())
.collect()
}
/// The first directory outside the repository that `dir/<rel>` lands in, if any.
fn sibling_of(member_depth: usize, rel: &str) -> Option<String> {
let mut depth = member_depth as isize;
for c in Path::new(rel).components() {
match c {
Component::ParentDir => depth -= 1,
Component::Normal(n) => {
if depth < 0 {
return Some(n.to_string_lossy().into_owned());
}
depth += 1;
}
_ => {}
}
}
None
}
#[test]
fn the_building_section_names_every_sibling_the_root_members_reach() {
let root = root();
let mut siblings = BTreeSet::new();
for m in members(&read(&root.join("Cargo.toml"))) {
let text = read(&root.join(&m).join("Cargo.toml"));
let depth = Path::new(&m).components().count();
for line in text.lines().map(|l| l.split('#').next().unwrap_or("")) {
for part in line.split("path = \"").skip(1) {
let rel = &part[..part.find('"').unwrap_or(part.len())];
if let Some(s) = sibling_of(depth, rel) {
siblings.insert(s);
}
}
}
}
assert!(!siblings.is_empty(), "the scan found no sibling path; if the root really builds alone now, say so in the README and delete this test");
let readme = read(&root.join("README.md"));
let building = readme.split("## Building").nth(1).expect("README.md has a `## Building` section");
let building = &building[..building.find("\n## ").unwrap_or(building.len())];
let missing: Vec<&String> = siblings.iter().filter(|s| !building.contains(&format!("../{s}"))).collect();
assert!(
missing.is_empty(),
"README.md `## Building` does not name the sibling checkouts {missing:?} that the root members reach by path (all: {siblings:?})"
);
}