From 8ec43ad3934790836a0e38b9a75f5730271aece6 Mon Sep 17 00:00:00 2001 From: Mathias Date: Fri, 2 Oct 2026 08:50:39 +0200 Subject: [PATCH] test(progress): read the log buffer safely while the heartbeat writes to it Co-Authored-By: Claude Sonnet 5.5 --- internal/progress/tracker_test.go | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/internal/progress/tracker_test.go b/internal/progress/tracker_test.go index d88a88f..f7e4928 100644 --- a/internal/progress/tracker_test.go +++ b/internal/progress/tracker_test.go @@ -1,7 +1,6 @@ package progress_test import ( - "bytes" "context" "errors" "log/slog" @@ -60,20 +59,38 @@ func (f *fakeSink) beatCount() int { return f.beats } +// syncBuf is a log sink safe to read while the heartbeat goroutine is still writing. +type syncBuf struct { + mu sync.Mutex + b strings.Builder +} + +func (s *syncBuf) Write(p []byte) (int, error) { + s.mu.Lock() + defer s.mu.Unlock() + return s.b.Write(p) +} + +func (s *syncBuf) String() string { + s.mu.Lock() + defer s.mu.Unlock() + return s.b.String() +} + // clock is a hand-wound clock so durations and throttling are exact. type clock struct{ t time.Time } func (c *clock) now() time.Time { return c.t } func (c *clock) tick(d time.Duration) { c.t = c.t.Add(d) } -func newClock() *clock { return &clock{t: time.Date(2026, 10, 2, 8, 0, 0, 0, time.UTC)} } +func newClock() *clock { return &clock{t: time.Date(2026, 10, 2, 8, 0, 0, 0, time.UTC)} } func plan() []progress.Planned { return []progress.Planned{{Key: "audio", Label: "Preparing audio"}, {Key: "decide", Label: "Deciding"}, {Key: "save", Label: "Saving"}} } -func newTracker(t *testing.T) (*progress.Tracker, *fakeSink, *clock, *bytes.Buffer) { +func newTracker(t *testing.T) (*progress.Tracker, *fakeSink, *clock, *syncBuf) { t.Helper() - sink, clk, buf := &fakeSink{}, newClock(), &bytes.Buffer{} + sink, clk, buf := &fakeSink{}, newClock(), &syncBuf{} log := slog.New(slog.NewTextHandler(buf, &slog.HandlerOptions{Level: slog.LevelDebug})) return progress.NewTracker(context.Background(), sink, log, plan(), progress.WithClock(clk.now), progress.WithMinSave(2*time.Second)), sink, clk, buf }