qlty_coverage/parser/
cobertura.rs1use crate::Parser;
2use anyhow::{Context, Result};
3use qlty_types::tests::v1::FileCoverage;
4use serde::Deserialize;
5use std::collections::BTreeMap;
6
7#[derive(Debug, Deserialize)]
8#[serde(rename = "coverage")]
9struct CoberturaSource {
10 packages: Packages,
11}
12
13#[derive(Debug, Deserialize)]
14struct Packages {
15 package: Vec<Package>,
16}
17
18#[derive(Debug, Deserialize)]
19struct Package {
20 classes: Classes,
21}
22
23#[derive(Debug, Deserialize)]
24struct Classes {
25 class: Vec<Class>,
26}
27
28#[derive(Debug, Deserialize, Clone)]
29struct Class {
30 filename: String,
31 lines: Lines,
32}
33
34#[derive(Debug, Deserialize, Clone)]
35struct Lines {
36 #[serde(default)]
37 line: Option<Vec<Line>>,
38}
39
40#[derive(Debug, Deserialize, Clone)]
41struct Line {
42 number: String,
43 hits: String,
44}
45
46#[derive(Debug, Clone, PartialEq, Eq, Default)]
47pub struct Cobertura {}
48
49impl Cobertura {
50 pub fn new() -> Self {
51 Self {}
52 }
53}
54
55impl Parser for Cobertura {
56 fn parse_text(&self, text: &str) -> Result<Vec<FileCoverage>> {
57 let source: CoberturaSource =
58 serde_xml_rs::from_str(text).with_context(|| "Failed to parse XML text")?;
59
60 let mut lines_by_filename: BTreeMap<String, Vec<Line>> = BTreeMap::new();
62 let mut file_coverages = vec![];
63
64 for package in source.packages.package.iter() {
65 for class in package.classes.class.iter() {
66 if let Some(ref lines) = class.lines.line {
67 for line in lines {
68 lines_by_filename
69 .entry(class.filename.clone())
70 .or_insert_with(Vec::new)
71 .push(line.clone());
72 }
73 } else {
74 lines_by_filename
75 .entry(class.filename.clone())
76 .or_insert_with(Vec::new);
77 }
78 }
79 }
80
81 for (filename, lines) in lines_by_filename {
82 let mut line_hits = Vec::new();
83 let mut sorted_lines = lines.clone();
84 sorted_lines.sort_by_key(|line| line.number.parse::<i32>().unwrap_or_default());
85
86 if let Some(last_line) = sorted_lines.last() {
87 line_hits = vec![-1; last_line.number.parse::<usize>().unwrap_or(0)];
88
89 for line in sorted_lines.iter() {
90 let line_number = line.number.parse::<usize>().ok().unwrap_or(0);
91
92 if line_number > 0 {
93 let hits = line.hits.parse::<i64>().ok().unwrap_or(-1);
94 if line_hits[line_number - 1] == -1 {
95 line_hits[line_number - 1] = hits;
96 } else {
97 line_hits[line_number - 1] += hits;
98 }
99 }
100 }
101 }
102
103 let file_coverage = FileCoverage {
104 path: filename,
105 hits: line_hits,
106 ..Default::default()
107 };
108
109 file_coverages.push(file_coverage);
110 }
111
112 Ok(file_coverages)
113 }
114}