qlty_coverage/parser/
simplecov.rs1use crate::Parser;
2use anyhow::{Context, Result};
3use qlty_types::tests::v1::FileCoverage;
4use semver::Version;
5use serde_json::Value;
6
7#[derive(Debug, Clone, PartialEq, Eq, Default)]
8pub struct Simplecov {}
9
10impl Simplecov {
11 pub fn new() -> Self {
12 Self {}
13 }
14}
15
16impl Parser for Simplecov {
17 fn parse_text(&self, text: &str) -> Result<Vec<FileCoverage>> {
18 let json_value: Value =
19 serde_json::from_str(text).with_context(|| "Failed to parse JSON text")?;
20
21 let mut file_coverages = vec![];
22
23 let coverage_data = if self.is_version_018_or_newer(&json_value) {
24 json_value.get("coverage").and_then(|c| c.as_object())
25 } else {
26 json_value
27 .as_object()
28 .and_then(|obj| obj.values().next())
29 .and_then(|group| group.get("coverage").and_then(|c| c.as_object()))
30 };
31
32 if let Some(coverage) = coverage_data {
33 for (file_path, data) in coverage {
34 let line_hits = self.parse_line_coverage(data);
35
36 let file_coverage = FileCoverage {
37 path: file_path.to_string(),
38 hits: line_hits,
39 ..Default::default()
40 };
41
42 file_coverages.push(file_coverage);
43 }
44 }
45
46 Ok(file_coverages)
47 }
48}
49
50impl Simplecov {
51 fn parse_line_coverage(&self, data: &Value) -> Vec<i64> {
52 match data {
53 Value::Object(obj) => {
54 obj.get("lines")
56 .and_then(|v| v.as_array())
57 .map_or(vec![], |arr| {
58 arr.iter().map(|x| self.parse_lines(x)).collect()
59 })
60 }
61 Value::Array(arr) => {
62 arr.iter().map(|x| self.parse_lines(x)).collect()
64 }
65 _ => vec![],
66 }
67 }
68
69 fn parse_lines(&self, value: &Value) -> i64 {
70 match value {
71 Value::Number(n) => n.as_i64().unwrap_or(-1),
72 Value::String(s) if s == "ignored" => -2,
73 Value::Null => -1,
74 _ => -1,
75 }
76 }
77
78 fn is_version_018_or_newer(&self, json_value: &serde_json::Value) -> bool {
79 if let Some(meta) = json_value.get("meta") {
80 if let Some(version_str) = meta.get("simplecov_version").and_then(|v| v.as_str()) {
81 if let Ok(version) = Version::parse(version_str) {
82 return version >= Version::parse("0.18.0").expect("Parsing version failed");
83 }
84 }
85 }
86 false
87 }
88}
89
90#[cfg(test)]
91mod test {
92 use super::*;
93
94 #[test]
95 fn simplecov_report() {
96 let input = r#"
97 {
98 "meta": {
99 "simplecov_version": "0.21.2"
100 },
101 "coverage": {
102 "sample.rb": {
103 "lines": [null, 1, 1, 1, 1, null, null, 1, 1, null, null, 1, 1, 0, null, 1, null, null, null, "ignored", "ignored", "ignored", "ignored", "ignored", null]
104 }
105 },
106 "groups": {}
107 }
108 "#;
109 let results = Simplecov::new().parse_text(input).unwrap();
110 insta::assert_yaml_snapshot!(results, @r#"
111 - path: sample.rb
112 hits:
113 - "-1"
114 - "1"
115 - "1"
116 - "1"
117 - "1"
118 - "-1"
119 - "-1"
120 - "1"
121 - "1"
122 - "-1"
123 - "-1"
124 - "1"
125 - "1"
126 - "0"
127 - "-1"
128 - "1"
129 - "-1"
130 - "-1"
131 - "-1"
132 - "-2"
133 - "-2"
134 - "-2"
135 - "-2"
136 - "-2"
137 - "-1"
138 "#);
139 }
140
141 #[test]
142 fn simplecov_legacy_report() {
143 let input = r#"
144 {
145 "Unit Tests": {
146 "coverage": {
147 "development/mygem/lib/mygem/errors.rb": [1, null, 1, 1, 0, null, null, null, 1, null, null, null, 1, null, null, null, 1, null, null, null, null]
148 },
149 "timestamp": 1488827968
150 }
151 }
152 "#;
153 let results = Simplecov::new().parse_text(input).unwrap();
154 insta::assert_yaml_snapshot!(results, @r#"
155 - path: development/mygem/lib/mygem/errors.rb
156 hits:
157 - "1"
158 - "-1"
159 - "1"
160 - "1"
161 - "0"
162 - "-1"
163 - "-1"
164 - "-1"
165 - "1"
166 - "-1"
167 - "-1"
168 - "-1"
169 - "1"
170 - "-1"
171 - "-1"
172 - "-1"
173 - "1"
174 - "-1"
175 - "-1"
176 - "-1"
177 - "-1"
178 "#);
179 }
180}