Skip to content

Commit

Permalink
feat: ignore context.Canceled by default in slogtest (#207)
Browse files Browse the repository at this point in the history
* feat: ignore context.Canceled by default in slogtest

Signed-off-by: Spike Curtis <[email protected]>

* code review suggestions

Signed-off-by: Spike Curtis <[email protected]>

---------

Signed-off-by: Spike Curtis <[email protected]>
  • Loading branch information
spikecurtis authored Jan 26, 2024
1 parent f0c466f commit 20367d4
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 3 deletions.
39 changes: 36 additions & 3 deletions sloggers/slogtest/t.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
"sync"
"testing"

"golang.org/x/xerrors"

"cdr.dev/slog"
"cdr.dev/slog/internal/entryhuman"
"cdr.dev/slog/sloggers/sloghuman"
Expand All @@ -36,13 +38,26 @@ type Options struct {
// conditions exist when t.Log is called concurrently of a test exiting. Set
// to true if you don't need this behavior.
SkipCleanup bool
// IgnoredErrorIs causes the test logger not to error the test on Error
// if the SinkEntry contains one of the listed errors in its "error" Field.
// Errors are matched using xerrors.Is().
//
// By default, context.Canceled and context.DeadlineExceeded are included,
// as these are nearly always benign in testing. Override to []error{} (zero
// length error slice) to disable the whitelist entirely.
IgnoredErrorIs []error
}

// Make creates a Logger that writes logs to tb in a human readable format.
var DefaultIgnoredErrorIs = []error{context.Canceled, context.DeadlineExceeded}

// Make creates a Logger that writes logs to tb in a human-readable format.
func Make(tb testing.TB, opts *Options) slog.Logger {
if opts == nil {
opts = &Options{}
}
if opts.IgnoredErrorIs == nil {
opts.IgnoredErrorIs = DefaultIgnoredErrorIs
}

sink := &testSink{
tb: tb,
Expand All @@ -66,7 +81,7 @@ type testSink struct {
testDone bool
}

func (ts *testSink) LogEntry(ctx context.Context, ent slog.SinkEntry) {
func (ts *testSink) LogEntry(_ context.Context, ent slog.SinkEntry) {
ts.mu.RLock()
defer ts.mu.RUnlock()

Expand All @@ -83,7 +98,7 @@ func (ts *testSink) LogEntry(ctx context.Context, ent slog.SinkEntry) {
case slog.LevelDebug, slog.LevelInfo, slog.LevelWarn:
ts.tb.Log(sb.String())
case slog.LevelError, slog.LevelCritical:
if ts.opts.IgnoreErrors {
if ts.shouldIgnoreError(ent) {
ts.tb.Log(sb.String())
} else {
sb.WriteString(fmt.Sprintf(
Expand All @@ -98,6 +113,24 @@ func (ts *testSink) LogEntry(ctx context.Context, ent slog.SinkEntry) {
}
}

func (ts *testSink) shouldIgnoreError(ent slog.SinkEntry) bool {
if ts.opts.IgnoreErrors {
return true
}
for _, f := range ent.Fields {
if f.Name == "error" {
if err, ok := f.Value.(error); ok {
for _, ig := range ts.opts.IgnoredErrorIs {
if xerrors.Is(err, ig) {
return true
}
}
}
}
}
return false
}

func (ts *testSink) Sync() {}

var ctx = context.Background()
Expand Down
63 changes: 63 additions & 0 deletions sloggers/slogtest/t_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import (
"context"
"testing"

"golang.org/x/xerrors"

"cdr.dev/slog"
"cdr.dev/slog/internal/assert"
"cdr.dev/slog/sloggers/slogtest"
)
Expand All @@ -15,6 +18,12 @@ func TestStateless(t *testing.T) {
slogtest.Debug(tb, "hello")
slogtest.Info(tb, "hello")

slogtest.Error(tb, "canceled", slog.Error(xerrors.Errorf("test %w:", context.Canceled)))
assert.Equal(t, "errors", 0, tb.errors)

slogtest.Error(tb, "deadline", slog.Error(xerrors.Errorf("test %w:", context.DeadlineExceeded)))
assert.Equal(t, "errors", 0, tb.errors)

slogtest.Error(tb, "hello")
assert.Equal(t, "errors", 1, tb.errors)

Expand Down Expand Up @@ -45,6 +54,60 @@ func TestIgnoreErrors(t *testing.T) {
l.Fatal(bg, "hello")
}

func TestIgnoreErrorIs_Default(t *testing.T) {
t.Parallel()

tb := &fakeTB{}
l := slogtest.Make(tb, nil)

l.Error(bg, "canceled", slog.Error(xerrors.Errorf("test %w:", context.Canceled)))
assert.Equal(t, "errors", 0, tb.errors)

l.Error(bg, "deadline", slog.Error(xerrors.Errorf("test %w:", context.DeadlineExceeded)))
assert.Equal(t, "errors", 0, tb.errors)

l.Error(bg, "new", slog.Error(xerrors.New("test")))
assert.Equal(t, "errors", 1, tb.errors)

defer func() {
recover()
assert.Equal(t, "fatals", 1, tb.fatals)
}()

l.Fatal(bg, "hello", slog.Error(xerrors.Errorf("fatal %w:", context.Canceled)))
}

func TestIgnoreErrorIs_Explicit(t *testing.T) {
t.Parallel()

tb := &fakeTB{}
ignored := xerrors.New("ignored")
notIgnored := xerrors.New("not ignored")
l := slogtest.Make(tb, &slogtest.Options{IgnoredErrorIs: []error{ignored}})

l.Error(bg, "ignored", slog.Error(xerrors.Errorf("test %w:", ignored)))
assert.Equal(t, "errors", 0, tb.errors)

l.Error(bg, "not ignored", slog.Error(xerrors.Errorf("test %w:", notIgnored)))
assert.Equal(t, "errors", 1, tb.errors)

l.Error(bg, "canceled", slog.Error(xerrors.Errorf("test %w:", context.Canceled)))
assert.Equal(t, "errors", 2, tb.errors)

l.Error(bg, "deadline", slog.Error(xerrors.Errorf("test %w:", context.DeadlineExceeded)))
assert.Equal(t, "errors", 3, tb.errors)

l.Error(bg, "new", slog.Error(xerrors.New("test")))
assert.Equal(t, "errors", 4, tb.errors)

defer func() {
recover()
assert.Equal(t, "fatals", 1, tb.fatals)
}()

l.Fatal(bg, "hello", slog.Error(xerrors.Errorf("test %w:", ignored)))
}

func TestCleanup(t *testing.T) {
t.Parallel()

Expand Down

0 comments on commit 20367d4

Please sign in to comment.