Skip to main content

nargo_coverage/
lib.rs

1#![warn(missing_docs)]
2
3use nargo_types::{Result, Span};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7/// 代码覆盖率元数据
8#[derive(Debug, Clone, Serialize, Deserialize, Default)]
9pub struct CoverageMetadata {
10    /// 文件路径
11    pub file_path: String,
12    /// 语句映射
13    pub statement_map: HashMap<usize, Span>,
14    /// 分支映射
15    pub branch_map: HashMap<usize, BranchMetadata>,
16    /// 函数映射
17    pub function_map: HashMap<usize, FunctionMetadata>,
18    /// 语句命中计数
19    pub s: HashMap<usize, u32>,
20    /// 分支命中计数
21    pub b: HashMap<usize, Vec<u32>>,
22    /// 函数命中计数
23    pub f: HashMap<usize, u32>,
24}
25
26/// 分支元数据
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct BranchMetadata {
29    /// 行号
30    pub line: usize,
31    /// 分支类型
32    pub r#type: String,
33    /// 分支位置
34    pub locations: Vec<Span>,
35}
36
37/// 函数元数据
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct FunctionMetadata {
40    /// 函数名称
41    pub name: String,
42    /// 行号
43    pub line: usize,
44    /// 位置范围
45    pub span: Span,
46}
47
48/// 代码覆盖率插桩器
49pub struct CoverageInstrumenter {
50    file_id: String,
51    next_stmt_id: usize,
52    metadata: CoverageMetadata,
53}
54
55impl CoverageInstrumenter {
56    /// 创建一个新的覆盖率插桩器
57    pub fn new(file_id: String, file_path: String) -> Self {
58        Self { file_id, next_stmt_id: 0, metadata: CoverageMetadata { file_path, ..Default::default() } }
59    }
60
61    /// 对模块进行覆盖率插桩
62    pub fn instrument(&mut self, _module: &mut ()) -> Result<CoverageMetadata> {
63        // 简化实现,实际需要根据 nargo_types 的 API 进行调整
64        Ok(self.metadata.clone())
65    }
66
67    fn instrument_program(&mut self, _program: &mut ()) {
68        // 简化实现,实际需要根据 nargo_types 的 API 进行调整
69    }
70
71    fn instrument_stmt(&mut self, _stmt: ()) -> () {
72        // 简化实现,实际需要根据 nargo_types 的 API 进行调整
73    }
74
75    fn instrument_expr(&mut self, _expr: &mut ()) {
76        // 简化实现,实际需要根据 nargo_types 的 API 进行调整
77    }
78
79    fn instrument_template(&mut self, _template: &mut ()) {
80        // 简化实现,实际需要根据 nargo_types 的 API 进行调整
81    }
82
83    fn instrument_template_node(&mut self, _node: &mut ()) {
84        // 简化实现,实际需要根据 nargo_types 的 API 进行调整
85    }
86
87    fn next_id(&mut self) -> usize {
88        let id = self.next_stmt_id;
89        self.next_stmt_id += 1;
90        id
91    }
92
93    fn create_counter_stmt(&self, _id: usize) -> () {
94        // 简化实现,实际需要根据 nargo_types 的 API 进行调整
95    }
96
97    fn create_branch_counter_expr(&self, _bid: usize, _index: usize) -> () {
98        // 简化实现,实际需要根据 nargo_types 的 API 进行调整
99    }
100
101    fn create_function_counter_stmt(&self, _fid: usize) -> () {
102        // 简化实现,实际需要根据 nargo_types 的 API 进行调整
103    }
104}
105
106/// LCOV 格式报告生成器
107pub struct LcovReporter;
108
109impl LcovReporter {
110    /// 生成 LCOV 格式的覆盖率报告
111    pub fn report(metadatas: &[CoverageMetadata]) -> String {
112        let mut out = String::new();
113        for meta in metadatas {
114            out.push_str(&format!("TN:\nSF:{}\n", meta.file_path));
115
116            for (id, func) in &meta.function_map {
117                let hits = meta.f.get(id).cloned().unwrap_or(0);
118                out.push_str(&format!("FN:{},{}\n", func.line, func.name));
119                out.push_str(&format!("FNDA:{},{}\n", hits, func.name));
120            }
121            out.push_str(&format!("FNF:{}\n", meta.function_map.len()));
122            out.push_str(&format!("FNH:{}\n", meta.f.values().filter(|&&h| h > 0).count()));
123
124            let mut line_hits: HashMap<u32, u32> = HashMap::new();
125            for (id, span) in &meta.statement_map {
126                let hits = meta.s.get(id).cloned().unwrap_or(0);
127                let line = span.start.line;
128                let entry = line_hits.entry(line).or_insert(0);
129                *entry = (*entry).max(hits);
130            }
131
132            let mut lines: Vec<_> = line_hits.keys().collect();
133            lines.sort();
134            for &line in lines {
135                let hits = line_hits[&line];
136                out.push_str(&format!("DA:{},{}\n", line, hits));
137            }
138
139            out.push_str(&format!("LF:{}\n", line_hits.len()));
140            out.push_str(&format!("LH:{}\n", line_hits.values().filter(|&&h| h > 0).count()));
141
142            let mut branch_count = 0;
143            let mut branch_hit = 0;
144            for (bid, branch) in &meta.branch_map {
145                let hits = meta.b.get(bid);
146                for (idx, &h) in hits.map(|v| v.as_slice()).unwrap_or(&[]).iter().enumerate() {
147                    out.push_str(&format!("BRDA:{},0,{},{}\n", branch.line, idx, if h > 0 { h.to_string() } else { "-".to_string() }));
148                    branch_count += 1;
149                    if h > 0 {
150                        branch_hit += 1;
151                    }
152                }
153            }
154            out.push_str(&format!("BRF:{}\n", branch_count));
155            out.push_str(&format!("BRH:{}\n", branch_hit));
156
157            out.push_str("end_of_record\n");
158        }
159        out
160    }
161}