Files
coder/testutil/fake_sink.go
T
Cian Johnston 65b7658568 chore: extract testutil.FakeSink for slog test assertions (#23208)
Follow-up to [review comment on
#23025](https://github.com/coder/coder/pull/23025#discussion_r2930309487)
from @mafredri.

Extracts the repeated `logSink` / `fakeSink` test pattern into a shared
`testutil.FakeSink` and migrates all existing call sites.

> 🤖 This PR was created with the help of Coder Agents, and will be
reviewed by my human. 🧑‍💻

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-03-18 17:02:38 +00:00

77 lines
1.8 KiB
Go

package testutil
import (
"context"
"sync"
"testing"
"cdr.dev/slog/v3"
)
// FakeSink is a thread-safe slog.Sink that captures log entries so
// tests can assert on what was logged. It requires a testing.TB
// as it is only meant for use in tests.
type FakeSink struct {
mu sync.RWMutex
entries []slog.SinkEntry
}
// NewFakeSink returns a FakeSink ready for use.
func NewFakeSink(_ testing.TB) *FakeSink {
return &FakeSink{}
}
// LogEntry implements slog.Sink. It appends the entry to the
// internal slice.
func (s *FakeSink) LogEntry(_ context.Context, e slog.SinkEntry) {
s.mu.Lock()
defer s.mu.Unlock()
s.entries = append(s.entries, e)
}
// Sync implements slog.Sink.
func (*FakeSink) Sync() {}
// Entries returns a copy of the captured entries. If filters are
// provided, only entries matching ALL filters are returned. This
// lets callers compose simple predicates instead of needing
// dedicated methods for each field.
func (s *FakeSink) Entries(filters ...func(slog.SinkEntry) bool) []slog.SinkEntry {
s.mu.RLock()
cpy := make([]slog.SinkEntry, len(s.entries))
copy(cpy, s.entries)
s.mu.RUnlock()
filtered := make([]slog.SinkEntry, 0)
for _, e := range cpy {
if !matchAll(e, filters) {
continue
}
filtered = append(filtered, e)
}
return filtered
}
// Logger returns a slog.Logger backed by this sink at the given
// level. If no level is provided it defaults to LevelDebug, which
// captures everything. If more than one level is provided, the
// first one wins.
func (s *FakeSink) Logger(level ...slog.Level) slog.Logger {
l := slog.LevelDebug
if len(level) > 0 {
l = level[0]
}
return slog.Make(s).Leveled(l)
}
func matchAll(e slog.SinkEntry, filters []func(slog.SinkEntry) bool) bool {
for _, f := range filters {
if f == nil {
continue
}
if !f(e) {
return false
}
}
return true
}