0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-03-03 09:31:22 -05:00

feat(coverage): add "--output" flag (#13289)

This commit adds "--output" to "deno coverage" subcommand.

It can be used instead of piping output to a file.

Co-authored-by: Bartek Iwańczuk <biwanczuk@gmail.com>
This commit is contained in:
VishnuJin 2022-02-15 21:03:21 +05:30 committed by GitHub
parent d2ab1ed378
commit 2dc5dba8ba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 108 additions and 20 deletions

View file

@ -62,6 +62,7 @@ pub struct CompletionsFlags {
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct CoverageFlags {
pub files: Vec<PathBuf>,
pub output: Option<PathBuf>,
pub ignore: Vec<PathBuf>,
pub include: Vec<String>,
pub exclude: Vec<String>,
@ -687,7 +688,7 @@ an url to match it must match the include pattern and not match the exclude patt
Write a report using the lcov format:
deno coverage --lcov cov_profile > cov.lcov
deno coverage --lcov --output=cov.lcov cov_profile/
Generate html reports from lcov:
@ -730,6 +731,18 @@ Generate html reports from lcov:
.help("Output coverage report in lcov format")
.takes_value(false),
)
.arg(Arg::new("output")
.requires("lcov")
.long("output")
.help("Output file (defaults to stdout) for lcov")
.long_help("Exports the coverage report in lcov format to the given file.
Filename should be passed along with '='
For example '--output=foo.lcov'
If no --output arg is specified then the report is written to stdout."
)
.takes_value(true)
.require_equals(true)
)
.arg(
Arg::new("files")
.takes_value(true)
@ -1864,8 +1877,10 @@ fn coverage_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
None => vec![],
};
let lcov = matches.is_present("lcov");
let output = matches.value_of("output").map(PathBuf::from);
flags.subcommand = DenoSubcommand::Coverage(CoverageFlags {
files,
output,
ignore,
include,
exclude,
@ -4782,6 +4797,7 @@ mod tests {
Flags {
subcommand: DenoSubcommand::Coverage(CoverageFlags {
files: vec![PathBuf::from("foo.json")],
output: None,
ignore: vec![],
include: vec![r"^file:".to_string()],
exclude: vec![r"test\.(js|mjs|ts|jsx|tsx)$".to_string()],
@ -4792,6 +4808,30 @@ mod tests {
);
}
#[test]
fn coverage_with_lcov_and_out_file() {
let r = flags_from_vec(svec![
"deno",
"coverage",
"--lcov",
"--output=foo.lcov",
"foo.json"
]);
assert_eq!(
r.unwrap(),
Flags {
subcommand: DenoSubcommand::Coverage(CoverageFlags {
files: vec![PathBuf::from("foo.json")],
ignore: vec![],
include: vec![r"^file:".to_string()],
exclude: vec![r"test\.(js|mjs|ts|jsx|tsx)$".to_string()],
lcov: true,
output: Some(PathBuf::from("foo.lcov")),
}),
..Flags::default()
}
);
}
#[test]
fn location_with_bad_scheme() {
#[rustfmt::skip]

View file

@ -21,7 +21,7 @@ use sourcemap::SourceMap;
use std::fs;
use std::fs::File;
use std::io::BufWriter;
use std::io::Write;
use std::io::{self, Error, Write};
use std::path::PathBuf;
use std::sync::Arc;
use text_lines::TextLines;
@ -159,12 +159,14 @@ struct CoverageReport {
named_functions: Vec<FunctionCoverageItem>,
branches: Vec<BranchCoverageItem>,
found_lines: Vec<(usize, i64)>,
output: Option<PathBuf>,
}
fn generate_coverage_report(
script_coverage: &ScriptCoverage,
script_source: &str,
maybe_source_map: &Option<Vec<u8>>,
output: &Option<PathBuf>,
) -> CoverageReport {
let maybe_source_map = maybe_source_map
.as_ref()
@ -191,6 +193,7 @@ fn generate_coverage_report(
),
branches: Vec::new(),
found_lines: Vec::new(),
output: output.clone(),
};
for function in &script_coverage.functions {
@ -359,7 +362,11 @@ fn create_reporter(
}
trait CoverageReporter {
fn report(&mut self, coverage_report: &CoverageReport, file_text: &str);
fn report(
&mut self,
coverage_report: &CoverageReport,
file_text: &str,
) -> Result<(), AnyError>;
fn done(&mut self);
}
@ -373,7 +380,22 @@ impl LcovCoverageReporter {
}
impl CoverageReporter for LcovCoverageReporter {
fn report(&mut self, coverage_report: &CoverageReport, _file_text: &str) {
fn report(
&mut self,
coverage_report: &CoverageReport,
_file_text: &str,
) -> Result<(), AnyError> {
// pipes output to stdout if no file is specified
let out_mode: Result<Box<dyn Write>, Error> = match coverage_report.output {
// only append to the file as the file should be created already
Some(ref path) => File::options()
.append(true)
.open(path)
.map(|f| Box::new(f) as Box<dyn Write>),
None => Ok(Box::new(io::stdout())),
};
let mut out_writer = out_mode?;
let file_path = coverage_report
.url
.to_file_path()
@ -381,24 +403,33 @@ impl CoverageReporter for LcovCoverageReporter {
.map(|p| p.to_str().map(|p| p.to_string()))
.flatten()
.unwrap_or_else(|| coverage_report.url.to_string());
println!("SF:{}", file_path);
writeln!(out_writer, "SF:{}", file_path)?;
for function in &coverage_report.named_functions {
println!("FN:{},{}", function.line_index + 1, function.name);
writeln!(
out_writer,
"FN:{},{}",
function.line_index + 1,
function.name
)?;
}
for function in &coverage_report.named_functions {
println!("FNDA:{},{}", function.execution_count, function.name);
writeln!(
out_writer,
"FNDA:{},{}",
function.execution_count, function.name
)?;
}
let functions_found = coverage_report.named_functions.len();
println!("FNF:{}", functions_found);
writeln!(out_writer, "FNF:{}", functions_found)?;
let functions_hit = coverage_report
.named_functions
.iter()
.filter(|f| f.execution_count > 0)
.count();
println!("FNH:{}", functions_hit);
writeln!(out_writer, "FNH:{}", functions_hit)?;
for branch in &coverage_report.branches {
let taken = if let Some(taken) = &branch.taken {
@ -407,23 +438,23 @@ impl CoverageReporter for LcovCoverageReporter {
"-".to_string()
};
println!(
writeln!(
out_writer,
"BRDA:{},{},{},{}",
branch.line_index + 1,
branch.block_number,
branch.branch_number,
taken
);
)?;
}
let branches_found = coverage_report.branches.len();
println!("BRF:{}", branches_found);
writeln!(out_writer, "BRF:{}", branches_found)?;
let branches_hit =
coverage_report.branches.iter().filter(|b| b.is_hit).count();
println!("BRH:{}", branches_hit);
writeln!(out_writer, "BRH:{}", branches_hit)?;
for (index, count) in &coverage_report.found_lines {
println!("DA:{},{}", index + 1, count);
writeln!(out_writer, "DA:{},{}", index + 1, count)?;
}
let lines_hit = coverage_report
@ -431,12 +462,13 @@ impl CoverageReporter for LcovCoverageReporter {
.iter()
.filter(|(_, count)| *count != 0)
.count();
println!("LH:{}", lines_hit);
writeln!(out_writer, "LH:{}", lines_hit)?;
let lines_found = coverage_report.found_lines.len();
println!("LF:{}", lines_found);
writeln!(out_writer, "LF:{}", lines_found)?;
println!("end_of_record");
writeln!(out_writer, "end_of_record")?;
Ok(())
}
fn done(&mut self) {}
@ -451,7 +483,11 @@ impl PrettyCoverageReporter {
}
impl CoverageReporter for PrettyCoverageReporter {
fn report(&mut self, coverage_report: &CoverageReport, file_text: &str) {
fn report(
&mut self,
coverage_report: &CoverageReport,
file_text: &str,
) -> Result<(), AnyError> {
let lines = file_text.split('\n').collect::<Vec<_>>();
print!("cover {} ... ", coverage_report.url);
@ -505,6 +541,7 @@ impl CoverageReporter for PrettyCoverageReporter {
last_line = Some(line_index);
}
Ok(())
}
fn done(&mut self) {}
@ -590,6 +627,16 @@ pub async fn cover_files(
let mut reporter = create_reporter(reporter_kind);
let out_mode = match coverage_flags.output {
Some(ref path) => match File::create(path) {
Ok(_) => Some(PathBuf::from(path)),
Err(e) => {
return Err(anyhow!("Failed to create output file: {}", e));
}
},
None => None,
};
for script_coverage in script_coverages {
let module_specifier =
deno_core::resolve_url_or_path(&script_coverage.url)?;
@ -653,9 +700,10 @@ pub async fn cover_files(
&script_coverage,
&transpiled_source,
&maybe_source_map,
&out_mode,
);
reporter.report(&coverage_report, original_source);
reporter.report(&coverage_report, original_source)?;
}
reporter.done();