Artifact f2032d6b6bbf66cb8f31c08cfeffe96859ca8841160542c3021fe23b0b3efd37:
- File
testutil/server_unix_test.go
— part of check-in
[bb864e6c58]
at
2026-10-13 00:48:23
on branch main
— fix(test): kill fossil server's process group before cleanup (#352) (#365)
Start fossil server in its own process group through a shared testutil.StartServer helper and SIGKILL the whole group before Wait, so forked per-connection children cannot write into t.TempDir() during cleanup. Tracked in #352. (user: [email protected], size: 1351) [annotate] [blame] [check-ins using]
//go:build unix package testutil import ( "os" "path/filepath" "strconv" "strings" "syscall" "testing" "time" ) // A forked child must not outlive StartServer's cleanup (#352). func TestStartServerKillsForkedChildren(t *testing.T) { pidFile := filepath.Join(t.TempDir(), "child.pid") t.Run("server", func(t *testing.T) { script := `sleep 300 & echo $! > "$1"; wait` StartServer(t, "/bin/sh", "-c", script, "sh", pidFile) waitForFile(t, pidFile) }) raw, err := os.ReadFile(pidFile) if err != nil { t.Fatalf("read child pid: %v", err) } pid, err := strconv.Atoi(strings.TrimSpace(string(raw))) if err != nil { t.Fatalf("parse child pid %q: %v", raw, err) } // Signal 0 probes existence; SIGKILL delivery is asynchronous, so poll. deadline := time.Now().Add(5 * time.Second) for syscall.Kill(pid, 0) == nil { if time.Now().After(deadline) { _ = syscall.Kill(pid, syscall.SIGKILL) t.Fatalf("forked child %d outlived the server cleanup", pid) } time.Sleep(10 * time.Millisecond) } } func waitForFile(t *testing.T, path string) { t.Helper() deadline := time.Now().Add(5 * time.Second) for { if b, err := os.ReadFile(path); err == nil && strings.TrimSpace(string(b)) != "" { return } if time.Now().After(deadline) { t.Fatalf("%s never appeared", path) } time.Sleep(10 * time.Millisecond) } }