Skip to main content

crap4rust/
coverage_index.rs

1// Copyright 2025 Umberto Gotti <umberto.gotti@umbertogotti.dev>
2// Licensed under the MIT License
3// SPDX-License-Identifier: MIT
4
5use std::collections::HashMap;
6
7use crate::coverage_record::CoverageRecord;
8use crate::source_function::SourceFunction;
9
10pub struct CoverageIndex {
11    inner: HashMap<(String, usize), CoverageRecord>,
12}
13
14impl CoverageIndex {
15    pub fn from_records(records: Vec<CoverageRecord>) -> Self {
16        let mut inner: HashMap<(String, usize), CoverageRecord> = HashMap::new();
17        for record in records {
18            let key = (record.path_key.clone(), record.line);
19            match inner.remove(&key) {
20                None => {
21                    inner.insert(key, record);
22                }
23                Some(existing) => {
24                    inner.insert(key, Self::merge_duplicate(existing, record));
25                }
26            }
27        }
28        Self { inner }
29    }
30
31    fn merge_duplicate(existing: CoverageRecord, incoming: CoverageRecord) -> CoverageRecord {
32        if incoming.covered_regions == 0 && existing.covered_regions > 0 {
33            return existing;
34        }
35        if existing.covered_regions == 0 && incoming.covered_regions > 0 {
36            return incoming;
37        }
38        CoverageRecord {
39            path_key: existing.path_key,
40            line: existing.line,
41            covered_regions: existing.covered_regions + incoming.covered_regions,
42            total_regions: existing.total_regions + incoming.total_regions,
43        }
44    }
45
46    pub fn match_function(&self, function: &SourceFunction) -> Option<f64> {
47        Self::match_function_coverage(function, &self.inner).map(|record| record.coverage_ratio())
48    }
49
50    pub fn get(&self, path_key: &str, line: usize) -> Option<&CoverageRecord> {
51        self.inner.get(&(path_key.to_string(), line))
52    }
53
54    pub fn match_function_coverage(
55        function: &SourceFunction,
56        coverage_index: &HashMap<(String, usize), CoverageRecord>,
57    ) -> Option<CoverageRecord> {
58        let matching: Vec<&CoverageRecord> = coverage_index
59            .iter()
60            .filter(|((path_key, line), _)| {
61                path_key == &function.path_key
62                    && *line >= function.line
63                    && *line <= function.end_line
64            })
65            .map(|(_, record)| record)
66            .collect();
67
68        if matching.is_empty() {
69            return None;
70        }
71
72        let covered_regions = matching.iter().map(|r| r.covered_regions).sum();
73        let total_regions = matching.iter().map(|r| r.total_regions).sum();
74
75        Some(CoverageRecord {
76            path_key: function.path_key.clone(),
77            line: function.line,
78            covered_regions,
79            total_regions,
80        })
81    }
82}