From f55b6cb2ccefa312e325fbf7a5568d1fc36c20c6 Mon Sep 17 00:00:00 2001 From: kiennt048 Date: Mon, 9 Nov 2026 22:13:33 +0700 Subject: [PATCH] test(cmd/vedette): wait for run to return before the temp dir is removed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit start and startArgs cancelled run at cleanup but never waited for it, so t.TempDir's RemoveAll raced the shutdown's last writes (CI on #82: "TempDir RemoveAll cleanup: unlinkat …/001: directory not empty" in TestLoopbackHTTPHostAllowlist). waitStopped cancels and waits (30 s bound) in a cleanup registered after the temp dir, so it runs first. Co-Authored-By: Claude Opus 5.5 --- cmd/vedette/main_test.go | 19 ++++++++++++++++++- cmd/vedette/tls_test.go | 5 +++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/cmd/vedette/main_test.go b/cmd/vedette/main_test.go index 439b043..6e94b2c 100644 --- a/cmd/vedette/main_test.go +++ b/cmd/vedette/main_test.go @@ -65,12 +65,14 @@ func start(t *testing.T) *instance { in := &instance{db: filepath.Join(dir, "v.db"), key: filepath.Join(dir, "v.key"), done: make(chan error, 1)} var ctx context.Context ctx, in.cancel = context.WithCancel(t.Context()) - t.Cleanup(in.cancel) pr, pw := io.Pipe() + stopped := make(chan struct{}) go func() { + defer close(stopped) in.done <- run(ctx, []string{"-db", in.db, "-key", in.key, "-listen", "127.0.0.1:0"}, io.Discard, pw) _ = pw.Close() }() + waitStopped(t, in.cancel, stopped) sc := bufio.NewScanner(pr) for sc.Scan() { var line struct { @@ -90,6 +92,21 @@ func start(t *testing.T) *instance { return nil } +// waitStopped registers a cleanup that cancels the process and waits until run has returned. It must be +// registered after t.TempDir: cleanups run last-in first-out, so the temp dir is removed only once nothing +// writes into it any more (CI saw "TempDir RemoveAll cleanup: directory not empty" while run was shutting down). +func waitStopped(t *testing.T, cancel context.CancelFunc, stopped <-chan struct{}) { + t.Helper() + t.Cleanup(func() { + cancel() + select { + case <-stopped: + case <-time.After(30 * time.Second): + t.Error("run did not return within 30 s of cancel") + } + }) +} + func (in *instance) get(t *testing.T, path string) (int, string) { t.Helper() req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, "http://"+in.addr+path, nil) diff --git a/cmd/vedette/tls_test.go b/cmd/vedette/tls_test.go index 9313a32..664d42e 100644 --- a/cmd/vedette/tls_test.go +++ b/cmd/vedette/tls_test.go @@ -139,11 +139,12 @@ func startArgs(t *testing.T, dir string, extra ...string) (addr string, tap *log t.Helper() var ctx context.Context ctx, cancel = context.WithCancel(t.Context()) - t.Cleanup(cancel) tap, done = &logTap{}, make(chan error, 1) pr, pw := io.Pipe() args := append([]string{"-db", filepath.Join(dir, "v.db"), "-key", filepath.Join(dir, "v.key")}, extra...) - go func() { done <- run(ctx, args, io.Discard, pw); _ = pw.Close() }() + stopped := make(chan struct{}) + go func() { defer close(stopped); done <- run(ctx, args, io.Discard, pw); _ = pw.Close() }() + waitStopped(t, cancel, stopped) sc := bufio.NewScanner(pr) for sc.Scan() { tap.add(sc.Text())