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:
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user