summaryrefslogtreecommitdiff
path: root/cli/flags.rs
diff options
context:
space:
mode:
authorCasper Beyer <caspervonb@pm.me>2021-02-24 22:27:51 +0800
committerGitHub <noreply@github.com>2021-02-24 15:27:51 +0100
commitae8874b4b2015453e53965dae2a2dae9cacbce70 (patch)
treedc61e231685fc3bd0b32e90769ce0d3ad112e875 /cli/flags.rs
parentf6a80f34d9f750e6c9c6c40f57211fc95befdf7a (diff)
feat: add "deno coverage" subcommand (#8664)
This commit adds a new subcommand called "coverage" which can generate code coverage reports to stdout in multiple formats from code coverage profiles collected to disk. Currently this supports outputting a pretty printed diff and the lcov format for interoperability with third-party services and tools. Code coverage is still collected via other subcommands that run and collect code coverage such as "deno test --coverage=<directory>" but that command no longer prints a pretty printed report at the end of a test run with coverage collection enabled. The restrictions on which files that can be reported on has also been relaxed and are fully controllable with the include and exclude regular expression flags on the coverage subcommand. Co-authored-by: Luca Casonato <lucacasonato@yahoo.com>
Diffstat (limited to 'cli/flags.rs')
-rw-r--r--cli/flags.rs149
1 files changed, 128 insertions, 21 deletions
diff --git a/cli/flags.rs b/cli/flags.rs
index 0528a4243..fe88526f4 100644
--- a/cli/flags.rs
+++ b/cli/flags.rs
@@ -14,7 +14,6 @@ use log::Level;
use std::net::SocketAddr;
use std::path::PathBuf;
use std::str::FromStr;
-use tempfile::TempDir;
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub enum DenoSubcommand {
@@ -35,6 +34,13 @@ pub enum DenoSubcommand {
Completions {
buf: Box<[u8]>,
},
+ Coverage {
+ files: Vec<PathBuf>,
+ ignore: Vec<PathBuf>,
+ include: Vec<String>,
+ exclude: Vec<String>,
+ lcov: bool,
+ },
Doc {
private: bool,
json: bool,
@@ -300,6 +306,8 @@ pub fn flags_from_vec(args: Vec<String>) -> clap::Result<Flags> {
types_parse(&mut flags, m);
} else if let Some(m) = matches.subcommand_matches("cache") {
cache_parse(&mut flags, m);
+ } else if let Some(m) = matches.subcommand_matches("coverage") {
+ coverage_parse(&mut flags, m);
} else if let Some(m) = matches.subcommand_matches("info") {
info_parse(&mut flags, m);
} else if let Some(m) = matches.subcommand_matches("eval") {
@@ -375,6 +383,7 @@ If the flag is set, restrict these messages to errors.",
.subcommand(cache_subcommand())
.subcommand(compile_subcommand())
.subcommand(completions_subcommand())
+ .subcommand(coverage_subcommand())
.subcommand(doc_subcommand())
.subcommand(eval_subcommand())
.subcommand(fmt_subcommand())
@@ -569,6 +578,33 @@ fn cache_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
flags.subcommand = DenoSubcommand::Cache { files };
}
+fn coverage_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
+ let files = match matches.values_of("files") {
+ Some(f) => f.map(PathBuf::from).collect(),
+ None => vec![],
+ };
+ let ignore = match matches.values_of("ignore") {
+ Some(f) => f.map(PathBuf::from).collect(),
+ None => vec![],
+ };
+ let include = match matches.values_of("include") {
+ Some(f) => f.map(String::from).collect(),
+ None => vec![],
+ };
+ let exclude = match matches.values_of("exclude") {
+ Some(f) => f.map(String::from).collect(),
+ None => vec![],
+ };
+ let lcov = matches.is_present("lcov");
+ flags.subcommand = DenoSubcommand::Coverage {
+ files,
+ ignore,
+ include,
+ exclude,
+ lcov,
+ };
+}
+
fn lock_args_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
if matches.is_present("lock") {
let lockfile = matches.value_of("lock").unwrap();
@@ -672,23 +708,6 @@ fn test_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
let quiet = matches.is_present("quiet");
let filter = matches.value_of("filter").map(String::from);
- flags.coverage_dir = if matches.is_present("coverage") {
- if let Some(coverage_dir) = matches.value_of("coverage") {
- Some(coverage_dir.to_string())
- } else {
- Some(
- TempDir::new()
- .unwrap()
- .into_path()
- .to_str()
- .unwrap()
- .to_string(),
- )
- }
- } else {
- None
- };
-
if matches.is_present("script_arg") {
let script_arg: Vec<String> = matches
.values_of("script_arg")
@@ -712,6 +731,7 @@ fn test_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
None
};
+ flags.coverage_dir = matches.value_of("coverage").map(String::from);
flags.subcommand = DenoSubcommand::Test {
no_run,
fail_fast,
@@ -1099,6 +1119,77 @@ Future runs of this module will trigger no downloads or compilation unless
)
}
+fn coverage_subcommand<'a, 'b>() -> App<'a, 'b> {
+ SubCommand::with_name("coverage")
+ .about("Print coverage reports")
+ .long_about(
+ "Print coverage reports from coverage profiles.
+
+Collect a coverage profile with deno test:
+ deno test --coverage=cov_profile
+
+Print a report to stdout:
+ deno coverage cov_profile
+
+Include urls that start with the file schema:
+ deno coverage --include=\"^file:\" cov_profile
+
+Exclude urls ending with test.ts and test.js:
+ deno coverage --exclude=\"test\\.(ts|js)\" cov_profile
+
+Include urls that start with the file schema and exclude files ending with test.ts and test.js, for
+an url to match it must match the include pattern and not match the exclude pattern:
+ deno coverage --include=\"^file:\" --exclude=\"test\\.(ts|js)\" cov_profile
+
+Write a report using the lcov format:
+ deno coverage --lcov cov_profile > cov.lcov
+
+Generate html reports from lcov:
+ genhtml -o html_cov cov.lcov
+",
+ )
+ .arg(
+ Arg::with_name("ignore")
+ .long("ignore")
+ .takes_value(true)
+ .use_delimiter(true)
+ .require_equals(true)
+ .help("Ignore coverage files"),
+ )
+ .arg(
+ Arg::with_name("include")
+ .long("include")
+ .takes_value(true)
+ .value_name("regex")
+ .multiple(true)
+ .require_equals(true)
+ .default_value(r"^file:")
+ .help("Include source files in the report"),
+ )
+ .arg(
+ Arg::with_name("exclude")
+ .long("exclude")
+ .takes_value(true)
+ .value_name("regex")
+ .multiple(true)
+ .require_equals(true)
+ .default_value(r"test\.(js|mjs|ts|jsx|tsx)$")
+ .help("Exclude source files from the report"),
+ )
+ .arg(
+ Arg::with_name("lcov")
+ .long("lcov")
+ .help("Output coverage report in lcov format")
+ .takes_value(false),
+ )
+ .arg(
+ Arg::with_name("files")
+ .takes_value(true)
+ .multiple(true)
+ .required(true),
+ )
+}
+
fn upgrade_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("upgrade")
.about("Upgrade deno executable to given version")
@@ -1391,14 +1482,12 @@ fn test_subcommand<'a, 'b>() -> App<'a, 'b> {
.arg(
Arg::with_name("coverage")
.long("coverage")
- .min_values(0)
- .max_values(1)
.require_equals(true)
.takes_value(true)
.requires("unstable")
.conflicts_with("inspect")
.conflicts_with("inspect-brk")
- .help("Collect coverage information"),
+ .help("Collect coverage profile data"),
)
.arg(
Arg::with_name("files")
@@ -3430,6 +3519,24 @@ mod tests {
}
#[test]
+ fn coverage() {
+ let r = flags_from_vec(svec!["deno", "coverage", "foo.json"]);
+ assert_eq!(
+ r.unwrap(),
+ Flags {
+ subcommand: DenoSubcommand::Coverage {
+ 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: false,
+ },
+ ..Flags::default()
+ }
+ );
+ }
+
+ #[test]
fn location_with_bad_scheme() {
#[rustfmt::skip]
let r = flags_from_vec(svec!["deno", "run", "--location", "foo:", "mod.ts"]);