1use crate::rules::RuleId;
2
3#[derive(Debug, Clone, Default)]
4pub struct TestAnalysis {
5 pub assertion_count: usize,
6 pub mock_count: usize,
7 pub mock_classes: Vec<String>,
8 pub line_count: usize,
9 pub how_not_what_count: usize,
10 pub fixture_count: usize,
11 pub has_wait: bool,
12 pub has_skip_call: bool,
13 pub assertion_message_count: usize,
14 pub duplicate_literal_count: usize,
15 pub suppressed_rules: Vec<RuleId>,
16}
17
18#[derive(Debug, Clone)]
19pub struct TestFunction {
20 pub name: String,
21 pub file: String,
22 pub line: usize,
23 pub end_line: usize,
24 pub analysis: TestAnalysis,
25}
26
27#[derive(Debug, Clone)]
34pub struct FileAnalysis {
35 pub file: String,
36 pub functions: Vec<TestFunction>,
37 pub has_pbt_import: bool,
38 pub has_contract_import: bool,
39 pub has_error_test: bool,
40 pub has_relational_assertion: bool,
41 pub parameterized_count: usize,
42}
43
44pub trait LanguageExtractor {
45 fn extract_test_functions(&self, source: &str, file_path: &str) -> Vec<TestFunction>;
46
47 fn extract_file_analysis(&self, source: &str, file_path: &str) -> FileAnalysis {
51 let functions = self.extract_test_functions(source, file_path);
52 FileAnalysis {
53 file: file_path.to_string(),
54 functions,
55 has_pbt_import: false,
56 has_contract_import: false,
57 has_error_test: false,
58 has_relational_assertion: false,
59 parameterized_count: 0,
60 }
61 }
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67
68 #[test]
69 fn test_analysis_default_all_zero_or_empty() {
70 let analysis = TestAnalysis::default();
71 assert_eq!(analysis.assertion_count, 0);
72 assert_eq!(analysis.mock_count, 0);
73 assert!(analysis.mock_classes.is_empty());
74 assert_eq!(analysis.line_count, 0);
75 assert_eq!(analysis.how_not_what_count, 0);
76 assert_eq!(analysis.fixture_count, 0);
77 assert!(!analysis.has_wait);
78 assert!(!analysis.has_skip_call);
79 assert_eq!(analysis.assertion_message_count, 0);
80 assert_eq!(analysis.duplicate_literal_count, 0);
81 assert!(analysis.suppressed_rules.is_empty());
82 }
83
84 #[test]
85 fn file_analysis_fields_accessible() {
86 let fa = FileAnalysis {
87 file: "test.py".to_string(),
88 functions: vec![],
89 has_pbt_import: true,
90 has_contract_import: false,
91 has_error_test: true,
92 has_relational_assertion: false,
93 parameterized_count: 3,
94 };
95 assert_eq!(fa.file, "test.py");
96 assert!(fa.functions.is_empty());
97 assert!(fa.has_pbt_import);
98 assert!(!fa.has_contract_import);
99 assert!(fa.has_error_test);
100 assert!(!fa.has_relational_assertion);
101 assert_eq!(fa.parameterized_count, 3);
102 }
103
104 struct DummyExtractor;
105 impl LanguageExtractor for DummyExtractor {
106 fn extract_test_functions(&self, _source: &str, file_path: &str) -> Vec<TestFunction> {
107 vec![TestFunction {
108 name: "test_dummy".to_string(),
109 file: file_path.to_string(),
110 line: 1,
111 end_line: 3,
112 analysis: TestAnalysis::default(),
113 }]
114 }
115 }
116
117 #[test]
118 fn default_extract_file_analysis_delegates_to_extract_test_functions() {
119 let extractor = DummyExtractor;
120 let fa = extractor.extract_file_analysis("x = 1", "test.py");
121 assert_eq!(fa.functions.len(), 1);
122 assert!(!fa.has_pbt_import);
123 assert!(!fa.has_contract_import);
124 assert!(!fa.has_error_test);
125 assert!(!fa.has_relational_assertion);
126 assert_eq!(fa.parameterized_count, 0);
127 }
128}