1 Commits
Author SHA1 Message Date
sneak ac2f4416fa Stop the disk size tests failing at random on ZFS (closes #150)
check / check (push) Canceled after 0s
One ZFS commit can take a file through several sizes, and unix.Sync can
return before the blocks are counted. A test that expected the size on
disk to be settled therefore failed at random. The test that needs the
size to change now calls unix.Sync in a loop until it does, for at most
30 seconds. All three tests read the size before and after the scan and
fail only on a disk_size outside those two reads. The two that do not
wait call unix.Sync once first, so a wrong 512 usually fails them.

Model: opus-5-5
2026-11-03 00:07:03 +00:00
2 changed files with 25 additions and 16 deletions

No files matched your search

+5 -4
View File
@@ -30,10 +30,11 @@ Newest first. An entry ends with its date and its issue or branch, where those
are known. are known.
- The three disk size tests in `scan_test.go` no longer fail at random on ZFS. - The three disk size tests in `scan_test.go` no longer fail at random on ZFS.
`unix.Sync` can return before ZFS has counted a file's blocks. The test that `unix.Sync` can return before ZFS has counted a file's blocks, and one ZFS
needs the size to change waits for it, for at most 30 seconds. Each test reads commit can take a file through several sizes. The test that needs the size to
the size before and after its scan and accepts either value (2026-11-02, change waits for it, for at most 30 seconds. Each test reads the size before
https://git.eeqj.de/sneak/sfdupes/issues/150) and after its scan. It fails only on a `disk_size` outside those two reads
(2026-11-02, https://git.eeqj.de/sneak/sfdupes/issues/150)
- `TODO.md` rewritten from scratch by the same rules as `README.md`, sentence by - `TODO.md` rewritten from scratch by the same rules as `README.md`, sentence by
sentence. Every completed step is kept, with its date and its issue or branch. sentence. Every completed step is kept, with its date and its issue or branch.
+20 -12
View File
@@ -44,12 +44,14 @@ func writeFile(t *testing.T, dir, name string, data []byte) string {
// diskSizeOf returns the bytes the file at path takes on disk, from its // diskSizeOf returns the bytes the file at path takes on disk, from its
// own lstat: st_blocks counts 512-byte units. ZFS counts a write's // own lstat: st_blocks counts 512-byte units. ZFS counts a write's
// blocks only when it commits the write, seconds later and sometimes in // blocks only when it commits the write, up to seconds later, and one
// steps, so this value can change at any moment. fsync does not hasten // commit can take the file through several growing sizes. So this value
// that. unix.Sync usually does, but it can return before the blocks are // can change at any moment. Neither fsync nor unix.Sync hastens that,
// counted. A test that needs the value to change therefore waits for // but a unix.Sync call often takes long enough that the blocks are
// that, and a test that compares it with a scan's disk_size reads it // counted when it returns. A test that needs the value to change waits
// before and after the scan and accepts either. // for the change. A test that compares it with a scan's disk_size reads
// it before and after the scan, and fails only on a disk_size outside
// those two reads.
func diskSizeOf(t *testing.T, path string) int64 { func diskSizeOf(t *testing.T, path string) int64 {
t.Helper() t.Helper()
@@ -1449,6 +1451,8 @@ func TestScanStoresDiskSize(t *testing.T) {
p := sparseFile(t, t.TempDir(), "sparse", length) p := sparseFile(t, t.TempDir(), "sparse", length)
pokeAt(t, p, 0, []byte("x")) pokeAt(t, p, 0, []byte("x"))
unix.Sync() // see diskSizeOf
size := diskSizeOf(t, p) size := diskSizeOf(t, p)
if size == 0 || size >= length { if size == 0 || size >= length {
t.Fatalf("the file takes %d bytes on disk, want more than none "+ t.Fatalf("the file takes %d bytes on disk, want more than none "+
@@ -1466,8 +1470,8 @@ func TestScanStoresDiskSize(t *testing.T) {
after := diskSizeOf(t, p) after := diskSizeOf(t, p)
got := recordByPath(t, dbRecords(t, db), p).diskSize got := recordByPath(t, dbRecords(t, db), p).diskSize
if got != before && got != after { if got < before || got > after {
t.Errorf("scan of %s: disk_size = %d, want %d or %d", t.Errorf("scan of %s: disk_size = %d, want %d to %d",
operand, got, before, after) operand, got, before, after)
} }
} }
@@ -1511,6 +1515,8 @@ func TestSyncScanRewriteUpdatesDiskSize(t *testing.T) {
} }
// ZFS may still be counting, so the size may change during the rescan. // ZFS may still be counting, so the size may change during the rescan.
// A rescan that keeps the old size still fails, because the wait made
// before differ from it.
before := diskSizeOf(t, p) before := diskSizeOf(t, p)
st := syncTree(t, db, dir) st := syncTree(t, db, dir)
@@ -1522,8 +1528,8 @@ func TestSyncScanRewriteUpdatesDiskSize(t *testing.T) {
} }
got := recordByPath(t, dbRecords(t, db), p).diskSize got := recordByPath(t, dbRecords(t, db), p).diskSize
if got != before && got != after { if got < before || got > after {
t.Errorf("disk_size = %d, want %d or %d", got, before, after) t.Errorf("disk_size = %d, want %d to %d", got, before, after)
} }
} }
@@ -1547,6 +1553,8 @@ func TestSyncScanUnchangedUpdatesDiskSize(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
unix.Sync() // see diskSizeOf
// ZFS may count the blocks during the rescan; see diskSizeOf. // ZFS may count the blocks during the rescan; see diskSizeOf.
before := diskSizeOf(t, p) before := diskSizeOf(t, p)
@@ -1560,8 +1568,8 @@ func TestSyncScanUnchangedUpdatesDiskSize(t *testing.T) {
} }
got := recordByPath(t, dbRecords(t, db), p).diskSize got := recordByPath(t, dbRecords(t, db), p).diskSize
if got != before && got != after { if got < before || got > after {
t.Errorf("disk_size = %d, want %d or %d", got, before, after) t.Errorf("disk_size = %d, want %d to %d", got, before, after)
} }
} }