test(progress): read the log buffer safely while the heartbeat writes to it

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-02 08:50:39 +02:00
co-authored by Claude Sonnet 5.5
parent 4bfe578a05
commit 8ec43ad393
+21 -4
View File
@@ -1,7 +1,6 @@
package progress_test package progress_test
import ( import (
"bytes"
"context" "context"
"errors" "errors"
"log/slog" "log/slog"
@@ -60,20 +59,38 @@ func (f *fakeSink) beatCount() int {
return f.beats 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. // clock is a hand-wound clock so durations and throttling are exact.
type clock struct{ t time.Time } type clock struct{ t time.Time }
func (c *clock) now() time.Time { return c.t } func (c *clock) now() time.Time { return c.t }
func (c *clock) tick(d time.Duration) { c.t = c.t.Add(d) } 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 { func plan() []progress.Planned {
return []progress.Planned{{Key: "audio", Label: "Preparing audio"}, {Key: "decide", Label: "Deciding"}, {Key: "save", Label: "Saving"}} 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() 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})) 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 return progress.NewTracker(context.Background(), sink, log, plan(), progress.WithClock(clk.now), progress.WithMinSave(2*time.Second)), sink, clk, buf
} }