diff --git a/Cargo.toml b/Cargo.toml index 9ad92d0b..2f1545fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ anyhow = "1.0.34" atty = "0.2.11" camino = "1.0.3" cargo_metadata = "0.14" -clap = "=3.0.0-beta.2" +clap = "=3.0.0-beta.4" dirs-next = "2" duct = "0.13.1" fs-err = "2.5" diff --git a/README.md b/README.md index 1c656d00..e75ea4f6 100644 --- a/README.md +++ b/README.md @@ -62,18 +62,25 @@ brew install taiki-e/tap/cargo-llvm-cov ```console $ cargo llvm-cov --help cargo-llvm-cov + Cargo subcommand for LLVM source-based code coverage (-Z instrument-coverage). Use -h for short descriptions and --help for more details. USAGE: - cargo llvm-cov [OPTIONS] [-- ...] + cargo llvm-cov [OPTIONS] [-- ...] ARGS: - ... + ... Arguments for the test binary OPTIONS: + -h, --help + Print help information + + -V, --version + Print version information + --json Export coverage data in "json" format @@ -187,12 +194,6 @@ OPTIONS: -Z ... Unstable (nightly-only) flags to Cargo - - -h, --help - Prints help information - - -V, --version - Prints version information ``` diff --git a/src/cli.rs b/src/cli.rs index e65ba3d2..2f8631ee 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,6 +1,6 @@ use anyhow::Result; use camino::Utf8PathBuf; -use clap::{AppSettings, Clap}; +use clap::{AppSettings, ArgSettings, Clap}; use serde::Deserialize; pub(crate) fn from_args() -> Result { @@ -17,24 +17,24 @@ const MAX_TERM_WIDTH: usize = 100; #[derive(Debug, Clap)] #[clap( bin_name = "cargo", - max_term_width = MAX_TERM_WIDTH, - setting = AppSettings::DeriveDisplayOrder, - setting = AppSettings::StrictUtf8, - setting = AppSettings::UnifiedHelpMessage, + max_term_width(MAX_TERM_WIDTH), + setting(AppSettings::DeriveDisplayOrder), + setting(AppSettings::StrictUtf8), + setting(AppSettings::UnifiedHelpMessage) )] enum Opts { - #[clap(about = ABOUT)] + #[clap(about(ABOUT))] LlvmCov(Args), } #[derive(Debug, Clap)] #[clap( bin_name = "cargo llvm-cov", - about = ABOUT, - max_term_width = MAX_TERM_WIDTH, - setting = AppSettings::DeriveDisplayOrder, - setting = AppSettings::StrictUtf8, - setting = AppSettings::UnifiedHelpMessage, + about(ABOUT), + max_term_width(MAX_TERM_WIDTH), + setting(AppSettings::DeriveDisplayOrder), + setting(AppSettings::StrictUtf8), + setting(AppSettings::UnifiedHelpMessage) )] pub(crate) struct Args { #[clap(subcommand)] @@ -63,7 +63,7 @@ pub(crate) struct Args { /// /// This internally calls `llvm-cov show -format=text`. /// See for more. - #[clap(long, conflicts_with_all = &["json", "lcov"])] + #[clap(long, conflicts_with = "json", conflicts_with = "lcov")] pub(crate) text: bool, /// Generate coverage reports in "html" format. //// @@ -71,36 +71,49 @@ pub(crate) struct Args { /// /// This internally calls `llvm-cov show -format=html`. /// See for more. - #[clap(long, conflicts_with_all = &["json", "lcov", "text"])] + #[clap(long, conflicts_with = "json", conflicts_with = "lcov", conflicts_with = "text")] pub(crate) html: bool, /// Generate coverage reports in "html" format and open them in a browser after the operation. /// /// See --html for more. - #[clap(long, conflicts_with_all = &["json", "lcov", "text"])] + #[clap(long, conflicts_with = "json", conflicts_with = "lcov", conflicts_with = "text")] pub(crate) open: bool, /// Export only summary information for each file in the coverage data. /// /// This flag can only be used together with either --json or --lcov. // If the format flag is not specified, this flag is no-op because the only summary is displayed anyway. - #[clap(long, conflicts_with_all = &["text", "html", "open"])] + #[clap(long, conflicts_with = "text", conflicts_with = "html", conflicts_with = "open")] pub(crate) summary_only: bool, /// Specify a file to write coverage data into. /// /// This flag can only be used together with --json, --lcov, or --text. /// See --output-dir for --html and --open. - #[clap(long, value_name = "PATH", conflicts_with_all = &["html", "open"])] + #[clap( + long, + value_name = "PATH", + conflicts_with = "html", + conflicts_with = "open", + setting(ArgSettings::ForbidEmptyValues) + )] pub(crate) output_path: Option, /// Specify a directory to write coverage reports into (default to `target/llvm-cov`). /// /// This flag can only be used together with --text, --html, or --open. /// See also --output-path. // If the format flag is not specified, this flag is no-op. - #[clap(long, value_name = "DIRECTORY", conflicts_with_all = &["json", "lcov", "output-path"])] + #[clap( + long, + value_name = "DIRECTORY", + conflicts_with = "json", + conflicts_with = "lcov", + conflicts_with = "output-path", + setting(ArgSettings::ForbidEmptyValues) + )] pub(crate) output_dir: Option, /// Skip source code files with file paths that match the given regular expression. - #[clap(long, value_name = "PATTERN")] + #[clap(long, value_name = "PATTERN", setting(ArgSettings::ForbidEmptyValues))] pub(crate) ignore_filename_regex: Option, // For debugging (unstable) #[clap(long, hidden = true)] @@ -129,13 +142,26 @@ pub(crate) struct Args { /// Package to run tests for // cargo allows the combination of --package and --workspace, but we reject // it because the situation where both flags are specified is odd. - #[clap(short, long, value_name = "SPEC", conflicts_with = "workspace")] + #[clap( + short, + long, + multiple_occurrences = true, + value_name = "SPEC", + conflicts_with = "workspace", + setting(ArgSettings::ForbidEmptyValues) + )] pub(crate) package: Vec, /// Test all packages in the workspace #[clap(long, visible_alias = "all")] pub(crate) workspace: bool, /// Exclude packages from the test - #[clap(long, value_name = "SPEC", requires = "workspace")] + #[clap( + long, + multiple_occurrences = true, + value_name = "SPEC", + requires = "workspace", + setting(ArgSettings::ForbidEmptyValues) + )] pub(crate) exclude: Vec, // TODO: Should this only work for cargo's --jobs? Or should it also work // for llvm-cov's -num-threads? @@ -146,7 +172,12 @@ pub(crate) struct Args { #[clap(long)] pub(crate) release: bool, /// Space or comma separated list of features to activate - #[clap(long, value_name = "FEATURES")] + #[clap( + long, + multiple_occurrences = true, + value_name = "FEATURES", + setting(ArgSettings::ForbidEmptyValues) + )] pub(crate) features: Vec, /// Activate all available features #[clap(long)] @@ -158,7 +189,7 @@ pub(crate) struct Args { /// /// When this option is used, coverage for proc-macro and build script will /// not be displayed because cargo does not pass RUSTFLAGS to them. - #[clap(long, value_name = "TRIPLE")] + #[clap(long, value_name = "TRIPLE", setting(ArgSettings::ForbidEmptyValues))] pub(crate) target: Option, // TODO: Currently, we are using a subdirectory of the target directory as // the actual target directory. What effect should this option have @@ -167,7 +198,7 @@ pub(crate) struct Args { // #[clap(long, value_name = "DIRECTORY")] // target_dir: Option, /// Path to Cargo.toml - #[clap(long, value_name = "PATH")] + #[clap(long, value_name = "PATH", setting(ArgSettings::ForbidEmptyValues))] pub(crate) manifest_path: Option, /// Use verbose output (-vv/-vvv propagate verbosity to cargo) #[clap(short, long, parse(from_occurrences))] @@ -184,11 +215,16 @@ pub(crate) struct Args { pub(crate) locked: bool, /// Unstable (nightly-only) flags to Cargo - #[clap(short = 'Z', value_name = "FLAG")] + #[clap( + short = 'Z', + multiple_occurrences = true, + value_name = "FLAG", + setting(ArgSettings::ForbidEmptyValues) + )] pub(crate) unstable_flags: Vec, /// Arguments for the test binary - #[clap(last = true)] + #[clap(last = true, setting(ArgSettings::ForbidEmptyValues))] pub(crate) args: Vec, } @@ -221,13 +257,8 @@ pub(crate) enum Coloring { } impl Coloring { - // TODO: use clap::ArgEnum::as_arg instead once new version of clap released. pub(crate) fn cargo_color(self) -> &'static str { - match self { - Self::Auto => "auto", - Self::Always => "always", - Self::Never => "never", - } + clap::ArgEnum::as_arg(&self).unwrap() } } @@ -236,11 +267,11 @@ mod tests { use std::{env, panic, path::Path, process::Command}; use anyhow::Result; - use clap::IntoApp; + use clap::{Clap, IntoApp}; use fs_err as fs; use tempfile::Builder; - use super::{Args, MAX_TERM_WIDTH}; + use super::{Args, Opts, MAX_TERM_WIDTH}; // https://github.com/clap-rs/clap/issues/751 #[cfg(unix)] @@ -248,10 +279,6 @@ mod tests { fn non_utf8_arg() { use std::{ffi::OsStr, os::unix::prelude::OsStrExt}; - use clap::Clap; - - use super::Opts; - // `cargo llvm-cov -- $'fo\x80o'` Opts::try_parse_from(&[ "cargo".as_ref(), @@ -262,6 +289,55 @@ mod tests { .unwrap_err(); } + // https://github.com/clap-rs/clap/issues/1772 + #[test] + fn multiple_occurrences() { + let Opts::LlvmCov(args) = + Opts::try_parse_from(&["cargo", "llvm-cov", "--features", "a", "--features", "b"]) + .unwrap(); + assert_eq!(args.features, ["a", "b"]); + + let Opts::LlvmCov(args) = + Opts::try_parse_from(&["cargo", "llvm-cov", "--package", "a", "--package", "b"]) + .unwrap(); + assert_eq!(args.package, ["a", "b"]); + + let Opts::LlvmCov(args) = Opts::try_parse_from(&[ + "cargo", + "llvm-cov", + "--exclude", + "a", + "--exclude", + "b", + "--all", + ]) + .unwrap(); + assert_eq!(args.exclude, ["a", "b"]); + + let Opts::LlvmCov(args) = + Opts::try_parse_from(&["cargo", "llvm-cov", "-Z", "a", "-Zb"]).unwrap(); + assert_eq!(args.unstable_flags, ["a", "b"]); + + let Opts::LlvmCov(args) = + Opts::try_parse_from(&["cargo", "llvm-cov", "--", "a", "b"]).unwrap(); + assert_eq!(args.args, ["a", "b"]); + } + + // https://github.com/clap-rs/clap/issues/1740 + #[test] + fn empty_value() { + Opts::try_parse_from(&["cargo", "llvm-cov", "--output-path", ""]).unwrap_err(); + Opts::try_parse_from(&["cargo", "llvm-cov", "--output-dir", ""]).unwrap_err(); + Opts::try_parse_from(&["cargo", "llvm-cov", "--ignore-filename-regex", ""]).unwrap_err(); + Opts::try_parse_from(&["cargo", "llvm-cov", "--package", ""]).unwrap_err(); + Opts::try_parse_from(&["cargo", "llvm-cov", "--exclude", ""]).unwrap_err(); + Opts::try_parse_from(&["cargo", "llvm-cov", "--features", ""]).unwrap_err(); + Opts::try_parse_from(&["cargo", "llvm-cov", "--target", ""]).unwrap_err(); + Opts::try_parse_from(&["cargo", "llvm-cov", "--manifest-path", ""]).unwrap_err(); + Opts::try_parse_from(&["cargo", "llvm-cov", "-Z", ""]).unwrap_err(); + Opts::try_parse_from(&["cargo", "llvm-cov", "--", ""]).unwrap_err(); + } + fn get_help(long: bool) -> Result { let mut buf = vec![]; if long { @@ -277,9 +353,6 @@ mod tests { out.push_str(line.trim_end()); out.push('\n'); } - if long { - out.pop(); - } Ok(out) } diff --git a/src/main.rs b/src/main.rs index ff2bf514..53315495 100644 --- a/src/main.rs +++ b/src/main.rs @@ -105,6 +105,9 @@ fn run_test(cx: &Context) -> Result<()> { " -Z instrument-coverage --remap-path-prefix {}/=", cx.metadata.workspace_root )); + if cx.target.is_none() { + rustflags.push(" --cfg trybuild_no_target"); + } // https://doc.rust-lang.org/nightly/unstable-book/compiler-flags/instrument-coverage.html#including-doc-tests let rustdocflags = &mut cx.env.rustdocflags.clone(); diff --git a/tests/fixtures/coverage-reports/merge/merge.full.json b/tests/fixtures/coverage-reports/merge/merge.full.json index 0b53f38c..7ba0efcf 100644 --- a/tests/fixtures/coverage-reports/merge/merge.full.json +++ b/tests/fixtures/coverage-reports/merge/merge.full.json @@ -191,7 +191,7 @@ 9, 3, 13, - 1, + 0, 0, 0, 0 @@ -201,7 +201,7 @@ 9, 5, 14, - 0, + 1, 0, 0, 0 @@ -229,7 +229,7 @@ [ 10, 1, - 14, + 12, 23, 1, 0, @@ -311,7 +311,7 @@ 9, 3, 13, - 0, + 1, 0, 0, 0 @@ -321,7 +321,7 @@ 9, 5, 14, - 1, + 0, 0, 0, 0 @@ -349,7 +349,7 @@ [ 10, 1, - 12, + 14, 23, 1, 0, diff --git a/tests/fixtures/coverage-reports/merge/merge.txt b/tests/fixtures/coverage-reports/merge/merge.txt index cd4543bf..aa9f9092 100644 --- a/tests/fixtures/coverage-reports/merge/merge.txt +++ b/tests/fixtures/coverage-reports/merge/merge.txt @@ -16,18 +16,18 @@ | merge::func: | 1| 1|fn func(x: i32) -> bool { | 2| 1| if x < 0 { - | 3| 1| true + | 3| 0| true | 4| | } else { - | 5| 0| false + | 5| 1| false | 6| | } | 7| 1|} ------------------ | merge::func: | 1| 1|fn func(x: i32) -> bool { | 2| 1| if x < 0 { - | 3| 0| true + | 3| 1| true | 4| | } else { - | 5| 1| false + | 5| 0| false | 6| | } | 7| 1|} ------------------ @@ -51,15 +51,15 @@ | 10| 1|fn test() { | 11| 1| #[cfg(feature = "a")] | 12| 1| assert!(!func(1)); - | 13| 1| #[cfg(feature = "b")] - | 14| 1| assert!(func(-1)); + | 13| | #[cfg(feature = "b")] + | 14| | assert!(func(-1)); | 15| 1|} ------------------ | merge::test: | 10| 1|fn test() { | 11| 1| #[cfg(feature = "a")] | 12| 1| assert!(!func(1)); - | 13| | #[cfg(feature = "b")] - | 14| | assert!(func(-1)); + | 13| 1| #[cfg(feature = "b")] + | 14| 1| assert!(func(-1)); | 15| 1|} ------------------ \ No newline at end of file diff --git a/tests/long-help.txt b/tests/long-help.txt index 8a252f9d..379d6f20 100644 --- a/tests/long-help.txt +++ b/tests/long-help.txt @@ -1,16 +1,23 @@ cargo-llvm-cov + Cargo subcommand for LLVM source-based code coverage (-Z instrument-coverage). Use -h for short descriptions and --help for more details. USAGE: - cargo llvm-cov [OPTIONS] [-- ...] + cargo llvm-cov [OPTIONS] [-- ...] ARGS: - ... + ... Arguments for the test binary OPTIONS: + -h, --help + Print help information + + -V, --version + Print version information + --json Export coverage data in "json" format @@ -124,9 +131,3 @@ OPTIONS: -Z ... Unstable (nightly-only) flags to Cargo - - -h, --help - Prints help information - - -V, --version - Prints version information diff --git a/tests/short-help.txt b/tests/short-help.txt index b8cddc18..96521515 100644 --- a/tests/short-help.txt +++ b/tests/short-help.txt @@ -1,18 +1,31 @@ cargo-llvm-cov + Cargo subcommand for LLVM source-based code coverage (-Z instrument-coverage). Use -h for short descriptions and --help for more details. USAGE: - cargo llvm-cov [OPTIONS] [-- ...] + cargo llvm-cov [OPTIONS] [-- ...] ARGS: - ... Arguments for the test binary + ... Arguments for the test binary OPTIONS: - --json Export coverage data in "json" format - --lcov Export coverage data in "lcov" format - --text Generate coverage reports in “text” format + -h, --help + Print help information + + -V, --version + Print version information + + --json + Export coverage data in "json" format + + --lcov + Export coverage data in "lcov" format + + --text + Generate coverage reports in “text” format + --html Generate coverage reports in "html" format. If --output-dir is not specified, the report will be generated in `target/llvm-cov` directory @@ -24,32 +37,65 @@ OPTIONS: --summary-only Export only summary information for each file in the coverage data - --output-path Specify a file to write coverage data into + --output-path + Specify a file to write coverage data into + --output-dir Specify a directory to write coverage reports into (default to `target/llvm-cov`) --ignore-filename-regex Skip source code files with file paths that match the given regular expression - --doctests Including doc tests (unstable) - --no-report Run tests, but don't generate coverage reports - --no-run Generate coverage reports without running tests - --no-fail-fast Run all tests regardless of failure - -p, --package ... Package to run tests for - --workspace Test all packages in the workspace [aliases: all] - --exclude ... Exclude packages from the test - --release Build artifacts in release mode, with optimizations - --features ... Space or comma separated list of features to activate - --all-features Activate all available features - --no-default-features Do not activate the `default` feature - --target Build for the target triple - --manifest-path Path to Cargo.toml + --doctests + Including doc tests (unstable) + + --no-report + Run tests, but don't generate coverage reports + + --no-run + Generate coverage reports without running tests + + --no-fail-fast + Run all tests regardless of failure + + -p, --package ... + Package to run tests for + + --workspace + Test all packages in the workspace [aliases: all] + + --exclude ... + Exclude packages from the test + + --release + Build artifacts in release mode, with optimizations + + --features ... + Space or comma separated list of features to activate + + --all-features + Activate all available features + + --no-default-features + Do not activate the `default` feature + + --target + Build for the target triple + + --manifest-path + Path to Cargo.toml + -v, --verbose Use verbose output (-vv/-vvv propagate verbosity to cargo) - --color Coloring [possible values: auto, always, never] - --frozen Require Cargo.lock and cache are up to date - --locked Require Cargo.lock is up to date - -Z ... Unstable (nightly-only) flags to Cargo - -h, --help Prints help information - -V, --version Prints version information + --color + Coloring [possible values: auto, always, never] + + --frozen + Require Cargo.lock and cache are up to date + + --locked + Require Cargo.lock is up to date + + -Z ... + Unstable (nightly-only) flags to Cargo diff --git a/tests/test.rs b/tests/test.rs index bf6ecd62..ffbc97a1 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -79,12 +79,6 @@ fn merge() { let output_dir = auxiliary::FIXTURES_PATH.join("coverage-reports").join(model); fs::create_dir_all(&output_dir).unwrap(); for (extension, args) in test_set() { - // TODO: On windows, the order of the instantiations in the generated coverage report will be different. - #[cfg(windows)] - if extension == "txt" || extension == "full.json" { - continue; - } - let workspace_root = auxiliary::test_project(model, model).unwrap(); let output_path = &output_dir.join(model).with_extension(extension); cargo_llvm_cov()