1pub mod assertion_detector;
19pub mod complexity_detector;
20pub mod flaky_detector;
21pub mod rust;
22pub mod timing_classifier;
23
24use crate::core::{DebtItem, DebtType, Priority};
25use std::path::{Path, PathBuf};
26use syn::{File, ItemFn};
27
28#[derive(Debug, Clone, PartialEq)]
29pub enum TestingAntiPattern {
30 TestWithoutAssertions {
31 test_name: String,
32 file: PathBuf,
33 line: usize,
34 has_setup: bool,
35 has_action: bool,
36 suggested_assertions: Vec<String>,
37 },
38 OverlyComplexTest {
39 test_name: String,
40 file: PathBuf,
41 line: usize,
42 complexity_score: u32,
43 complexity_sources: Vec<ComplexitySource>,
44 suggested_simplification: TestSimplification,
45 },
46 FlakyTestPattern {
47 test_name: String,
48 file: PathBuf,
49 line: usize,
50 flakiness_type: FlakinessType,
51 reliability_impact: ReliabilityImpact,
52 stabilization_suggestion: String,
53 },
54}
55
56#[derive(Debug, Clone, PartialEq)]
57pub enum ComplexitySource {
58 ExcessiveMocking,
59 NestedConditionals,
60 MultipleAssertions,
61 LoopInTest,
62 ExcessiveSetup,
63}
64
65#[derive(Debug, Clone, PartialEq)]
66pub enum TestSimplification {
67 ExtractHelper,
68 SplitTest,
69 ParameterizeTest,
70 SimplifySetup,
71 ReduceMocking,
72}
73
74#[derive(Debug, Clone, PartialEq)]
75pub enum FlakinessType {
76 TimingDependency,
77 RandomValues,
78 ExternalDependency,
79 FilesystemDependency,
80 NetworkDependency,
81 ThreadingIssue,
82}
83
84#[derive(Debug, Clone, PartialEq)]
85pub enum ReliabilityImpact {
86 Critical,
87 High,
88 Medium,
89 Low,
90}
91
92#[derive(Debug, Clone, PartialEq)]
93pub enum TestQualityImpact {
94 Critical,
95 High,
96 Medium,
97 Low,
98}
99
100pub trait TestingDetector {
101 fn detect_anti_patterns(&self, file: &File, path: &Path) -> Vec<TestingAntiPattern>;
102 fn detector_name(&self) -> &'static str;
103 fn assess_test_quality_impact(&self, pattern: &TestingAntiPattern) -> TestQualityImpact;
104}
105
106pub fn is_test_function(function: &ItemFn) -> bool {
107 function.attrs.iter().any(|attr| {
108 let path_str = attr
110 .path()
111 .segments
112 .iter()
113 .map(|seg| seg.ident.to_string())
114 .collect::<Vec<_>>()
115 .join("::");
116
117 path_str == "test"
119 || path_str == "tokio::test"
120 || path_str == "async_std::test"
121 || path_str == "bench"
122 || path_str.ends_with("::test")
123 }) || function.sig.ident.to_string().starts_with("test_")
124 || function.sig.ident.to_string().ends_with("_test")
125}
126
127pub fn analyze_testing_patterns(file: &File, path: &Path) -> Vec<DebtItem> {
128 let detectors: Vec<Box<dyn TestingDetector>> = vec![
129 Box::new(assertion_detector::AssertionDetector::new()),
130 Box::new(complexity_detector::TestComplexityDetector::new()),
131 Box::new(flaky_detector::FlakyTestDetector::new()),
132 ];
133
134 let mut testing_items = Vec::new();
135
136 for detector in detectors {
137 let anti_patterns = detector.detect_anti_patterns(file, path);
138
139 for pattern in anti_patterns {
140 let impact = detector.assess_test_quality_impact(&pattern);
141 let debt_item = convert_testing_pattern_to_debt_item(pattern, impact, path);
142 testing_items.push(debt_item);
143 }
144 }
145
146 testing_items
147}
148
149fn convert_testing_pattern_to_debt_item(
150 pattern: TestingAntiPattern,
151 _impact: TestQualityImpact,
152 path: &Path,
153) -> DebtItem {
154 let (priority, message, context, line, debt_type) = match pattern {
155 TestingAntiPattern::TestWithoutAssertions {
156 test_name,
157 suggested_assertions,
158 line,
159 ..
160 } => (
161 Priority::High,
162 format!("Test '{}' has no assertions", test_name),
163 Some(format!(
164 "Add assertions: {}",
165 suggested_assertions.join(", ")
166 )),
167 line,
168 DebtType::TestQuality { issue_type: None },
169 ),
170 TestingAntiPattern::OverlyComplexTest {
171 test_name,
172 complexity_score,
173 suggested_simplification,
174 line,
175 ..
176 } => (
177 Priority::Medium,
178 format!(
179 "Test '{}' is overly complex (score: {})",
180 test_name, complexity_score
181 ),
182 Some(format!("Consider: {:?}", suggested_simplification)),
183 line,
184 DebtType::TestComplexity {
185 cyclomatic: 0,
186 cognitive: 0,
187 },
188 ),
189 TestingAntiPattern::FlakyTestPattern {
190 test_name,
191 flakiness_type,
192 stabilization_suggestion,
193 line,
194 ..
195 } => (
196 Priority::High,
197 format!(
198 "Test '{}' has flaky pattern: {:?}",
199 test_name, flakiness_type
200 ),
201 Some(stabilization_suggestion),
202 line,
203 DebtType::TestQuality { issue_type: None },
204 ),
205 };
206
207 DebtItem {
208 id: format!("testing-{}-{}", path.display(), line),
209 debt_type,
210 priority,
211 file: path.to_path_buf(),
212 line,
213 column: None,
214 message,
215 context,
216 }
217}