diff --git a/TODO.md b/TODO.md index d824ca3..53e2e68 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) + - test that `scan` refuses a database with another schema version (2026-10-04, https://git.eeqj.de/sneak/sfdupes/issues/64) 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)