use std::fs;
use std::path::{Path, PathBuf};
const HELPERS: [&str; 2] = ["src/test_git_env.rs", "src/test_support.rs"];
const BARE_GIT: &str = concat!("Command::new(", "\"git\")");
fn rust_files(dir: &Path, found: &mut Vec<PathBuf>) {
for entry in fs::read_dir(dir).expect("list sources") {
let path = entry.expect("read a source entry").path();
if path.is_dir() {
rust_files(&path, found);
} else if path.extension().is_some_and(|extension| extension == "rs") {
found.push(path);
}
}
}
fn is_test_code(relative: &Path) -> bool {
relative.starts_with("tests")
|| relative
.components()
.any(|part| part.as_os_str() == "tests")
|| relative.file_stem().is_some_and(|stem| {
let stem = stem.to_string_lossy();
stem == "tests" || stem.ends_with("_tests") || stem.starts_with("test_")
})
}
fn test_lines<'a>(relative: &Path, text: &'a str) -> impl Iterator<Item = (usize, &'a str)> {
let whole = is_test_code(relative);
let mut in_tests = whole;
text.lines().enumerate().filter(move |(_, line)| {
in_tests = in_tests || line.contains("#[cfg(test)]");
in_tests
})
}
#[test]
fn tests_spawn_git_only_through_the_isolating_helpers() {
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
let mut files = Vec::new();
rust_files(&root.join("src"), &mut files);
rust_files(&root.join("tests"), &mut files);
let mut offenders = Vec::new();
for path in files {
let relative = path.strip_prefix(root).expect("path under the crate");
if HELPERS.iter().any(|helper| relative == Path::new(helper)) {
continue;
}
let text = fs::read_to_string(&path).expect("read a source");
for (number, line) in test_lines(relative, &text) {
let code = line.split("//").next().unwrap_or_default();
if code.contains(BARE_GIT) {
offenders.push(format!("{}:{}", relative.display(), number + 1));
}
}
}
assert!(
offenders.is_empty(),
"spawn git through test_support::git or common::without_outer_git instead: {offenders:?}"
);
}