|
1
|
package is_test |
|
2
|
|
|
3
|
import ( |
|
4
|
"errors" |
|
5
|
"fmt" |
|
6
|
"io/fs" |
|
7
|
"math/rand/v2" |
|
8
|
"reflect" |
|
9
|
"testing" |
|
10
|
"time" |
|
11
|
|
|
12
|
"olexsmir.xyz/x/is" |
|
13
|
) |
|
14
|
|
|
15
|
func TestEqual(t *testing.T) { |
|
16
|
t.Run("equal", func(t *testing.T) { |
|
17
|
now := time.Now() |
|
18
|
val := 420 |
|
19
|
|
|
20
|
tests := map[string]struct{ got, expected any }{ |
|
21
|
"int": {69, 69}, |
|
22
|
"string": {"hello", "hello"}, |
|
23
|
"bool": {true, true}, |
|
24
|
"struct": {intType{1}, intType{1}}, |
|
25
|
"pointer": {&val, &val}, |
|
26
|
"nil": {nil, nil}, |
|
27
|
"nil pointer": {(*int)(nil), (*int)(nil)}, |
|
28
|
"nil map": {map[string]int(nil), map[string]int(nil)}, |
|
29
|
"nil chan": {(chan int)(nil), (chan int)(nil)}, |
|
30
|
"nil slice": {[]int(nil), []int(nil)}, |
|
31
|
"byte slice": {[]byte("abc"), []byte("abc")}, |
|
32
|
"int slice": {[]int{1, 2}, []int{1, 2}}, |
|
33
|
"string slice": {[]string{"a", "b"}, []string{"a", "b"}}, |
|
34
|
"empty map": {map[string]int{}, map[string]int{}}, |
|
35
|
"map": {map[string]int{"a": 1}, map[string]int{"a": 1}}, |
|
36
|
"time.Time": {now, now}, |
|
37
|
} |
|
38
|
for tname, tt := range tests { |
|
39
|
t.Run(tname, func(t *testing.T) { |
|
40
|
ft := &fakeT{} |
|
41
|
is.Equal(ft, tt.expected, tt.got) |
|
42
|
if ft.failed { |
|
43
|
t.Errorf("failed: %s", ft.msg) |
|
44
|
} |
|
45
|
}) |
|
46
|
} |
|
47
|
}) |
|
48
|
|
|
49
|
t.Run("non-equal", func(t *testing.T) { |
|
50
|
now := time.Now() |
|
51
|
val1, val2 := 69, 420 |
|
52
|
tests := map[string]struct { |
|
53
|
expected, got any |
|
54
|
expectedMsg string |
|
55
|
}{ |
|
56
|
"int": {228, 1337, `expected: 228, got: 1337`}, |
|
57
|
"string": {"hello", "world", `expected: "hello", got: "world"`}, |
|
58
|
"bool": {true, false, "expected: true, got: false"}, |
|
59
|
"struct": {intType{1}, intType{2}, `expected: is_test.intType{v:1}, got: is_test.intType{v:2}`}, |
|
60
|
"pointer": {&val1, &val2, ``}, |
|
61
|
"nil/non-nil": {nil, 2, `expected: <nil>, got: 2`}, |
|
62
|
"non-nil/nil": {2, nil, `expected: 2, got: <nil>`}, |
|
63
|
"nil slice/empty": {[]int(nil), []int{}, `expected: []int(nil), got: []int{}`}, |
|
64
|
"int slice": {[]int{1, 2}, []int{2, 1}, `expected: []int{1, 2}, got: []int{2, 1}`}, |
|
65
|
"byte slice": {[]byte("abc"), []byte("def"), `expected: []byte{0x61, 0x62, 0x63}, got: []byte{0x64, 0x65, 0x66}`}, |
|
66
|
"chan": {make(chan int), make(chan int), ``}, |
|
67
|
"string slice": {[]string{"a", "b"}, []string{"b", "a"}, `expected: []string{"a", "b"}, got: []string{"b", "a"}`}, |
|
68
|
"map": {map[string]int{"a": 1}, map[string]int{"a": 2}, `expected: map[string]int{"a":1}, got: map[string]int{"a":2}`}, |
|
69
|
"time.Time": {now, now.Add(time.Second), ``}, |
|
70
|
} |
|
71
|
|
|
72
|
for tname, tt := range tests { |
|
73
|
t.Run(tname, func(t *testing.T) { |
|
74
|
ft := &fakeT{} |
|
75
|
is.Equal(ft, tt.expected, tt.got) |
|
76
|
shouldHaveFailed(t, ft) |
|
77
|
if tt.expectedMsg != "" { |
|
78
|
expectMsg(t, ft, tt.expectedMsg) |
|
79
|
} |
|
80
|
}) |
|
81
|
} |
|
82
|
}) |
|
83
|
|
|
84
|
t.Run("implements equaler", func(t *testing.T) { |
|
85
|
ft := &fakeT{} |
|
86
|
val1, val2 := newRandomy(1), newRandomy(1) |
|
87
|
is.Equal(ft, val1, val2) |
|
88
|
if ft.failed { |
|
89
|
t.Errorf("%#v == %#v: should have passed", val1, val2) |
|
90
|
} |
|
91
|
}) |
|
92
|
|
|
93
|
t.Run("implements equaler, not empty", func(t *testing.T) { |
|
94
|
ft := &fakeT{} |
|
95
|
val1, val2 := newRandomy(1), newRandomy(2) |
|
96
|
is.Equal(ft, val1, val2) |
|
97
|
shouldHaveFailed(t, ft) |
|
98
|
}) |
|
99
|
|
|
100
|
t.Run("time.Time", func(t *testing.T) { |
|
101
|
ft := &fakeT{} |
|
102
|
val1 := time.Date(2022, 1, 3, 0, 0, 18, 0, time.UTC) |
|
103
|
val2 := time.Date(2022, 1, 3, 5, 0, 18, 0, time.FixedZone("UTC+5", 5*3600)) |
|
104
|
is.Equal(ft, val1, val1) |
|
105
|
if ft.failed { |
|
106
|
t.Errorf("%#v == %#v: should have passed", val1, val2) |
|
107
|
} |
|
108
|
}) |
|
109
|
} |
|
110
|
|
|
111
|
func TestErr(t *testing.T) { |
|
112
|
t.Run("expected nil, got nil", func(t *testing.T) { |
|
113
|
ft := &fakeT{} |
|
114
|
is.Err(ft, nil, nil) |
|
115
|
if ft.failed { |
|
116
|
t.Errorf("failed: %s", ft.msg) |
|
117
|
} |
|
118
|
}) |
|
119
|
|
|
120
|
t.Run("expected nil, got error", func(t *testing.T) { |
|
121
|
ft := &fakeT{} |
|
122
|
is.Err(ft, errors.New("an error"), nil) |
|
123
|
|
|
124
|
shouldBeFatal(t, ft) |
|
125
|
shouldHaveFailed(t, ft) |
|
126
|
expectMsg(t, ft, "unexpected error: an error") |
|
127
|
}) |
|
128
|
|
|
129
|
t.Run("expected error, got nil", func(t *testing.T) { |
|
130
|
ft := &fakeT{} |
|
131
|
is.Err(ft, nil, errors.New("err")) |
|
132
|
|
|
133
|
shouldHaveFailed(t, ft) |
|
134
|
expectMsg(t, ft, `got: <nil>, expected: error`) |
|
135
|
}) |
|
136
|
|
|
137
|
t.Run("expected error, got same error", func(t *testing.T) { |
|
138
|
ft := &fakeT{} |
|
139
|
err := errors.New("an error") |
|
140
|
is.Err(ft, err, err) |
|
141
|
if ft.failed { |
|
142
|
t.Errorf("failed: %s", ft.msg) |
|
143
|
} |
|
144
|
}) |
|
145
|
|
|
146
|
t.Run("expected error, got different error", func(t *testing.T) { |
|
147
|
ft := &fakeT{} |
|
148
|
err1 := errors.New("an error") |
|
149
|
err2 := errors.New("second error") |
|
150
|
is.Err(ft, err1, err2) |
|
151
|
|
|
152
|
shouldHaveFailed(t, ft) |
|
153
|
shouldBeFatal(t, ft) |
|
154
|
expectMsg(t, ft, `expected: *errors.errorString(an error), got: *errors.errorString(second error)`) |
|
155
|
}) |
|
156
|
|
|
157
|
t.Run("expected error, got error of different type", func(t *testing.T) { |
|
158
|
ft := &fakeT{} |
|
159
|
err1 := errors.New("an error") |
|
160
|
err2 := errType("my error") |
|
161
|
is.Err(ft, err1, err2) |
|
162
|
|
|
163
|
shouldHaveFailed(t, ft) |
|
164
|
shouldBeFatal(t, ft) |
|
165
|
expectMsg(t, ft, `expected: *errors.errorString(an error), got: is_test.errType(my error)`) |
|
166
|
}) |
|
167
|
|
|
168
|
t.Run("expected error, got wrapped error", func(t *testing.T) { |
|
169
|
ft := &fakeT{} |
|
170
|
err := errors.New("an error") |
|
171
|
werr := fmt.Errorf("wrapped: %w", err) |
|
172
|
is.Err(ft, werr, err) |
|
173
|
if ft.failed { |
|
174
|
t.Errorf("failed: %s", ft.msg) |
|
175
|
} |
|
176
|
}) |
|
177
|
|
|
178
|
t.Run("got error, string", func(t *testing.T) { |
|
179
|
ft := &fakeT{} |
|
180
|
is.Err(ft, errors.New("test string"), "test string") |
|
181
|
if ft.failed { |
|
182
|
t.Errorf("failed: %s", ft.msg) |
|
183
|
} |
|
184
|
}) |
|
185
|
|
|
186
|
t.Run("got error, partial string", func(t *testing.T) { |
|
187
|
ft := &fakeT{} |
|
188
|
is.Err(ft, errors.New("test string"), "string") |
|
189
|
if ft.failed { |
|
190
|
t.Errorf("failed: %s", ft.msg) |
|
191
|
} |
|
192
|
}) |
|
193
|
|
|
194
|
t.Run("got error, not containing string", func(t *testing.T) { |
|
195
|
ft := &fakeT{} |
|
196
|
is.Err(ft, errors.New("test string"), "asdfasdf") |
|
197
|
|
|
198
|
shouldHaveFailed(t, ft) |
|
199
|
shouldBeFatal(t, ft) |
|
200
|
expectMsg(t, ft, `expected: "test string", got: "asdfasdf"`) |
|
201
|
}) |
|
202
|
|
|
203
|
t.Run("got custom type, same types", func(t *testing.T) { |
|
204
|
ft := &fakeT{} |
|
205
|
err := errType("custom error") |
|
206
|
is.Err(ft, err, err) |
|
207
|
if ft.failed { |
|
208
|
t.Errorf("failed: %s", ft.msg) |
|
209
|
} |
|
210
|
}) |
|
211
|
|
|
212
|
t.Run("got custom type, different types", func(t *testing.T) { |
|
213
|
ft := &fakeT{} |
|
214
|
err := errType("custom error") |
|
215
|
is.Err(ft, err, reflect.TypeFor[*fs.PathError]()) |
|
216
|
shouldHaveFailed(t, ft) |
|
217
|
shouldBeFatal(t, ft) |
|
218
|
expectMsg(t, ft, `expected: *fs.PathError, got: is_test.errType`) |
|
219
|
}) |
|
220
|
|
|
221
|
t.Run("unexpected type", func(t *testing.T) { |
|
222
|
ft := &fakeT{} |
|
223
|
is.Err(ft, errors.New("asdf"), 0) |
|
224
|
shouldHaveFailed(t, ft) |
|
225
|
shouldBeFatal(t, ft) |
|
226
|
expectMsg(t, ft, `unexpected type: int`) |
|
227
|
}) |
|
228
|
} |
|
229
|
|
|
230
|
func expectMsg(t *testing.T, ft *fakeT, expectedMsg string) { |
|
231
|
t.Helper() |
|
232
|
if ft.msg != expectedMsg { |
|
233
|
t.Errorf("expected msg: %q; got: %q", expectedMsg, ft.msg) |
|
234
|
} |
|
235
|
} |
|
236
|
|
|
237
|
func shouldBeFatal(t *testing.T, ft *fakeT) { |
|
238
|
t.Helper() |
|
239
|
if !ft.fatal { |
|
240
|
t.Error("should be fatal") |
|
241
|
} |
|
242
|
} |
|
243
|
|
|
244
|
func shouldHaveFailed(t *testing.T, ft *fakeT) { |
|
245
|
t.Helper() |
|
246
|
if !ft.failed { |
|
247
|
t.Error("should have failed") |
|
248
|
} |
|
249
|
} |
|
250
|
|
|
251
|
// helper types --- |
|
252
|
|
|
253
|
type intType struct{ v int } |
|
254
|
|
|
255
|
type randomy struct { |
|
256
|
val int |
|
257
|
rnd float64 |
|
258
|
} |
|
259
|
|
|
260
|
func newRandomy(val int) randomy { return randomy{val, rand.Float64()} } |
|
261
|
func (n randomy) Equal(other randomy) bool { return n.val == other.val } |
|
262
|
|
|
263
|
type errType string |
|
264
|
|
|
265
|
func (e errType) Error() string { return string(e) } |
|
266
|
|
|
267
|
// mockT ---- |
|
268
|
|
|
269
|
type fakeT struct { |
|
270
|
testing.TB |
|
271
|
failed bool |
|
272
|
fatal bool |
|
273
|
msg string |
|
274
|
} |
|
275
|
|
|
276
|
func (m *fakeT) Helper() {} |
|
277
|
|
|
278
|
func (m *fakeT) Log(a ...any) { fmt.Println(a...) } |
|
279
|
|
|
280
|
func (m *fakeT) Fatal(args ...any) { |
|
281
|
m.fatal = true |
|
282
|
m.Error(args...) |
|
283
|
} |
|
284
|
|
|
285
|
func (m *fakeT) Fatalf(format string, args ...any) { |
|
286
|
m.fatal = true |
|
287
|
m.Errorf(format, args...) |
|
288
|
} |
|
289
|
|
|
290
|
func (m *fakeT) Error(args ...any) { |
|
291
|
m.failed = true |
|
292
|
m.msg = fmt.Sprint(args...) |
|
293
|
} |
|
294
|
|
|
295
|
func (m *fakeT) Errorf(format string, args ...any) { |
|
296
|
m.failed = true |
|
297
|
m.msg = fmt.Sprintf(format, args...) |
|
298
|
} |