-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy patherrors_test.go
157 lines (139 loc) · 3.42 KB
/
errors_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package serrors_test
import (
"errors"
"runtime"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/morningconsult/serrors"
)
func Test_stackErr(t *testing.T) {
e := serrors.New("new err")
se := serrors.WithStack(e)
want := traceLine(15)
got := traceError(t, se)
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("stacktrace differs:\n%s", diff)
}
se2 := serrors.WithStack(se)
if !errors.Is(se2, se) {
t.Error("wrapping is not a noop")
}
}
func TestSentinel(t *testing.T) {
const msg = "This is a constant error"
const s = serrors.Sentinel(msg)
if s.Error() != msg {
t.Errorf("want error %q, got %q", msg, s.Error())
}
}
func TestNew(t *testing.T) {
err := serrors.New("test message")
expected := traceLine(39)
result := traceError(t, err)
if diff := cmp.Diff(expected, result); diff != "" {
t.Errorf("stacktrace differs\n%s", diff)
}
}
func TestErrorf(t *testing.T) {
data := []struct {
name string
formatString string
values []interface{}
wantMessage string
wantTrace string
}{
{
"wrapped non-stack trace error",
"this is an %s: %w",
[]interface{}{"error", errors.New("inner error")},
"this is an error: inner error",
traceLine(80),
},
{
"wrapped stack trace error",
"this is an %s: %w",
[]interface{}{"error", serrors.New("inner error")},
"this is an error: inner error",
traceLine(65),
},
{
"no error",
"this is an %s",
[]interface{}{"error"},
"this is an error",
traceLine(80),
},
}
for _, v := range data {
// produce the error outside the anon function below so we get the test
// as the caller and not test.func1
errOuter := serrors.Errorf(v.formatString, v.values...)
t.Run(v.name, func(t *testing.T) {
result := traceError(t, errOuter)
if diff := cmp.Diff(v.wantTrace, result); diff != "" {
t.Errorf("stacktrace differs\n%s", diff)
}
})
}
}
func TestSentinelComparisons(t *testing.T) {
const s = serrors.Sentinel("This is a constant error")
err := s
if err != s {
t.Errorf("should be the same")
}
if !errors.Is(err, s) {
t.Errorf("should be the same")
}
err2 := serrors.Errorf("Wrapping error: %w", s)
if !errors.Is(err2, s) {
t.Errorf("should be there")
}
}
func Test_stackErr_Is(t *testing.T) {
err := serrors.New("foo")
if !errors.Is(err, err) {
t.Error("oops")
}
stdErr := errors.New("bar")
wrappedErr := serrors.WithStack(stdErr)
if !errors.Is(wrappedErr, stdErr) {
t.Error("oops")
}
}
func TestWithStackNil(t *testing.T) {
if serrors.WithStack(nil) != nil {
t.Error("Got non-nil for nil passed to WithStack")
}
}
// traceLine formats a stack trace message based on the provided line for the
// test, using the actual outside callers of the test.
func traceLine(expectedLine int) string {
pcs := make([]uintptr, 100)
// start at 2, skipping runtime.Callers and this function
n := runtime.Callers(2, pcs)
frames := runtime.CallersFrames(pcs[:n])
str := strings.Builder{}
frame, _ := frames.Next()
frame.Line = expectedLine
_ = serrors.PanicFormat.Execute(&str, frame)
str.WriteByte('\n')
for {
frame, more := frames.Next()
_ = serrors.PanicFormat.Execute(&str, frame)
if !more {
break
}
str.WriteByte('\n')
}
return str.String()
}
// traceError formats a stack trace from an error.
func traceError(t *testing.T, err error) string {
lines, err := serrors.Trace(err, serrors.PanicFormat)
if err != nil {
t.Fatal(err)
}
return strings.Join(lines, "\n")
}