From a1e4a7c944bb75fe3c2adb47de137985b5eaa7c8 Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Tue, 26 May 2026 11:25:48 -0400 Subject: [PATCH] fix(backend): fix data race in tests using captureDebugLogs The captureDebugLogs function was using a plain bytes.Buffer which is not safe for concurrent access. When app.New spawns goroutines that log asynchronously (e.g., mcp.Initialize), the test would read from the buffer while goroutines were still writing to it, causing a data race. This change introduces a syncBuffer type that wraps bytes.Buffer with a mutex, making it safe for concurrent reads and writes. --- internal/backend/backend_test.go | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/internal/backend/backend_test.go b/internal/backend/backend_test.go index 8ee69c9e574bda73351a3ddaf1b10fafa36a8ec7..ee1165dfba5c27e2f021ba2e834a99b4d3a769e5 100644 --- a/internal/backend/backend_test.go +++ b/internal/backend/backend_test.go @@ -642,17 +642,36 @@ func protoWS(path, dataDir, clientID string) proto.Workspace { return proto.Workspace{Path: path, DataDir: dataDir, ClientID: clientID} } +// syncBuffer is a thread-safe buffer that can be safely read and written +// from multiple goroutines. +type syncBuffer struct { + mu sync.Mutex + buf bytes.Buffer +} + +func (sb *syncBuffer) Write(p []byte) (n int, err error) { + sb.mu.Lock() + defer sb.mu.Unlock() + return sb.buf.Write(p) +} + +func (sb *syncBuffer) String() string { + sb.mu.Lock() + defer sb.mu.Unlock() + return sb.buf.String() +} + // captureDebugLogs installs a buffer-backed slog handler at Debug // level for the duration of the test, returning the buffer. The // previous default handler is restored via t.Cleanup. -func captureDebugLogs(t *testing.T) *bytes.Buffer { +func captureDebugLogs(t *testing.T) *syncBuffer { t.Helper() - var buf bytes.Buffer + var sb syncBuffer prev := slog.Default() - handler := slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug}) + handler := slog.NewTextHandler(&sb, &slog.HandlerOptions{Level: slog.LevelDebug}) slog.SetDefault(slog.New(handler)) t.Cleanup(func() { slog.SetDefault(prev) }) - return &buf + return &sb } // xdgIsolated points HOME and XDG_* variables at fresh tempdirs so