Files
pad/internal/testtmp/testtmp.go
T
xarmian 48c3ca066c fix(test): test temp dirs stop leaking into /tmp (BUG-3412) (#1786)
* fix(test): template and fixture temp dirs no longer leak into /tmp (BUG-3412)

The store test templates are named for the owning pid, and each build
first removes the template dirs of processes that are gone (and pid-less
legacy dirs over a day old), so a test binary killed before TestMain's
Cleanup no longer leaves its template on the tmpfs for good. The fence
source tests remove their fixture dirs when the file finishes.

Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS

* fix(test): never sweep pid-less template dirs; age proves no owner gone (codex r1 on BUG-3412)

Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS
2026-10-05 04:50:02 -04:00

60 lines
1.8 KiB
Go

// Package testtmp makes the process-wide temp directories test binaries
// share (BUG-3412) without leaking them when a binary dies before its
// cleanup runs.
//
// A test binary killed by a timeout, a signal or the harness's memory reaper
// never reaches TestMain's Cleanup, so its template directory stayed in /tmp
// for good; /tmp is a tmpfs here, so every leaked template is RAM. The
// directory name now carries the owning process id, and each new build first
// removes the directories of processes that are gone.
package testtmp
import (
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
)
// MkdirTemp is os.MkdirTemp("", prefix+"<pid>-*") after a sweep of earlier
// prefix directories whose process is dead. prefix must end in "-".
func MkdirTemp(prefix string) (string, error) {
Sweep(os.TempDir(), prefix)
return os.MkdirTemp("", fmt.Sprintf("%s%d-*", prefix, os.Getpid()))
}
// Sweep removes dir/prefix<pid>-* directories owned by this user whose
// process is no longer running. A directory without a pid (made before
// BUG-3412) is never removed: its age cannot prove its owner gone, since a
// stopped or suspended process can outlive any bound (codex r1). Errors are
// ignored: a sweep that cannot remove something leaves it as it was.
func Sweep(dir, prefix string) {
entries, err := os.ReadDir(dir)
if err != nil {
return
}
self := os.Getpid()
for _, e := range entries {
name := e.Name()
if !e.IsDir() || !strings.HasPrefix(name, prefix) {
continue
}
path := filepath.Join(dir, name)
info, err := e.Info()
if err != nil || !ownedByMe(info) {
continue
}
rest := name[len(prefix):]
pidPart, _, hasDash := strings.Cut(rest, "-")
pid, perr := strconv.Atoi(pidPart)
if !hasDash || perr != nil || pid <= 0 {
continue
}
if pid == self || processAlive(pid) {
continue
}
_ = os.RemoveAll(path)
}
}