Skip to main content

kiss/test_refs/
mod.rs

1mod collect;
2mod collect_parallel;
3mod coverage;
4mod coverage_weighted;
5mod scope;
6pub(crate) mod detection;
7pub(crate) mod disambiguation;
8
9use crate::graph::DependencyGraph;
10#[cfg(test)]
11use crate::graph::build_dependency_graph;
12use crate::parsing::ParsedFile;
13use crate::units::CodeUnitKind;
14use std::collections::{HashMap, HashSet};
15use std::path::PathBuf;
16
17pub use collect_parallel::test_functions_in;
18pub(crate) use collect_parallel::collect_refs_parallel;
19pub(crate) use coverage::{build_py_coverage_map, is_definition_covered};
20#[cfg(test)]
21pub(crate) use coverage::CoverageContext;
22pub use coverage_weighted::compute_py_weighted_file_pcts;
23pub use coverage_weighted::py_init_marker_pct;
24pub use detection::{has_test_framework_import, is_in_test_directory, is_test_file};
25pub use disambiguation::build_name_file_map;
26pub(crate) use disambiguation::{build_disambiguation_map, file_to_module_suffix};
27
28#[cfg(test)]
29pub(crate) use collect::collect_definitions;
30
31#[derive(Debug, Clone)]
32pub struct CodeDefinition {
33    pub name: String,
34    pub kind: CodeUnitKind,
35    pub file: PathBuf,
36    pub line: usize,
37    pub containing_class: Option<String>,
38}
39
40/// (`test_file_path`, `test_function_name`) — e.g. (`"tests/test_utils.py"`, `"test_parse_empty"`)
41pub type CoveringTest = (PathBuf, String);
42
43pub(crate) type PerTestUsage = Vec<(PathBuf, Vec<(String, HashSet<String>, HashSet<String>)>)>;
44
45#[derive(Debug, Clone)]
46pub struct TestRefAnalysis {
47    pub definitions: Vec<CodeDefinition>,
48    pub test_references: HashSet<String>,
49    /// Names that appear as call targets in test code (not import/bind-only).
50    pub call_references: HashSet<String>,
51    pub unreferenced: Vec<CodeDefinition>,
52    /// For each covered definition (file, name), the list of tests that reference it.
53    pub coverage_map: HashMap<(PathBuf, String), Vec<CoveringTest>>,
54}
55
56#[allow(clippy::too_many_lines)]
57pub fn analyze_test_refs(
58    parsed_files: &[&ParsedFile],
59    graph: Option<&DependencyGraph>,
60) -> TestRefAnalysis {
61    analyze_test_refs_inner(parsed_files, graph, true)
62}
63
64pub fn analyze_test_refs_quick(parsed_files: &[&ParsedFile]) -> TestRefAnalysis {
65    analyze_test_refs_inner(parsed_files, None, false)
66}
67
68pub fn analyze_test_refs_no_map(
69    parsed_files: &[&ParsedFile],
70    graph: Option<&DependencyGraph>,
71) -> TestRefAnalysis {
72    analyze_test_refs_inner(parsed_files, graph, false)
73}
74
75fn analyze_test_refs_inner(
76    parsed_files: &[&ParsedFile],
77    graph: Option<&DependencyGraph>,
78    need_coverage_map: bool,
79) -> TestRefAnalysis {
80    let (
81        definitions,
82        test_references,
83        usage_references,
84        call_references,
85        import_bindings,
86        alias_bindings,
87        per_test_usage,
88    ) = collect_refs_parallel(parsed_files, need_coverage_map);
89
90    let name_files = build_name_file_map(
91        definitions
92            .iter()
93            .map(|d| (d.name.as_str(), d.file.as_path())),
94    );
95    let disambiguation =
96        build_disambiguation_map(&name_files, &test_references, &per_test_usage, graph);
97    let module_suffixes: HashMap<PathBuf, String> = definitions
98        .iter()
99        .map(|d| (d.file.clone(), file_to_module_suffix(&d.file)))
100        .collect();
101
102    let unreferenced: Vec<CodeDefinition> = definitions
103        .iter()
104        .filter(|def| {
105            let ctx = coverage::CoverageContext {
106                name_files: &name_files,
107                disambiguation: &disambiguation,
108                import_bindings: &import_bindings,
109                module_suffixes: &module_suffixes,
110                usage_refs: &usage_references,
111                call_refs: &call_references,
112                alias_bindings: &alias_bindings,
113            };
114            !is_definition_covered(def, &ctx)
115        })
116        .cloned()
117        .collect();
118
119    let coverage_map = if need_coverage_map {
120        build_py_coverage_map(
121            &definitions,
122            &per_test_usage,
123            &name_files,
124            &disambiguation,
125            &import_bindings,
126            &module_suffixes,
127            &alias_bindings,
128        )
129    } else {
130        HashMap::new()
131    };
132
133    TestRefAnalysis {
134        definitions,
135        test_references,
136        call_references,
137        unreferenced,
138        coverage_map,
139    }
140}
141
142#[cfg(test)]
143mod tests;
144#[cfg(test)]
145mod tests_2;
146#[cfg(test)]
147mod tests_3;
148#[cfg(test)]
149mod tests_4;
150#[cfg(test)]
151mod tests_5;
152#[cfg(test)]
153mod tests_weighted;