use super::run::filter_env_vars_for_recording;
use crate::dispatch::{app::CargoNextestApp, core::filter::TestBuildFilter};
use clap::Parser;
use nextest_runner::run_mode::NextestRunMode;
use std::collections::BTreeMap;
#[test]
fn test_argument_parsing() {
use clap::error::ErrorKind::{self, *};
let valid: &[&'static str] = &[
"cargo nextest list",
"cargo nextest run",
"cargo nextest list --list-type binaries-only",
"cargo nextest list --list-type full",
"cargo nextest list --message-format json-pretty",
"cargo nextest list --message-format oneline",
"cargo nextest list --message-format auto",
"cargo nextest list -T oneline",
"cargo nextest list -T auto",
"cargo nextest run --failure-output never",
"cargo nextest run --success-output=immediate",
"cargo nextest run --status-level=all",
"cargo nextest run --no-capture",
"cargo nextest run --nocapture",
"cargo nextest run --no-run",
"cargo nextest run --final-status-level flaky",
"cargo nextest run --max-fail 3",
"cargo nextest run --max-fail=all",
"cargo nextest run --final-status-level retry",
"NEXTEST_HIDE_PROGRESS_BAR=1 cargo nextest run",
"NEXTEST_HIDE_PROGRESS_BAR=true cargo nextest run",
"cargo nextest run --no-run -j8",
"cargo nextest run --no-run --retries 3",
"NEXTEST_TEST_THREADS=8 cargo nextest run --no-run",
"cargo nextest run --no-run --success-output never",
"NEXTEST_SUCCESS_OUTPUT=never cargo nextest run --no-run",
"cargo nextest run --no-run --failure-output immediate",
"NEXTEST_FAILURE_OUTPUT=immediate cargo nextest run --no-run",
"cargo nextest run --no-run --status-level pass",
"NEXTEST_STATUS_LEVEL=pass cargo nextest run --no-run",
"cargo nextest run --no-run --final-status-level skip",
"NEXTEST_FINAL_STATUS_LEVEL=skip cargo nextest run --no-run",
"cargo nextest run --no-capture --test-threads=24",
"NEXTEST_NO_CAPTURE=1 cargo nextest run --test-threads=24",
"cargo nextest run --no-capture --failure-output=never",
"NEXTEST_NO_CAPTURE=1 cargo nextest run --failure-output=never",
"cargo nextest run --no-capture --success-output=final",
"NEXTEST_SUCCESS_OUTPUT=final cargo nextest run --no-capture",
"cargo nextest list --lib --bins",
"cargo nextest run --ignore-rust-version --unit-graph",
"cargo nextest list --cargo-message-format human",
"cargo nextest list --cargo-message-format short",
"cargo nextest list --cargo-message-format json",
"cargo nextest list --cargo-message-format json-diagnostic-short",
"cargo nextest list --cargo-message-format json-diagnostic-rendered-ansi",
"cargo nextest list --cargo-message-format json-render-diagnostics",
"cargo nextest run --cargo-message-format json",
"cargo nextest list --no-pager",
"cargo nextest show-config test-groups --no-pager",
"cargo nextest list --binaries-metadata=foo",
"cargo nextest run --binaries-metadata=foo --target-dir-remap=bar",
"cargo nextest list --cargo-metadata path",
"cargo nextest run --cargo-metadata=path --workspace-remap remapped-path",
"cargo nextest archive --archive-file my-archive.tar.zst --zstd-level -1",
"cargo nextest archive --archive-file my-archive.foo --archive-format tar-zst",
"cargo nextest archive --archive-file my-archive.foo --archive-format tar-zstd",
"cargo nextest list --archive-file my-archive.tar.zst",
"cargo nextest list --archive-file my-archive.tar.zst --archive-format tar-zst",
"cargo nextest list --archive-file my-archive.tar.zst --extract-to my-path",
"cargo nextest list --archive-file my-archive.tar.zst --extract-to my-path --extract-overwrite",
"cargo nextest list --archive-file my-archive.tar.zst --persist-extract-tempdir",
"cargo nextest list --archive-file my-archive.tar.zst --workspace-remap foo",
"cargo nextest list --archive-file my-archive.tar.zst --config target.'cfg(all())'.runner=\"my-runner\"",
"cargo nextest list -E deps(foo)",
"cargo nextest run --filterset 'test(bar)' --package=my-package test-filter",
"cargo nextest run --filter-expr 'test(bar)' --package=my-package test-filter",
"cargo nextest list -E 'deps(foo)' --ignore-default-filter",
"cargo nextest run --stress-count 4",
"cargo nextest run --stress-count infinite",
"cargo nextest run --stress-duration 60m",
"cargo nextest run --stress-duration 24h",
"cargo nextest run -- --a an arbitrary arg",
"cargo nextest run --jobs -3",
"cargo nextest run --jobs 3",
"cargo nextest run --build-jobs -1",
"cargo nextest run --build-jobs 1",
"cargo nextest self update",
"cargo nextest self update --beta",
"cargo nextest self update --rc",
"cargo nextest self update --version 0.9.100",
"cargo nextest self update --version latest",
"cargo nextest self update --check",
"cargo nextest self update --beta --check",
"cargo nextest self update --rc --force",
"cargo nextest bench",
"cargo nextest bench --no-run",
"cargo nextest bench --fail-fast",
"cargo nextest bench --no-fail-fast",
"cargo nextest bench --max-fail 3",
"cargo nextest bench --max-fail=all",
"cargo nextest bench --stress-count 4",
"cargo nextest bench --stress-count infinite",
"cargo nextest bench --stress-duration 60m",
"cargo nextest bench --debugger gdb",
"cargo nextest bench --tracer strace",
"cargo nextest replay",
"cargo nextest replay --run-id abc123",
"cargo nextest replay -R abc123",
"cargo nextest replay --exit-code",
"cargo nextest replay --no-capture",
"cargo nextest replay --nocapture",
"cargo nextest replay --no-capture --failure-output never",
"cargo nextest replay --no-capture --success-output final",
"cargo nextest replay --no-capture --no-output-indent",
"cargo nextest replay --status-level pass",
"cargo nextest replay --final-status-level flaky",
"cargo nextest replay -R /proc/self/fd/11",
"cargo nextest replay -R /dev/fd/5",
"cargo nextest store list",
"cargo nextest store info latest",
"cargo nextest store info abc123",
"cargo nextest store info -R latest",
"cargo nextest store info --run-id abc123",
"cargo nextest store info archive.zip",
"cargo nextest store info /path/to/run.zip",
"cargo nextest store info -R ./relative/path.zip",
"cargo nextest store info /proc/self/fd/11",
"cargo nextest store info /dev/fd/5",
"cargo nextest store export latest",
"cargo nextest store export abc123",
"cargo nextest store export -R latest",
"cargo nextest store export --run-id abc123",
"cargo nextest store export latest --archive-file output.zip",
"cargo nextest store export --run-id abc123 --archive-file /path/to/archive.zip",
"cargo nextest store prune",
"cargo nextest store prune --dry-run",
"cargo nextest run --rerun archive.zip",
"cargo nextest run --rerun /path/to/run.zip",
"cargo nextest run -R ./relative/path.zip",
"cargo nextest run --rerun ../parent/run.zip",
"cargo nextest run --rerun /proc/self/fd/11",
"cargo nextest run -R /dev/fd/5",
];
let invalid: &[(&'static str, ErrorKind)] = &[
("cargo nextest run --no-run --fail-fast", ArgumentConflict),
(
"cargo nextest run --no-run --no-fail-fast",
ArgumentConflict,
),
("cargo nextest run --no-run --max-fail=3", ArgumentConflict),
(
"cargo nextest run --max-fail=3 --no-fail-fast",
ArgumentConflict,
),
(
"cargo nextest run --manifest-path foo --cargo-metadata bar",
ArgumentConflict,
),
(
"cargo nextest run --binaries-metadata=foo --lib",
ArgumentConflict,
),
(
"cargo nextest run --workspace-remap foo",
MissingRequiredArgument,
),
(
"cargo nextest run --target-dir-remap bar",
MissingRequiredArgument,
),
(
"cargo nextest run --archive-format tar-zst",
MissingRequiredArgument,
),
(
"cargo nextest run --archive-file foo --archive-format no",
InvalidValue,
),
(
"cargo nextest run --extract-to foo",
MissingRequiredArgument,
),
(
"cargo nextest run --archive-file foo --extract-overwrite",
MissingRequiredArgument,
),
(
"cargo nextest run --extract-to foo --extract-overwrite",
MissingRequiredArgument,
),
(
"cargo nextest run --persist-extract-tempdir",
MissingRequiredArgument,
),
(
"cargo nextest run --archive-file foo --extract-to bar --persist-extract-tempdir",
ArgumentConflict,
),
(
"cargo nextest run --archive-file foo --cargo-metadata bar",
ArgumentConflict,
),
(
"cargo nextest run --archive-file foo --binaries-metadata bar",
ArgumentConflict,
),
(
"cargo nextest run --archive-file foo --target-dir-remap bar",
ArgumentConflict,
),
("cargo nextest run --jobs 0", ValueValidation),
("cargo nextest run --jobs -twenty", UnknownArgument),
("cargo nextest run --build-jobs -inf1", UnknownArgument),
("cargo nextest run --stress-count 0", ValueValidation),
("cargo nextest run --stress-duration 0m", ValueValidation),
(
"cargo nextest run --debugger gdb --stress-count 4",
ArgumentConflict,
),
(
"cargo nextest run --debugger gdb --stress-duration 1h",
ArgumentConflict,
),
(
"cargo nextest run --debugger gdb --no-run",
ArgumentConflict,
),
("cargo nextest bench --no-run --fail-fast", ArgumentConflict),
(
"cargo nextest bench --no-run --no-fail-fast",
ArgumentConflict,
),
(
"cargo nextest bench --no-run --max-fail=3",
ArgumentConflict,
),
(
"cargo nextest bench --max-fail=3 --no-fail-fast",
ArgumentConflict,
),
(
"cargo nextest bench --debugger gdb --stress-count 4",
ArgumentConflict,
),
(
"cargo nextest bench --debugger gdb --stress-duration 1h",
ArgumentConflict,
),
(
"cargo nextest bench --debugger gdb --no-run",
ArgumentConflict,
),
(
"cargo nextest bench --tracer strace --stress-count 4",
ArgumentConflict,
),
("cargo nextest bench --stress-count 0", ValueValidation),
("cargo nextest bench --stress-duration 0m", ValueValidation),
("cargo nextest self update --beta --rc", ArgumentConflict),
(
"cargo nextest self update --beta --version 0.9.100",
ArgumentConflict,
),
(
"cargo nextest self update --rc --version 0.9.100",
ArgumentConflict,
),
("cargo nextest store info", MissingRequiredArgument),
(
"cargo nextest store info latest -R abc123",
ArgumentConflict,
),
(
"cargo nextest store info latest --run-id abc123",
ArgumentConflict,
),
("cargo nextest store export", MissingRequiredArgument),
(
"cargo nextest store export latest -R abc123",
ArgumentConflict,
),
(
"cargo nextest store export latest --run-id abc123",
ArgumentConflict,
),
("cargo nextest store export archive.zip", ValueValidation),
(
"cargo nextest store export /path/to/run.zip",
ValueValidation,
),
(
"cargo nextest store export -R ./relative.zip",
ValueValidation,
),
(
"cargo nextest store export latest --archive-file output.txt",
ValueValidation,
),
(
"cargo nextest store export latest --archive-file /path/to/archive.tar.zst",
ValueValidation,
),
];
for (k, _) in std::env::vars() {
if k.starts_with("NEXTEST_") {
unsafe { std::env::remove_var(k) };
}
}
for valid_args in valid {
let cmd = shell_words::split(valid_args).expect("valid command line");
let env_vars: Vec<_> = cmd
.iter()
.take_while(|arg| arg.contains('='))
.cloned()
.collect();
let mut env_keys = Vec::with_capacity(env_vars.len());
for k_v in &env_vars {
let (k, v) = k_v.split_once('=').expect("valid env var");
unsafe { std::env::set_var(k, v) };
env_keys.push(k);
}
let cmd = cmd.iter().skip(env_vars.len());
if let Err(error) = CargoNextestApp::try_parse_from(cmd) {
panic!("{valid_args} should have successfully parsed, but didn't: {error}");
}
for &k in &env_keys {
unsafe { std::env::remove_var(k) };
}
}
for &(invalid_args, kind) in invalid {
match CargoNextestApp::try_parse_from(
shell_words::split(invalid_args).expect("valid command"),
) {
Ok(_) => {
panic!("{invalid_args} should have errored out but successfully parsed");
}
Err(error) => {
let actual_kind = error.kind();
if kind != actual_kind {
panic!(
"{invalid_args} should error with kind {kind:?}, but actual kind was {actual_kind:?}",
);
}
}
}
}
}
#[derive(Debug, clap::Parser)]
struct TestCli {
#[structopt(flatten)]
build_filter: TestBuildFilter,
}
#[test]
fn test_test_binary_argument_parsing() {
use crate::{ExpectedError, Result};
use nextest_runner::test_filter::{RunIgnored, TestFilter, TestFilterPatterns};
fn get_test_filter(cmd: &str) -> Result<TestFilter> {
let app = TestCli::try_parse_from(shell_words::split(cmd).expect("valid command line"))
.unwrap_or_else(|_| panic!("{cmd} should have successfully parsed"));
app.build_filter
.make_test_filter(NextestRunMode::Test, vec![])
}
let valid = &[
("foo -- str1", "foo str1"),
("foo -- str2 str3", "foo str2 str3"),
("foo -- --ignored", "foo --run-ignored only"),
("foo -- --ignored", "foo --run-ignored ignored-only"),
("foo -- --include-ignored", "foo --run-ignored all"),
(
"foo -- --ignored -- str --- --ignored",
"foo --run-ignored ignored-only str -- -- --- --ignored",
),
("foo -- -- str1 str2 --", "foo str1 str2 -- -- --"),
];
let skip_exact = &[
("foo -- --skip my-pattern --skip your-pattern", {
let mut patterns = TestFilterPatterns::default();
patterns.add_skip_pattern("my-pattern".to_owned());
patterns.add_skip_pattern("your-pattern".to_owned());
patterns
}),
("foo -- pattern1 --skip my-pattern --skip your-pattern", {
let mut patterns = TestFilterPatterns::default();
patterns.add_substring_pattern("pattern1".to_owned());
patterns.add_skip_pattern("my-pattern".to_owned());
patterns.add_skip_pattern("your-pattern".to_owned());
patterns
}),
(
"foo -- --skip my-pattern --skip your-pattern exact1 --exact pattern2",
{
let mut patterns = TestFilterPatterns::default();
patterns.add_skip_exact_pattern("my-pattern".to_owned());
patterns.add_skip_exact_pattern("your-pattern".to_owned());
patterns.add_exact_pattern("exact1".to_owned());
patterns.add_exact_pattern("pattern2".to_owned());
patterns
},
),
];
let invalid = &[
("foo -- --include-ignored --include-ignored", "duplicated"),
("foo -- --ignored --ignored", "duplicated"),
("foo -- --exact --exact", "duplicated"),
("foo -- --ignored --include-ignored", "mutually exclusive"),
("foo --run-ignored all -- --ignored", "mutually exclusive"),
("foo -- --skip", "missing required argument"),
("foo -- --bar", "unsupported"),
];
for (a, b) in valid {
let a_str = format!(
"{:?}",
get_test_filter(a).unwrap_or_else(|_| panic!("failed to parse {a}"))
);
let b_str = format!(
"{:?}",
get_test_filter(b).unwrap_or_else(|_| panic!("failed to parse {b}"))
);
assert_eq!(a_str, b_str);
}
for (args, patterns) in skip_exact {
let builder = get_test_filter(args).unwrap_or_else(|_| panic!("failed to parse {args}"));
let builder2 = TestFilter::new(
NextestRunMode::Test,
RunIgnored::Default,
patterns.clone(),
Vec::new(),
)
.unwrap_or_else(|_| panic!("failed to build TestFilter"));
assert!(
builder.patterns_eq(&builder2),
"{args} matches expected (from TestCli: {:?}, from direct construction: {:?})",
builder,
builder2,
);
}
for (s, r) in invalid {
let res = get_test_filter(s);
if let Err(ExpectedError::TestBinaryArgsParseError { reason, .. }) = &res {
assert_eq!(reason, r);
} else {
panic!("{s} should have errored out with TestBinaryArgsParseError, actual: {res:?}",);
}
}
}
#[test]
fn test_filter_env_vars_for_recording() {
let input = [
("NEXTEST_PROFILE", "ci"),
("CARGO_HOME", "/home/user/.cargo"),
("NEXTEST_TEST_THREADS", "4"),
("CARGO_TARGET_DIR", "/tmp/target"),
("NEXTEST_TOKEN", "secret123"),
("CARGO_REGISTRY_TOKEN", "crates-io-token"),
("NEXTEST_API_TOKEN", "api-secret"),
("PATH", "/usr/bin"),
("HOME", "/home/user"),
("RUST_BACKTRACE", "1"),
("MY_TOKEN", "other-token"),
("NEXTEST_TOKEN_COUNT", "5"),
("CARGO_TOKEN_PATH", "/path"),
];
let result =
filter_env_vars_for_recording(input.into_iter().map(|(k, v)| (k.to_owned(), v.to_owned())));
let expected: BTreeMap<String, String> = [
("CARGO_HOME", "/home/user/.cargo"),
("CARGO_TARGET_DIR", "/tmp/target"),
("CARGO_TOKEN_PATH", "/path"),
("NEXTEST_PROFILE", "ci"),
("NEXTEST_TEST_THREADS", "4"),
("NEXTEST_TOKEN_COUNT", "5"),
]
.into_iter()
.map(|(k, v)| (k.to_owned(), v.to_owned()))
.collect();
assert_eq!(result, expected);
}