use crate::invariant::rules::util::{list_cargo_tomls, rel};
use crate::invariant::{Category, Context, Invariant, Outcome};
use std::fs;
use std::path::Path;
pub struct CrateTested;
const PUBLISHABLE_PARENT: &str = "crates";
impl Invariant for CrateTested {
fn id(&self) -> &'static str {
"arch.crate-tested"
}
fn category(&self) -> Category {
Category::Arch
}
fn intent(&self) -> &'static str {
"Every workspace crate under `crates/` exposes at least one \
`#[test]` (inline mod, tests/, or benches/). Untested crates \
can't be refactored safely."
}
fn adr(&self) -> Option<&'static str> {
Some("ADR-0001")
}
fn evaluate(&self, ctx: &Context) -> Outcome {
let mut missing: Vec<String> = Vec::new();
for cargo in list_cargo_tomls(ctx.root()) {
let parent = match cargo.parent() {
Some(p) => p,
None => continue,
};
if !parent
.components()
.any(|c| c.as_os_str() == PUBLISHABLE_PARENT)
{
continue;
}
let Ok(text) = fs::read_to_string(&cargo) else {
continue;
};
if !text.lines().any(|l| l.trim() == "[package]") {
continue;
}
let has_bin = text.contains("[[bin]]");
let lib_path = parent.join("src/lib.rs");
let has_lib = lib_path.is_file() || text.contains("[lib]");
if has_bin && !has_lib {
continue;
}
if !crate_has_tests(parent) {
missing.push(rel(parent, ctx.root()));
}
}
if missing.is_empty() {
Outcome::pass()
} else {
Outcome::fail_repro(
format!(
"{} crate(s) without any #[test]:\n {}",
missing.len(),
missing.join("\n ")
),
"rg -l '#\\[test\\]' crates/<name>/",
)
}
}
}
fn crate_has_tests(crate_dir: &Path) -> bool {
if dir_has_test_files(&crate_dir.join("tests")) {
return true;
}
if dir_has_test_files(&crate_dir.join("benches")) {
return true;
}
let src = crate_dir.join("src");
if src.is_dir() {
for entry in walkdir::WalkDir::new(&src).into_iter().flatten() {
if !entry.file_type().is_file() {
continue;
}
if entry.path().extension().and_then(|s| s.to_str()) != Some("rs") {
continue;
}
let Ok(text) = fs::read_to_string(entry.path()) else {
continue;
};
if text.contains("#[test]") {
return true;
}
}
}
false
}
fn dir_has_test_files(dir: &Path) -> bool {
let Ok(read) = fs::read_dir(dir) else {
return false;
};
read.flatten().any(|e| {
e.path()
.extension()
.and_then(|s| s.to_str())
.map(|s| s == "rs")
.unwrap_or(false)
})
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
fn write_crate(root: &std::path::Path, name: &str, body: &str, lib: &str) {
let dir = root.join("crates").join(name);
fs::create_dir_all(dir.join("src")).unwrap();
fs::write(
dir.join("Cargo.toml"),
format!("[package]\nname = \"{name}\"\nversion = \"0.1\"\n{body}"),
)
.unwrap();
fs::write(dir.join("src/lib.rs"), lib).unwrap();
}
#[test]
fn each_crate_self_testable_passes_when_all_have_tests() {
let tmp = TempDir::new().unwrap();
write_crate(
tmp.path(),
"alpha",
"",
"pub fn k() -> u32 { 1 }\n#[cfg(test)] mod t { #[test] fn t() {} }\n",
);
let ctx = Context::new(tmp.path().to_path_buf());
assert!(matches!(CrateTested.evaluate(&ctx), Outcome::Pass { .. }));
}
#[test]
fn each_crate_self_testable_fails_when_one_crate_has_no_tests() {
let tmp = TempDir::new().unwrap();
write_crate(
tmp.path(),
"alpha",
"",
"pub fn k() {}\n#[cfg(test)] mod t { #[test] fn t() {} }\n",
);
write_crate(tmp.path(), "beta", "", "pub fn no_tests_here() {}\n");
let ctx = Context::new(tmp.path().to_path_buf());
let out = CrateTested.evaluate(&ctx);
assert!(matches!(out, Outcome::Fail { .. }), "{out:?}");
}
#[test]
fn binary_crate_is_exempt() {
let tmp = TempDir::new().unwrap();
let dir = tmp.path().join("crates/shell");
fs::create_dir_all(dir.join("src")).unwrap();
fs::write(
dir.join("Cargo.toml"),
"[package]\nname = \"shell\"\nversion = \"0.1\"\n\n\
[[bin]]\nname = \"shell\"\npath = \"src/main.rs\"\n",
)
.unwrap();
fs::write(dir.join("src/main.rs"), "fn main() {}\n").unwrap();
let ctx = Context::new(tmp.path().to_path_buf());
assert!(matches!(CrateTested.evaluate(&ctx), Outcome::Pass { .. }));
}
#[test]
fn binary_lib_hybrid_must_test_lib() {
let tmp = TempDir::new().unwrap();
write_crate(
tmp.path(),
"hybrid",
"\n[[bin]]\nname = \"hybrid\"\npath = \"src/main.rs\"\n",
"pub fn library_logic() -> u32 { 42 }\n",
);
let dir = tmp.path().join("crates/hybrid/src");
fs::write(dir.join("main.rs"), "fn main() {}\n").unwrap();
let ctx = Context::new(tmp.path().to_path_buf());
let out = CrateTested.evaluate(&ctx);
assert!(
matches!(out, Outcome::Fail { .. }),
"binary+lib hybrid without #[test] should Fail: {out:?}"
);
}
#[test]
fn binary_lib_hybrid_with_tests_passes() {
let tmp = TempDir::new().unwrap();
write_crate(
tmp.path(),
"hybrid",
"\n[[bin]]\nname = \"hybrid\"\npath = \"src/main.rs\"\n",
"pub fn k() -> u32 { 1 }\n#[cfg(test)] mod t { #[test] fn t() {} }\n",
);
let dir = tmp.path().join("crates/hybrid/src");
fs::write(dir.join("main.rs"), "fn main() {}\n").unwrap();
let ctx = Context::new(tmp.path().to_path_buf());
assert!(matches!(CrateTested.evaluate(&ctx), Outcome::Pass { .. }));
}
}