test(server): synchronize startup watchdog log capture

TestStartupWatchdogLogsPhaseAndStack polls a bytes.Buffer while the watchdog goroutine writes the same buffer through zerolog, so 'go test -race' reports a read/write data race at service_health_test.go:134 and fails the rest-0 backend shard. Capture the log through a mutex-guarded buffer so the poll and the watchdog write are serialized.

Change-source: pulse-maintainer
This commit is contained in:
pulse-triage[bot]
2026-09-21 09:25:24 +01:00
parent 1b422dd1a6
commit 4d2ee8666c
+21 -1
View File
@@ -6,6 +6,7 @@ import (
"net"
"net/http"
"strings"
"sync"
"testing"
"time"
@@ -118,12 +119,31 @@ func TestFrontendAssetPathsStayLocalAndBounded(t *testing.T) {
}
}
// syncBuffer is a goroutine-safe bytes.Buffer for capturing watchdog log output
// written from another goroutine while the test polls it.
type syncBuffer struct {
mu sync.Mutex
buf bytes.Buffer
}
func (b *syncBuffer) Write(p []byte) (int, error) {
b.mu.Lock()
defer b.mu.Unlock()
return b.buf.Write(p)
}
func (b *syncBuffer) String() string {
b.mu.Lock()
defer b.mu.Unlock()
return b.buf.String()
}
// A bound-but-not-serving listener is otherwise undiagnosable: nothing accepts
// connections and no startup log line is emitted while a synchronous step
// stalls. The watchdog must name the last completed phase and dump the local
// goroutine stacks so a recurrence can be read from the field log.
func TestStartupWatchdogLogsPhaseAndStack(t *testing.T) {
var buf bytes.Buffer
var buf syncBuffer
phase := &startupPhase{}
phase.mark("config watcher started")