sem_core/parser/
test_detect.rs1const TEST_TOKENS: &[&str] = &["test", "tests", "spec", "specs"];
10
11const EXACT_DIR_NAMES: &[&str] = &[
14 "e2e",
15 "cypress",
16 "playwright",
17 "testing",
18 "fixtures",
19 "fixture",
20 "benchmarks",
21 "benchmark",
22 "__tests__",
23 "__mocks__",
24];
25
26const TEST_FILE_PATTERNS: &[&str] = &["_test.", ".test.", "_spec.", ".spec."];
28
29pub fn is_test_path(path: &str) -> bool {
32 is_test_path_with_custom_dirs(path, &[])
33}
34
35pub fn is_test_path_with_custom_dirs(path: &str, custom_dirs: &[String]) -> bool {
38 let path_lower = path.to_lowercase();
39
40 if let Some(file_name) = path_lower.rsplit('/').next() {
42 for pat in TEST_FILE_PATTERNS {
43 if file_name.contains(pat) {
44 return true;
45 }
46 }
47 }
48
49 for component in path_lower.split('/') {
51 if component.is_empty() {
52 continue;
53 }
54
55 if EXACT_DIR_NAMES.contains(&component) {
57 return true;
58 }
59
60 if custom_dirs
62 .iter()
63 .any(|d| d.eq_ignore_ascii_case(component))
64 {
65 return true;
66 }
67
68 let has_test_token = component
70 .split(|c: char| c == '-' || c == '_' || c == '.')
71 .any(|token| TEST_TOKENS.contains(&token));
72 if has_test_token {
73 return true;
74 }
75 }
76
77 false
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83
84 #[test]
87 fn classic_test_dirs() {
88 assert!(is_test_path("src/test/foo.ts"));
89 assert!(is_test_path("src/tests/foo.ts"));
90 assert!(is_test_path("src/spec/foo.ts"));
91 assert!(is_test_path("src/specs/foo.ts"));
92 }
93
94 #[test]
95 fn hyphenated_test_dirs() {
96 assert!(is_test_path("e2e-tests/foo.ts"));
97 assert!(is_test_path("integration-test/bar.py"));
98 assert!(is_test_path("unit-tests/baz.js"));
99 }
100
101 #[test]
102 fn underscored_test_dirs() {
103 assert!(is_test_path("__tests__/baz.js"));
104 assert!(is_test_path("unit_tests/foo.rs"));
105 assert!(is_test_path("integration_test/bar.go"));
106 }
107
108 #[test]
109 fn dotted_test_dirs() {
110 assert!(is_test_path("src/test.unit/foo.ts"));
111 }
112
113 #[test]
114 fn well_known_exact_dirs() {
115 assert!(is_test_path("e2e/login.spec.ts"));
116 assert!(is_test_path("cypress/e2e/login.spec.ts"));
117 assert!(is_test_path("playwright/tests/foo.ts"));
118 assert!(is_test_path("testing/helpers.py"));
119 assert!(is_test_path("fixtures/data.json"));
120 assert!(is_test_path("fixture/sample.txt"));
121 assert!(is_test_path("benchmarks/bench_main.rs"));
122 assert!(is_test_path("benchmark/perf.go"));
123 assert!(is_test_path("__mocks__/api.ts"));
124 }
125
126 #[test]
129 fn test_file_name_patterns() {
130 assert!(is_test_path("src/utils_test.go"));
131 assert!(is_test_path("src/utils.test.ts"));
132 assert!(is_test_path("src/utils_spec.rb"));
133 assert!(is_test_path("src/utils.spec.js"));
134 }
135
136 #[test]
139 fn no_false_positives() {
140 assert!(!is_test_path("src/main.rs"));
141 assert!(!is_test_path("src/contest/solution.py"));
142 assert!(!is_test_path("src/spectacle/viewer.ts"));
143 assert!(!is_test_path("src/attestation/verify.go"));
144 assert!(!is_test_path("src/latest/handler.js"));
145 assert!(!is_test_path("src/protest/rally.rb"));
146 assert!(!is_test_path("lib/fastest/core.ts"));
147 }
148
149 #[test]
152 fn custom_dir_match() {
153 let custom = vec!["qa".to_string(), "smoke".to_string()];
154 assert!(is_test_path_with_custom_dirs("qa/check.ts", &custom));
155 assert!(is_test_path_with_custom_dirs("smoke/login.py", &custom));
156 }
157
158 #[test]
159 fn custom_dir_case_insensitive() {
160 let custom = vec!["QA".to_string()];
161 assert!(is_test_path_with_custom_dirs("qa/check.ts", &custom));
162 assert!(is_test_path_with_custom_dirs("Qa/check.ts", &custom));
163 }
164
165 #[test]
166 fn custom_dir_no_false_positive() {
167 let custom = vec!["qa".to_string()];
168 assert!(!is_test_path_with_custom_dirs("src/main.rs", &custom));
169 }
170
171 #[test]
172 fn builtin_still_works_with_custom_dirs() {
173 let custom = vec!["qa".to_string()];
174 assert!(is_test_path_with_custom_dirs("src/tests/foo.ts", &custom));
175 assert!(is_test_path_with_custom_dirs("e2e-tests/bar.py", &custom));
176 }
177}