use std::path::{Path, PathBuf};
#[test]
fn every_spawned_scv_binary_is_isolated() {
let tests = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests");
let mut sources = Vec::new();
rust_files(&tests, &mut sources);
let mut unisolated = Vec::new();
for path in sources {
if path.ends_with("it/guard.rs") {
continue;
}
let source = std::fs::read_to_string(&path).unwrap();
let mut rest = source.as_str();
while let Some(start) = rest.find("CARGO_BIN_EXE_") {
let spawn = &rest[start..];
let end = ["spawn()", "output()", "status()"]
.iter()
.filter_map(|call| spawn.find(call))
.min()
.unwrap_or(spawn.len());
if !spawn[..end].contains(".isolated(") {
let line = source[..source.len() - spawn.len()].lines().count();
unisolated.push(format!("{}:{line}", path.display()));
}
rest = &spawn["CARGO_BIN_EXE_".len()..];
}
}
assert!(
unisolated.is_empty(),
"spawn SCV binaries with `support::Isolated::isolated(home)`: {unisolated:?}"
);
}
#[test]
fn unit_tests_live_in_tests_rs_files() {
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
let mut sources = Vec::new();
rust_files(&root.join("src"), &mut sources);
for entry in std::fs::read_dir(root.join("crates")).unwrap() {
rust_files(&entry.unwrap().path().join("src"), &mut sources);
}
let mut misplaced = Vec::new();
let mut unused = Vec::new();
for path in &sources {
let relative = relative(root, path);
let source = std::fs::read_to_string(path).unwrap();
if in_test_module(&relative) {
if !included(path) {
unused.push(relative);
}
} else if holds_tests(&source) {
misplaced.push(relative);
}
}
assert!(
misplaced.is_empty(),
"move these unit tests into the module's `tests.rs` (`#[cfg(test)] mod tests;`, see \
docs/quality.md): {misplaced:?}"
);
assert!(
unused.is_empty(),
"these test files are not declared by their parent module, so they never run: {unused:?}"
);
}
fn rust_files(directory: &Path, found: &mut Vec<PathBuf>) {
let Ok(entries) = std::fs::read_dir(directory) else {
return;
};
for entry in entries {
let path = entry.unwrap().path();
if path.is_dir() {
rust_files(&path, found);
} else if path.extension().is_some_and(|extension| extension == "rs") {
found.push(path);
}
}
}
fn relative(root: &Path, path: &Path) -> String {
path.strip_prefix(root)
.unwrap()
.components()
.map(|component| component.as_os_str().to_string_lossy())
.collect::<Vec<_>>()
.join("/")
}
fn in_test_module(relative: &str) -> bool {
let parts: Vec<&str> = relative.split('/').collect();
let inside_src = parts
.iter()
.position(|part| *part == "src")
.map_or(&parts[..0], |index| &parts[index + 1..parts.len() - 1]);
parts.last() == Some(&"tests.rs") || inside_src.contains(&"tests")
}
fn holds_tests(source: &str) -> bool {
source.lines().any(|line| {
let line = line.trim_start();
line.starts_with("#[test]") || line.starts_with("#[tokio::test")
})
}
fn included(path: &Path) -> bool {
let directory = path.parent().unwrap();
let stem = path.file_stem().unwrap().to_string_lossy();
let parents: Vec<PathBuf> = if stem == "tests" {
let mut parents = vec![directory.join("mod.rs")];
if directory.file_name().is_some_and(|name| name == "src") {
parents.extend([directory.join("lib.rs"), directory.join("main.rs")]);
} else {
parents.push(directory.with_extension("rs"));
}
parents
} else {
vec![directory.with_extension("rs"), directory.join("mod.rs")]
};
parents.iter().any(|parent| {
std::fs::read_to_string(parent).is_ok_and(|source| {
source
.lines()
.any(|line| line.trim() == format!("mod {stem};"))
})
})
}