use std::path::Path;
const TEST_DIRS: &[&str] = &["test", "tests", "__tests__", "spec", "specs"];
const SEGMENT_MARKERS: &[&str] = &["test", "tests", "spec", "specs"];
const CAMEL_MARKERS: &[&str] = &["Test", "Tests", "Spec", "Specs"];
fn has_test_directory(path: &Path) -> bool {
path.components().any(|c| {
let segment = c.as_os_str().to_string_lossy().to_lowercase();
TEST_DIRS.contains(&segment.as_str())
})
}
fn has_marker_segment(stem: &str) -> bool {
stem.split(['_', '-', '.'])
.any(|segment| SEGMENT_MARKERS.contains(&segment.to_lowercase().as_str()))
}
fn has_camel_marker(stem: &str) -> bool {
for marker in CAMEL_MARKERS {
if stem.strip_suffix(marker).is_some() {
return true;
}
if let Some(rest) = stem.strip_prefix(marker) {
if rest.starts_with(char::is_uppercase) {
return true;
}
}
}
false
}
pub fn is_test_path(path: &Path) -> bool {
if has_test_directory(path) {
return true;
}
let Some(stem) = path.file_stem().map(|s| s.to_string_lossy().into_owned()) else {
return false;
};
has_marker_segment(&stem) || has_camel_marker(&stem)
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
fn is_test(p: &str) -> bool {
is_test_path(Path::new(p))
}
#[test]
fn every_supported_naming_convention_is_recognised() {
for path in [
"tests/test_auth.py",
"src/auth_test.py",
"web/handler_test.go",
"ui/widget.test.ts",
"ui/widget.spec.tsx",
"ui/widget-spec.js",
"src/FooTest.java",
"src/FooTest.kt",
"src/AuthSpec.scala",
"src/TestHelpers.kt",
"src/XMLTest.java",
"src/HTTPTest.go",
"src/DBTest.kt",
"src/UITest.swift",
"src/IOTest.scala",
"src/MyXMLTest.java",
"src/JSONSpec.scala",
"crates/x/tests/integration.rs",
"src/tests.rs",
"app/__tests__/widget.jsx",
"spec/models/user_spec.rb",
] {
assert!(is_test(path), "not recognised as a test: {path}");
}
}
#[test]
fn an_ordinary_word_ending_in_test_is_not_a_test_file() {
for path in [
"src/latest.rs",
"src/latest.java",
"src/latest.kt",
"src/greatest.scala",
"src/contest.py",
"src/attest.go",
"src/testing.rs",
"src/tester.py",
"src/manifest.json",
] {
assert!(!is_test(path), "wrongly classified as a test: {path}");
}
}
#[test]
fn conftest_is_not_itself_a_test_file() {
assert!(!is_test("tests_helpers/conftest.py"));
assert!(!is_test("src/conftest.py"));
}
#[test]
fn a_test_directory_marks_everything_beneath_it() {
assert!(is_test("tests/fixtures/data_loader.py"));
assert!(is_test("crates/x/tests/common/mod.rs"));
assert!(!is_test("src/testdata/loader.py"));
}
}