Skip to content

Commit 0ec81e6

Browse files
authored
feat: add IgnoreErrorFn to slogtest options (#217)
Signed-off-by: Spike Curtis <spike@coder.com>
1 parent 3e5cea5 commit 0ec81e6

File tree

2 files changed

+76
-8
lines changed

2 files changed

+76
-8
lines changed

sloggers/slogtest/t.go

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ type Options struct {
4646
// as these are nearly always benign in testing. Override to []error{} (zero
4747
// length error slice) to disable the whitelist entirely.
4848
IgnoredErrorIs []error
49+
// IgnoreErrorFn, if non-nil, defines a function that should return true if
50+
// the given SinkEntry should not error the test on Error or Critical. The
51+
// result of this function is logically ORed with ignore directives defined
52+
// by IgnoreErrors and IgnoredErrorIs. To depend exclusively on
53+
// IgnoreErrorFn, set IgnoreErrors=false and IgnoredErrorIs=[]error{} (zero
54+
// length error slice).
55+
IgnoreErrorFn func(slog.SinkEntry) bool
4956
}
5057

5158
var DefaultIgnoredErrorIs = []error{context.Canceled, context.DeadlineExceeded}
@@ -117,17 +124,16 @@ func (ts *testSink) shouldIgnoreError(ent slog.SinkEntry) bool {
117124
if ts.opts.IgnoreErrors {
118125
return true
119126
}
120-
for _, f := range ent.Fields {
121-
if f.Name == "error" {
122-
if err, ok := f.Value.(error); ok {
123-
for _, ig := range ts.opts.IgnoredErrorIs {
124-
if xerrors.Is(err, ig) {
125-
return true
126-
}
127-
}
127+
if err, ok := FindFirstError(ent); ok {
128+
for _, ig := range ts.opts.IgnoredErrorIs {
129+
if xerrors.Is(err, ig) {
130+
return true
128131
}
129132
}
130133
}
134+
if ts.opts.IgnoreErrorFn != nil {
135+
return ts.opts.IgnoreErrorFn(ent)
136+
}
131137
return false
132138
}
133139

@@ -162,3 +168,16 @@ func Fatal(t testing.TB, msg string, fields ...any) {
162168
slog.Helper()
163169
l(t).Fatal(ctx, msg, fields...)
164170
}
171+
172+
// FindFirstError finds the first slog.Field named "error" that contains an
173+
// error value.
174+
func FindFirstError(ent slog.SinkEntry) (err error, ok bool) {
175+
for _, f := range ent.Fields {
176+
if f.Name == "error" {
177+
if err, ok = f.Value.(error); ok {
178+
return err, true
179+
}
180+
}
181+
}
182+
return nil, false
183+
}

sloggers/slogtest/t_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package slogtest_test
22

33
import (
44
"context"
5+
"fmt"
56
"testing"
67

78
"golang.org/x/xerrors"
@@ -108,6 +109,46 @@ func TestIgnoreErrorIs_Explicit(t *testing.T) {
108109
l.Fatal(bg, "hello", slog.Error(xerrors.Errorf("test %w:", ignored)))
109110
}
110111

112+
func TestIgnoreErrorFn(t *testing.T) {
113+
t.Parallel()
114+
115+
tb := &fakeTB{}
116+
ignored := testCodedError{code: 777}
117+
notIgnored := testCodedError{code: 911}
118+
l := slogtest.Make(tb, &slogtest.Options{IgnoreErrorFn: func(ent slog.SinkEntry) bool {
119+
err, ok := slogtest.FindFirstError(ent)
120+
if !ok {
121+
t.Error("did not contain an error")
122+
return false
123+
}
124+
ce := testCodedError{}
125+
if !xerrors.As(err, &ce) {
126+
return false
127+
}
128+
return ce.code != 911
129+
}})
130+
131+
l.Error(bg, "ignored", slog.Error(xerrors.Errorf("test %w:", ignored)))
132+
assert.Equal(t, "errors", 0, tb.errors)
133+
134+
l.Error(bg, "not ignored", slog.Error(xerrors.Errorf("test %w:", notIgnored)))
135+
assert.Equal(t, "errors", 1, tb.errors)
136+
137+
// still ignored by default for IgnoredErrorIs
138+
l.Error(bg, "canceled", slog.Error(xerrors.Errorf("test %w:", context.Canceled)))
139+
assert.Equal(t, "errors", 1, tb.errors)
140+
141+
l.Error(bg, "new", slog.Error(xerrors.New("test")))
142+
assert.Equal(t, "errors", 2, tb.errors)
143+
144+
defer func() {
145+
recover()
146+
assert.Equal(t, "fatals", 1, tb.fatals)
147+
}()
148+
149+
l.Fatal(bg, "hello", slog.Error(xerrors.Errorf("test %w:", ignored)))
150+
}
151+
111152
func TestCleanup(t *testing.T) {
112153
t.Parallel()
113154

@@ -163,3 +204,11 @@ func (tb *fakeTB) Fatal(v ...interface{}) {
163204
func (tb *fakeTB) Cleanup(fn func()) {
164205
tb.cleanups = append(tb.cleanups, fn)
165206
}
207+
208+
type testCodedError struct {
209+
code int
210+
}
211+
212+
func (e testCodedError) Error() string {
213+
return fmt.Sprintf("code: %d", e.code)
214+
}

0 commit comments

Comments
 (0)