Files
Josh Siviour 7d2106bfb5 fix(test): reclaim fixture scratch dirs stranded in /tmp (#598)
* fix(test): reclaim fixture scratch dirs stranded in /tmp

The shared predastore fixture and the integration suite's JetStream store both
outlive any individual test, so neither can use t.TempDir() - the first test to
finish would delete files the daemon still has open. Both used os.MkdirTemp
with nothing removing the directory afterwards, so every run stranded one, and
a run killed by a timeout panic or the OOM killer stranded it regardless. /tmp
is a tmpfs on the dev boxes, so the leak is charged to RAM and swap, and
swapped tmpfs pages have no backing store to be dropped to.

Add a shared sweep that reclaims a directory only when it matches both the
prefix and an age no live run can reach. The NATS store also has a clean
shutdown point, so TestMain now removes it outright and the sweep is left to
cover the killed-run case. The predastore daemon runs until the process exits
and has no such point, so the sweep is its only mechanism.

* chore: repin viperblock to the dev tip carrying the scratch-dir fix

Test-only upstream change, so the build output is unchanged - this keeps
go.mod aligned with the submodule pointer rather than delivering a fix.
2026-07-29 14:57:50 +10:00

51 lines
1.9 KiB
Go

package scratch
import (
"os"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// The sweep runs against a directory shared with other processes, so what it
// declines to touch matters as much as what it reclaims: only a directory that
// matches the prefix AND is far older than any live run may go.
func TestSweepAbandoned(t *testing.T) {
tmpDir := t.TempDir()
stale := time.Now().Add(-DefaultMaxAge - time.Hour)
mkdir := func(name string, modTime time.Time) string {
path := filepath.Join(tmpDir, name)
require.NoError(t, os.MkdirAll(filepath.Join(path, "data"), 0o750))
require.NoError(t, os.Chtimes(path, modTime, modTime))
return path
}
abandoned := mkdir("fixture-1234567890", stale)
concurrent := mkdir("fixture-0987654321", time.Now())
otherPrefix := mkdir("some-other-tool-cache", stale)
// A stale FILE sharing the prefix is not scratch and is not ours to delete.
stalefile := filepath.Join(tmpDir, "fixture-notadir")
require.NoError(t, os.WriteFile(stalefile, []byte("x"), 0o600))
require.NoError(t, os.Chtimes(stalefile, stale, stale))
SweepAbandoned(tmpDir, "fixture-", DefaultMaxAge)
assert.NoDirExists(t, abandoned, "a stale scratch dir from a killed run must be reclaimed")
assert.DirExists(t, concurrent, "a freshly-modified scratch dir may belong to a concurrent test binary")
assert.DirExists(t, otherPrefix, "the sweep must not touch directories outside its own prefix")
assert.FileExists(t, stalefile, "the sweep must only remove directories")
}
// A missing or unreadable directory is not a test failure: the sweep exists to
// reclaim disk, and failing to do so must never take the run down with it.
func TestSweepAbandonedToleratesUnreadableDir(t *testing.T) {
assert.NotPanics(t, func() {
SweepAbandoned(filepath.Join(t.TempDir(), "does-not-exist"), "fixture-", DefaultMaxAge)
})
}