Skip to content

Commit 04dfdf1

Browse files
authored
fix(test): de-flake TestCleanDeletesOldestFiles (#2446)
The test failed intermittently in CI with messages like "Expect subA/file5.txt to match file(6|7|8)\.txt". Root cause is a race between the scanner and the main loop. With BatchN=4 and DeleteN=2, the scanner can pop a 5th candidate (e.g. file6_A) before the main loop reinserts the "younger" half of the previous batch (e.g. file7_A) back into the same subdir. The reinserted file7_A then never gets popped again, while file6_A goes on to be deleted in the next batch — producing a "gap" in the deleted suffix that the regex assertion didn't allow for. Set BatchN == DeleteN so splitBatch reinserts nothing, eliminating the cross-batch race. Replace the brittle filename regex with the real algorithmic invariant the cleaner now guarantees: per subdir the deleted indices form a contiguous suffix [N..8]. splitBatch's reinsert behavior is still covered by TestSplitBatch.
1 parent 06a6402 commit 04dfdf1

1 file changed

Lines changed: 36 additions & 15 deletions

File tree

packages/orchestrator/cmd/clean-nfs-cache/cleaner/clean_test.go

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,15 @@ func TestCleanDeletesOldestFiles(t *testing.T) {
8181
origFiles[sd] = names
8282
}
8383

84-
// Configure Cleaner to delete 2 files (target bytes equal to 2 files)
84+
// Configure Cleaner to delete 2 files (target bytes equal to 2 files).
85+
// BatchN must equal DeleteN here: with BatchN > DeleteN, splitBatch
86+
// reinserts the "younger" half of each batch, and this can race with the
87+
// scanner's next pop (which happens before the reinsert runs), allowing a
88+
// younger file to be deleted while an older sibling reinserted into the
89+
// same subdir is never popped again.
8590
opts := Options{
8691
Path: rootPath,
87-
BatchN: 4,
92+
BatchN: 2,
8893
DeleteN: 2,
8994
TargetBytesToDelete: 1024, // 2 * 512
9095
DryRun: false,
@@ -98,33 +103,49 @@ func TestCleanDeletesOldestFiles(t *testing.T) {
98103
err = c.Clean(t.Context())
99104
require.NoError(t, err)
100105

101-
// Collect which files remain and which were deleted
102-
deleted := []string{}
106+
// Collect which files remain and which were deleted, per subdir.
107+
deletedCount := 0
108+
remaining := map[string]map[string]bool{}
103109
for _, sd := range subdirs {
104110
dirPath := filepath.Join(rootPath, sd)
105111
entries, err := os.ReadDir(dirPath)
106112
require.NoError(t, err)
107-
remaining := map[string]bool{}
113+
remaining[sd] = map[string]bool{}
108114
for _, e := range entries {
109115
if !e.IsDir() {
110-
remaining[e.Name()] = true
116+
remaining[sd][e.Name()] = true
111117
}
112118
}
113119
for _, fn := range origFiles[sd] {
114-
if !remaining[fn] {
115-
deleted = append(deleted, filepath.Join(sd, fn))
120+
if !remaining[sd][fn] {
121+
deletedCount++
116122
}
117123
}
118124
}
119125

120-
// Expect at least 2 deletions, it can be more due to concurrency.
121-
require.GreaterOrEqual(t, len(deleted), 2)
126+
// Expect at least 2 deletions (target = 1024 bytes = 2 * 512). Concurrency
127+
// can cause additional batches to complete before the byte target is
128+
// observed, so we don't assert an upper bound.
129+
require.GreaterOrEqual(t, deletedCount, 2)
122130

123-
// The two files must have been some combination of 7 and 8 (from whichever
124-
// folder) Because of concurrency, sometimes we may pick an extra batch of
125-
// candidates, so include 6 as well.
126-
for _, d := range deleted {
127-
require.Regexp(t, `.+/file(6|7|8)\.txt`, d)
131+
// The cleaner pops the oldest file from each subdir; with BatchN==DeleteN
132+
// nothing is reinserted, so per subdir the deleted indices must form a
133+
// contiguous suffix [N..8]: if fileN was deleted then fileN+1 ... file8
134+
// must also be deleted.
135+
for _, sd := range subdirs {
136+
maxRemaining, minDeleted := -1, len(origFiles[sd])
137+
for i, fn := range origFiles[sd] {
138+
if remaining[sd][fn] {
139+
if i > maxRemaining {
140+
maxRemaining = i
141+
}
142+
} else if i < minDeleted {
143+
minDeleted = i
144+
}
145+
}
146+
require.Lessf(t, maxRemaining, minDeleted,
147+
"subdir %s: file%d.txt remained but file%d.txt was deleted (expected oldest-first deletion)",
148+
sd, maxRemaining, minDeleted)
128149
}
129150
}
130151

0 commit comments

Comments
 (0)