Skip to main content

code_analyze_mcp/
test_detection.rs

1//! Test file detection using path heuristics.
2//!
3//! Identifies test files based on directory and filename patterns.
4//! Supports Rust, Python, Go, Java, TypeScript, and JavaScript.
5//! Note: Fortran has no test-file naming conventions and is not covered by these heuristics.
6
7use std::path::Path;
8
9/// Detect if a file path represents a test file based on path-based heuristics.
10///
11/// Checks for:
12/// - Directory patterns: tests/, test/, `__tests__`/, spec/
13/// - Filename patterns:
14///   - Rust: `test_*.rs`, `*_test.rs`
15///   - Python: `test_*.py`, `*_test.py`
16///   - Go: `*_test.go`
17///   - Java: `Test*.java`, `*Test.java`
18///   - TypeScript/JavaScript: `*.test.ts`, `*.test.js`, `*.spec.ts`, `*.spec.js`
19///
20/// Returns true if the path matches any test heuristic, false otherwise.
21#[must_use]
22pub fn is_test_file(path: &Path) -> bool {
23    // Check directory components for test directories
24    for component in path.components() {
25        if let Some("tests" | "test" | "__tests__" | "spec") = component.as_os_str().to_str() {
26            return true;
27        }
28    }
29
30    // Check filename patterns
31    let Some(file_name) = path.file_name().and_then(|n| n.to_str()) else {
32        return false;
33    };
34
35    // Rust patterns
36    let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
37    if file_name.starts_with("test_") && ext.eq_ignore_ascii_case("rs") {
38        return true;
39    }
40    if file_name.ends_with("_test.rs") {
41        return true;
42    }
43
44    // Python patterns
45    if file_name.starts_with("test_") && ext.eq_ignore_ascii_case("py") {
46        return true;
47    }
48    if file_name.ends_with("_test.py") {
49        return true;
50    }
51
52    // Go patterns
53    if file_name.ends_with("_test.go") {
54        return true;
55    }
56
57    // Java patterns
58    if file_name.starts_with("Test") && ext.eq_ignore_ascii_case("java") {
59        return true;
60    }
61    if file_name.ends_with("Test.java") {
62        return true;
63    }
64
65    // TypeScript/JavaScript patterns
66    if file_name.ends_with(".test.ts") || file_name.ends_with(".test.js") {
67        return true;
68    }
69    if file_name.ends_with(".spec.ts") || file_name.ends_with(".spec.js") {
70        return true;
71    }
72
73    false
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn filename_pattern_detects_test_file() {
82        assert!(is_test_file(Path::new("test_utils.rs")));
83        assert!(is_test_file(Path::new("utils_test.rs")));
84    }
85
86    #[test]
87    fn filename_pattern_rejects_production_file() {
88        assert!(!is_test_file(Path::new("utils.rs")));
89        assert!(!is_test_file(Path::new("main.rs")));
90    }
91
92    #[test]
93    fn directory_pattern_detects_test_file() {
94        assert!(is_test_file(Path::new("tests/utils.rs")));
95    }
96
97    #[test]
98    fn directory_pattern_detects_nested_test_file() {
99        assert!(is_test_file(Path::new("src/tests/utils.rs")));
100        assert!(!is_test_file(Path::new("src/utils.rs")));
101    }
102}