Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 64 additions & 18 deletions tests/functional/diff_collect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,15 +242,20 @@ fn diff_collect_errors_with_no_prior_collect() {
);
}

/// When one sha out of several diverges, `run` proceeds with the rows still
/// anchored at reachable shas — only tests stranded at the diverged sha
/// rerun (as "new"). The diverged rows stay in the DB until the user runs
/// `cargo affected clean`.
/// A `collect_sha` that HEAD has moved off of is still *reachable* as long
/// as its commit sits in the object database — reachability is `git cat-file
/// -e`, not ancestry. So `run` diffs against the sibling like any other sha:
/// the test anchored there comes back as plain `affected`, nothing is
/// stranded, and the run doesn't widen. The sibling's rows stay in the DB
/// until the user runs `cargo affected clean`.
///
/// The genuinely-missing case — where the commit is gone and its tests do
/// strand — is [`run_unions_affected_and_stranded_when_sha_is_missing`].
///
/// One-test-per-file isolates each test's row set per file so the
/// structural-edit backstop doesn't pull unrelated tests in.
#[test]
fn run_uses_reachable_shas_when_one_sha_diverges() {
fn run_uses_sibling_sha_without_stranding_or_widening() {
let tmp = tempfile::tempdir().unwrap();
let dir = tmp.path();
write_three_one_test_modules(dir, "sample_diff_partial_diverge");
Expand Down Expand Up @@ -298,9 +303,11 @@ fn run_uses_reachable_shas_when_one_sha_diverges() {

// Selection chose exactly one test (test_fa — anchored at sha1, which is
// a sibling but reachable, and the diff against it picks up the edit).
// The breakdown pins the *category*: a sibling sha contributes `affected`
// hits, never `stranded` ones.
assert!(
combined.contains("1 tests to run"),
"expected '1 tests to run' (test_fa affected via sha1), got:\n{combined}"
combined.contains("1 tests to run (1 affected + 0 config + 0 new + 0 stranded"),
"expected test_fa selected as 'affected' with nothing stranded, got:\n{combined}"
);
assert!(
combined.contains("test_fa"),
Expand Down Expand Up @@ -334,12 +341,20 @@ fn run_uses_reachable_shas_when_one_sha_diverges() {
);
}

/// Middle-case: one sha diverged, one reachable — and there's a real edit
/// against a file covered by the reachable sha. Verifies the union of
/// `affected` (over the reachable sha) and `new_tests` (the stranded one)
/// is what runs, not just one or the other.
/// Middle-case: one sha genuinely gone, one reachable — and there's a real
/// edit against a file covered by the reachable sha. Verifies the union of
/// `affected` (over the reachable sha) and `stranded` (the tests anchored
/// only at the missing one) is what runs, not just one or the other.
///
/// Resetting HEAD is *not* enough to strand anything. Reachability is
/// `git cat-file -e`, so an orphaned commit still in the object database
/// stays reachable and its tests come back as ordinary `affected` hits —
/// that variant is
/// [`run_uses_sibling_sha_without_stranding_or_widening`]. Expiring the
/// reflog and running `git gc --prune=now` is what actually removes the
/// commit and drives the `stranded` path.
#[test]
fn run_unions_affected_and_stranded_when_partially_diverged() {
fn run_unions_affected_and_stranded_when_sha_is_missing() {
let tmp = tempfile::tempdir().unwrap();
let dir = tmp.path();
write_three_one_test_modules(dir, "sample_diff_partial_diverge_with_edit");
Expand All @@ -358,18 +373,37 @@ fn run_unions_affected_and_stranded_when_partially_diverged() {
replace_in_file(&dir.join("src/a.rs"), "x + 1", "x + 1 /* edited */");
git(dir, &["add", "."]);
git(dir, &["commit", "-q", "-m", "edit fa"]);
let _sha1 = git_head(dir);
let sha1 = git_head(dir);
let diff = cargo_affected(dir, &["affected", "collect", "--diff"]);
assert!(
diff.status.success(),
"--diff failed: {}",
String::from_utf8_lossy(&diff.stderr),
);

// Reset HEAD to sha0 (orphans sha1) AND modify b.rs so there's a real
// diff against sha0. Selection should pick test_fb (overlap at sha0)
// AND test_fa (stranded → "new").
// Reset HEAD to sha0, then expire the reflog and gc so sha1's commit
// object is really gone — the reset alone orphans it but leaves it
// findable, which would make it reachable and this scenario a duplicate
// of the sibling test above.
git(dir, &["reset", "--hard", "-q", &sha0]);
git(dir, &["reflog", "expire", "--expire=now", "--all"]);
git(dir, &["gc", "--prune=now", "--quiet"]);
assert!(
// `.output()` rather than `.status()`: the probe is expected to fail,
// and this keeps git's "Not a valid object name" off the test log.
!std::process::Command::new("git")
.args(["cat-file", "-e", &format!("{sha1}^{{commit}}")])
.current_dir(dir)
.output()
.unwrap()
.status
.success(),
"sha1 {sha1} should be pruned out of the object database",
);

// Modify b.rs so there's a real diff against sha0. Selection should pick
// test_fb (overlap at sha0) AND test_fa (anchored only at the missing
// sha1 → stranded).
replace_in_file(&dir.join("src/b.rs"), "x + 1", "x + 1 /* run-time edit */");

let run = cargo_affected(dir, &["affected", "run", "-v"]);
Expand All @@ -381,9 +415,21 @@ fn run_unions_affected_and_stranded_when_partially_diverged() {
);
let combined = combined_output(&run);

// The breakdown is the assertion that matters: one hit from each
// category. A bare "2 tests to run" would also pass with both tests
// classified `affected`, which is exactly what happens if sha1 survives.
assert!(
combined.contains("2 tests to run (1 affected + 0 config + 0 new + 1 stranded"),
"expected the affected ∪ stranded union, got:\n{combined}"
);
assert!(
combined.contains("test_fa (stranded)"),
"test_fa is anchored only at the missing sha → stranded, got:\n{combined}"
);
// The missing sha is announced, not silently dropped.
assert!(
combined.contains("2 tests to run"),
"expected '2 tests to run' (test_fb affected + test_fa as new), got:\n{combined}"
combined.contains("not in the repo"),
"expected the missing-sha notice for {sha1}, got:\n{combined}"
);
for t in ["test_fa", "test_fb"] {
assert!(
Expand Down
Loading