use crate::{
cargo_affected, combined_output, init_git_with_initial_commit, replace_in_file,
write_two_module_project,
};
#[test]
fn run_executes_only_affected_tests() {
let tmp = tempfile::tempdir().unwrap();
let dir = tmp.path();
write_two_module_project(dir, "sample_run");
init_git_with_initial_commit(dir);
let collect = cargo_affected(dir, &["affected", "collect"]);
assert!(
collect.status.success(),
"collect failed: {}",
String::from_utf8_lossy(&collect.stderr)
);
replace_in_file(&dir.join("src/math.rs"), "a + b", "a + b /* edited */");
let run = cargo_affected(dir, &["affected", "run", "-v"]);
assert!(
run.status.success(),
"run failed: stderr=\n{}\nstdout=\n{}",
String::from_utf8_lossy(&run.stderr),
String::from_utf8_lossy(&run.stdout)
);
let combined = combined_output(&run);
assert!(
combined.contains("1 tests to run"),
"expected '1 tests to run' in run output, got:\n{combined}"
);
assert!(
combined.contains("test_add"),
"expected test_add in selection listing, got:\n{combined}"
);
assert!(
!combined.contains("test_multiply"),
"test_multiply must NOT appear (its range didn't overlap the edit), got:\n{combined}"
);
assert!(
!combined.contains("test_greet"),
"test_greet must NOT appear (strings.rs unchanged), got:\n{combined}"
);
assert!(
combined.contains("PASS") && combined.contains("test_add"),
"expected nextest to PASS test_add, got:\n{combined}"
);
}
#[test]
fn run_forwards_fail_fast_flags_to_nextest() {
let tmp = tempfile::tempdir().unwrap();
let dir = tmp.path();
write_two_module_project(dir, "sample_run_fail_fast");
init_git_with_initial_commit(dir);
let collect = cargo_affected(dir, &["affected", "collect"]);
assert!(
collect.status.success(),
"collect failed: {}",
String::from_utf8_lossy(&collect.stderr)
);
replace_in_file(&dir.join("src/math.rs"), "a + b", "a + b + 1");
replace_in_file(&dir.join("src/math.rs"), "a * b", "a * b + 1");
let default = cargo_affected(
dir,
&["affected", "run", "--", "--test-threads=1"],
);
assert!(
!default.status.success(),
"default run should fail when tests fail",
);
let default_out = combined_output(&default);
assert!(
default_out.contains("Cancelling"),
"expected nextest to print its 'Cancelling due to test failure' \
line on default fail-fast; got:\n{default_out}"
);
assert!(
!default_out.contains("test_multiply"),
"test_multiply must not run when default fail-fast cancels after \
the first failure; got:\n{default_out}"
);
let nff = cargo_affected(
dir,
&["affected", "run", "--", "--test-threads=1", "--no-fail-fast"],
);
assert!(
!nff.status.success(),
"run with --no-fail-fast should still fail when tests fail",
);
let nff_out = combined_output(&nff);
assert!(
!nff_out.contains("Cancelling"),
"expected --no-fail-fast to skip nextest's cancel path; got:\n{nff_out}"
);
assert!(
nff_out.contains("test_add") && nff_out.contains("test_multiply"),
"expected both failing tests to appear with --no-fail-fast; got:\n{nff_out}"
);
let mf2 = cargo_affected(
dir,
&["affected", "run", "--", "--test-threads=1", "--max-fail=2"],
);
let mf2_out = combined_output(&mf2);
assert!(
mf2_out.contains("test_add") && mf2_out.contains("test_multiply"),
"expected --max-fail=2 to run both tests; got:\n{mf2_out}"
);
assert_eq!(
nff.status.code(),
Some(100),
"expected nextest's test-failure exit code (100) to propagate"
);
}
#[test]
fn run_with_no_changes_reports_nothing_to_do() {
let tmp = tempfile::tempdir().unwrap();
let dir = tmp.path();
write_two_module_project(dir, "sample_run_clean");
init_git_with_initial_commit(dir);
let collect = cargo_affected(dir, &["affected", "collect"]);
assert!(
collect.status.success(),
"collect failed: {}",
String::from_utf8_lossy(&collect.stderr)
);
let run = cargo_affected(dir, &["affected", "run"]);
assert!(
run.status.success(),
"run on clean tree failed: {}",
String::from_utf8_lossy(&run.stderr)
);
let stderr = String::from_utf8_lossy(&run.stderr);
assert!(
stderr.contains("nothing to run"),
"expected 'nothing to run' message on clean tree, got:\n{stderr}"
);
}