Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cobertura conciseness and summary option #1180

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ Options:
- *files* to only return a list of files.
- *markdown* for human easy read.
- *cobertura* for output in cobertura format.
- *cobertura-pretty* to pretty-print in cobertura format.


[default: lcov]
Expand Down Expand Up @@ -405,6 +406,7 @@ grcov provides the following output types:
| covdir | Provides coverage in a recursive JSON format. |
| html | Output a HTML coverage report, including coverage badges for your README. |
| cobertura | Cobertura XML. Used for coverage analysis in some IDEs and Gitlab CI. |
| cobertura-pretty | Pretty-printed Cobertura XML. |

### Hosting HTML reports and using coverage badges

Expand Down
61 changes: 37 additions & 24 deletions src/cobertura.rs
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ pub fn output_cobertura(
results: &[ResultTuple],
output_file: Option<&Path>,
demangle: bool,
pretty: bool,
) {
let demangle_options = DemangleOptions::name_only();
let sources = vec![source_dir
Expand All @@ -341,7 +342,11 @@ pub fn output_cobertura(
.to_string()];
let coverage = get_coverage(results, sources, demangle, demangle_options);

let mut writer = Writer::new_with_indent(Cursor::new(vec![]), b' ', 4);
let mut writer = if pretty {
Writer::new_with_indent(Cursor::new(vec![]), b' ', 4)
} else {
Writer::new(Cursor::new(vec![]))
};
writer
.write_event(Event::Decl(BytesDecl::new("1.0", None, None)))
.unwrap();
Expand Down Expand Up @@ -515,7 +520,7 @@ fn write_lines(writer: &mut Writer<Cursor<Vec<u8>>>, lines: &[Line]) {
} => {
l.push_attribute(("number", number.to_string().as_ref()));
l.push_attribute(("hits", hits.to_string().as_ref()));
writer.write_event(Event::Start(l)).unwrap();
writer.write_event(Event::Empty(l)).unwrap();
}
Line::Branch {
ref number,
Expand Down Expand Up @@ -546,11 +551,11 @@ fn write_lines(writer: &mut Writer<Cursor<Vec<u8>>>, lines: &[Line]) {
writer
.write_event(Event::End(BytesEnd::new(conditions_tag)))
.unwrap();
writer
.write_event(Event::End(BytesEnd::new(line_tag)))
.unwrap();
}
}
writer
.write_event(Event::End(BytesEnd::new(line_tag)))
.unwrap();
}
writer
.write_event(Event::End(BytesEnd::new(lines_tag)))
Expand Down Expand Up @@ -715,26 +720,28 @@ mod tests {
coverage_result(Result::Main),
)];

output_cobertura(None, &results, Some(&file_path), true);
for pretty in [false, true] {
output_cobertura(None, &results, Some(&file_path), true, pretty);

let results = read_file(&file_path);
let results = read_file(&file_path);

assert!(results.contains(r#"<source>.</source>"#));
assert!(results.contains(r#"<source>.</source>"#));

assert!(results.contains(r#"package name="src/main.rs""#));
assert!(results.contains(r#"class name="main" filename="src/main.rs""#));
assert!(results.contains(r#"method name="cov_test::main""#));
assert!(results.contains(r#"line number="1" hits="1">"#));
assert!(results.contains(r#"line number="3" hits="2" branch="true""#));
assert!(results.contains(r#"<condition number="0" type="jump" coverage="1"/>"#));
assert!(results.contains(r#"package name="src/main.rs""#));
assert!(results.contains(r#"class name="main" filename="src/main.rs""#));
assert!(results.contains(r#"method name="cov_test::main""#));
assert!(results.contains(r#"line number="1" hits="1"/>"#));
assert!(results.contains(r#"line number="3" hits="2" branch="true""#));
assert!(results.contains(r#"<condition number="0" type="jump" coverage="1"/>"#));

assert!(results.contains(r#"lines-covered="6""#));
assert!(results.contains(r#"lines-valid="8""#));
assert!(results.contains(r#"line-rate="0.75""#));
assert!(results.contains(r#"lines-covered="6""#));
assert!(results.contains(r#"lines-valid="8""#));
assert!(results.contains(r#"line-rate="0.75""#));

assert!(results.contains(r#"branches-covered="1""#));
assert!(results.contains(r#"branches-valid="4""#));
assert!(results.contains(r#"branch-rate="0.25""#));
assert!(results.contains(r#"branches-covered="1""#));
assert!(results.contains(r#"branches-valid="4""#));
assert!(results.contains(r#"branch-rate="0.25""#));
}
}

#[test]
Expand All @@ -749,7 +756,7 @@ mod tests {
coverage_result(Result::Test),
)];

output_cobertura(None, &results, Some(file_path.as_ref()), true);
output_cobertura(None, &results, Some(file_path.as_ref()), true, true);

let results = read_file(&file_path);

Expand Down Expand Up @@ -788,7 +795,7 @@ mod tests {
),
];

output_cobertura(None, &results, Some(file_path.as_ref()), true);
output_cobertura(None, &results, Some(file_path.as_ref()), true, true);

let results = read_file(&file_path);

Expand Down Expand Up @@ -820,7 +827,7 @@ mod tests {
CovResult::default(),
)];

output_cobertura(None, &results, Some(&file_path), true);
output_cobertura(None, &results, Some(&file_path), true, true);

let results = read_file(&file_path);

Expand All @@ -840,7 +847,13 @@ mod tests {
CovResult::default(),
)];

output_cobertura(Some(Path::new("src")), &results, Some(&file_path), true);
output_cobertura(
Some(Path::new("src")),
&results,
Some(&file_path),
true,
true,
);

let results = read_file(&file_path);

Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ pub mod html;
mod file_filter;
pub use crate::file_filter::*;

mod summary;
pub use crate::summary::*;

use log::{error, warn};
use std::fs;
use std::io::{BufReader, Cursor};
Expand Down
22 changes: 21 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ enum OutputType {
Covdir,
Html,
Cobertura,
CoberturaPretty,
Markdown,
}

Expand All @@ -45,6 +46,7 @@ impl FromStr for OutputType {
"covdir" => Self::Covdir,
"html" => Self::Html,
"cobertura" => Self::Cobertura,
"cobertura-pretty" => Self::CoberturaPretty,
"markdown" => Self::Markdown,
_ => return Err(format!("{} is not a supported output type", s)),
})
Expand All @@ -63,7 +65,9 @@ impl OutputType {
OutputType::Files => path.join("files"),
OutputType::Covdir => path.join("covdir"),
OutputType::Html => path.join("html"),
OutputType::Cobertura => path.join("cobertura.xml"),
OutputType::Cobertura | OutputType::CoberturaPretty => {
path.join("cobertura.xml")
}
OutputType::Markdown => path.join("markdown.md"),
}
} else {
Expand Down Expand Up @@ -166,6 +170,7 @@ struct Opt {
- *files* to only return a list of files.\n\
- *markdown* for human easy read.\n\
- *cobertura* for output in cobertura format.\n\
- *cobertura-pretty* to pretty-print in cobertura format.\n\
",
value_name = "OUTPUT TYPE",
requires_ifs = [
Expand Down Expand Up @@ -292,6 +297,9 @@ struct Opt {
/// No symbol demangling.
#[arg(long)]
no_demangle: bool,
/// Print a summary of the results
#[arg(long)]
print_summary: bool,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if we have "summary" as one of the possible output types instead of adding a --print-summary option?

}

fn main() {
Expand Down Expand Up @@ -563,10 +571,22 @@ fn main() {
results,
output_path.as_deref(),
demangle,
false,
),
OutputType::CoberturaPretty => output_cobertura(
source_root.as_deref(),
results,
output_path.as_deref(),
demangle,
true,
),
OutputType::Markdown => output_markdown(results, output_path.as_deref(), opt.precision),
};
}

if opt.print_summary {
print_summary(&iterator);
}
}

#[cfg(test)]
Expand Down
138 changes: 138 additions & 0 deletions src/summary.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
use std::path::PathBuf;

use crate::CovResult;

#[derive(Debug, Default)]
struct CoverageStats {
lines_covered: i64,
lines_valid: i64,
branches_covered: i64,
branches_valid: i64,
}

fn get_coverage_stats(results: &[(PathBuf, PathBuf, CovResult)]) -> CoverageStats {
results
.iter()
.fold(CoverageStats::default(), |stats, (_, _, result)| {
let (lines_covered, lines_valid) = result.lines.values().fold(
(stats.lines_covered, stats.lines_valid),
|(covered, valid), l| {
if *l == 0 {
(covered, valid + 1)
} else {
(covered + 1, valid + 1)
}
},
);
let (branches_covered, branches_valid) = result.branches.values().fold(
(stats.branches_covered, stats.branches_valid),
|(covered, valid), branches| {
branches
.iter()
.fold((covered, valid), |(covered, valid), b| {
if *b {
(covered + 1, valid + 1)
} else {
(covered, valid + 1)
}
})
},
);
CoverageStats {
lines_covered,
lines_valid,
branches_covered,
branches_valid,
}
})
}

pub fn print_summary(results: &[(PathBuf, PathBuf, CovResult)]) {
let stats = get_coverage_stats(results);
let lines_percentage = if stats.lines_valid == 0 {
0.0
} else {
(stats.lines_covered as f64 / stats.lines_valid as f64) * 100.0
};
let branches_percentage = if stats.branches_valid == 0 {
0.0
} else {
(stats.branches_covered as f64 / stats.branches_valid as f64) * 100.0
};
println!(
"lines: {:.1}% ({} out of {})",
lines_percentage, stats.lines_covered, stats.lines_valid
);
println!(
"branches: {:.1}% ({} out of {})",
branches_percentage, stats.branches_covered, stats.branches_valid
);
}

#[cfg(test)]
mod tests {
use std::{collections::BTreeMap, path::PathBuf};

use rustc_hash::FxHashMap;

use crate::{CovResult, Function};

use super::get_coverage_stats;

#[test]
fn test_summary() {
let results = vec![(
PathBuf::from("src/main.rs"),
PathBuf::from("src/main.rs"),
CovResult {
/* main.rs
fn main() {
let inp = "a";
if "a" == inp {
println!("a");
} else if "b" == inp {
println!("b");
}
println!("what?");
}
*/
lines: [
(1, 1),
(2, 1),
(3, 2),
(4, 1),
(5, 0),
(6, 0),
(8, 1),
(9, 1),
]
.iter()
.cloned()
.collect(),
branches: {
let mut map = BTreeMap::new();
map.insert(3, vec![true, false]);
map.insert(5, vec![false, false]);
map
},
functions: {
let mut map = FxHashMap::default();
map.insert(
"_ZN8cov_test4main17h7eb435a3fb3e6f20E".to_string(),
Function {
start: 1,
executed: true,
},
);
map
},
},
)];

let stats = get_coverage_stats(&results);
assert_eq!(stats.lines_covered, 6);
assert_eq!(stats.lines_valid, 8);
assert_eq!(stats.branches_covered, 1);
assert_eq!(stats.branches_valid, 4);
}
}
Loading