From 1a004e8dbbb3c94801bee913d71a8ad32217ca96 Mon Sep 17 00:00:00 2001 From: sneak Date: Sun, 4 Oct 2026 11:16:25 +0000 Subject: [PATCH] Test both walk cancellation checks on their own (closes #81) walkOneDir's check was hiding the worker's: a worker that walked a queued directory on a cancelled scan still emitted nothing, because walkOneDir stopped at its first entry. The worker test now queues a missing directory, whose read fails and sends a warning before walkOneDir's check is reached. A new test calls walkOneDir directly on a cancelled scan and checks it returns no subdirectory to descend into. Model: opus-5-5 --- TODO.md | 3 +++ cancel_test.go | 45 +++++++++++++++++++++++++++++++++++++-------- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/TODO.md b/TODO.md index 57c7daf..54e44df 100644 --- a/TODO.md +++ b/TODO.md @@ -29,6 +29,9 @@ # Completed Steps +- a test fails when either walk cancellation check in `scan.go` is + removed (2026-10-04, https://git.eeqj.de/sneak/sfdupes/issues/81) + - correct four inaccurate comments in `cancel_test.go` and rename `walkCancelInFlightDirs` to `walkCancelInFlightFiles` (2026-10-04, https://git.eeqj.de/sneak/sfdupes/issues/33) diff --git a/cancel_test.go b/cancel_test.go index 59e4210..8723bb4 100644 --- a/cancel_test.go +++ b/cancel_test.go @@ -586,20 +586,49 @@ func TestSendEventAbandonsBlockedSend(t *testing.T) { awaitReturn(t, done, "sendEvent") } -// TestWalkWorkersDropQueuedDirs checks that cancelled walk workers keep -// reading jobs and drop the directories rather than stopping their -// read: the range over jobs has to run out for the pool to tear down -// and close its event stream. -func TestWalkWorkersDropQueuedDirs(t *testing.T) { +// TestWalkOneDirStopsWhenCancelled checks that a cancelled scan stops +// reading a directory instead of going through the rest of its +// entries. A walk that kept going would return the subdirectory below +// to descend into. Unlike a file event, that return is not a send the +// cancellation can abandon, so the test catches the regression every +// time. +func TestWalkOneDirStopsWhenCancelled(t *testing.T) { t.Parallel() dir := t.TempDir() - writeEmptyFiles(t, dir, walkCancelFilesPerDir) + + err := os.Mkdir(filepath.Join(dir, "sub"), 0o750) + if err != nil { + t.Fatal(err) + } + + // Unbuffered and unread: on a cancelled scan every send gives up. + events := make(chan walkEvent) + + subs := walkOneDir(cancelledContext(t), dirJob{path: dir}, false, events) + if len(subs) != 0 { + t.Errorf("cancelled walkOneDir returned %+v to descend into, "+ + "want none", subs) + } +} + +// TestWalkWorkersDropQueuedDirs checks that cancelled walk workers keep +// reading jobs and drop the directories rather than stopping their +// read: the range over jobs has to run out for the pool to tear down +// and close its event stream. The queued directory does not exist, so +// a worker that walked it anyway would send a warning before +// walkOneDir's own cancellation check could stop it. On a cancelled +// scan that send delivers or gives up at random, so with 64 jobs +// queued the regression has a one in 2^64 chance of passing. +func TestWalkWorkersDropQueuedDirs(t *testing.T) { + t.Parallel() + + missing := filepath.Join(t.TempDir(), "missing") jobs, _, events := startWalkWorkers(cancelledContext(t), 2, false) - for range 4 { - jobs <- dirJob{path: dir} + for range 64 { + jobs <- dirJob{path: missing} } close(jobs)