1
16
17 package errors
18
19 import (
20 "errors"
21 "fmt"
22 "reflect"
23 "sort"
24 "testing"
25 )
26
27 func TestEmptyAggregate(t *testing.T) {
28 var slice []error
29 var agg Aggregate
30 var err error
31
32 agg = NewAggregate(slice)
33 if agg != nil {
34 t.Errorf("expected nil, got %#v", agg)
35 }
36 err = NewAggregate(slice)
37 if err != nil {
38 t.Errorf("expected nil, got %#v", err)
39 }
40
41
42 agg = aggregate(slice)
43 if s := agg.Error(); s != "" {
44 t.Errorf("expected empty string, got %q", s)
45 }
46 if s := agg.Errors(); len(s) != 0 {
47 t.Errorf("expected empty slice, got %#v", s)
48 }
49 err = agg.(error)
50 if s := err.Error(); s != "" {
51 t.Errorf("expected empty string, got %q", s)
52 }
53 }
54
55 func TestAggregateWithNil(t *testing.T) {
56 var slice []error
57 slice = []error{nil}
58 var agg Aggregate
59 var err error
60
61 agg = NewAggregate(slice)
62 if agg != nil {
63 t.Errorf("expected nil, got %#v", agg)
64 }
65 err = NewAggregate(slice)
66 if err != nil {
67 t.Errorf("expected nil, got %#v", err)
68 }
69
70
71 slice = append(slice, fmt.Errorf("err"))
72 agg = NewAggregate(slice)
73 if agg == nil {
74 t.Errorf("expected non-nil")
75 }
76 if s := agg.Error(); s != "err" {
77 t.Errorf("expected 'err', got %q", s)
78 }
79 if s := agg.Errors(); len(s) != 1 {
80 t.Errorf("expected one-element slice, got %#v", s)
81 }
82 if s := agg.Errors()[0].Error(); s != "err" {
83 t.Errorf("expected 'err', got %q", s)
84 }
85
86 err = agg.(error)
87 if err == nil {
88 t.Errorf("expected non-nil")
89 }
90 if s := err.Error(); s != "err" {
91 t.Errorf("expected 'err', got %q", s)
92 }
93 }
94
95 func TestSingularAggregate(t *testing.T) {
96 var slice = []error{fmt.Errorf("err")}
97 var agg Aggregate
98 var err error
99
100 agg = NewAggregate(slice)
101 if agg == nil {
102 t.Errorf("expected non-nil")
103 }
104 if s := agg.Error(); s != "err" {
105 t.Errorf("expected 'err', got %q", s)
106 }
107 if s := agg.Errors(); len(s) != 1 {
108 t.Errorf("expected one-element slice, got %#v", s)
109 }
110 if s := agg.Errors()[0].Error(); s != "err" {
111 t.Errorf("expected 'err', got %q", s)
112 }
113
114 err = agg.(error)
115 if err == nil {
116 t.Errorf("expected non-nil")
117 }
118 if s := err.Error(); s != "err" {
119 t.Errorf("expected 'err', got %q", s)
120 }
121 }
122
123 func TestPluralAggregate(t *testing.T) {
124 var slice = []error{fmt.Errorf("abc"), fmt.Errorf("123")}
125 var agg Aggregate
126 var err error
127
128 agg = NewAggregate(slice)
129 if agg == nil {
130 t.Errorf("expected non-nil")
131 }
132 if s := agg.Error(); s != "[abc, 123]" {
133 t.Errorf("expected '[abc, 123]', got %q", s)
134 }
135 if s := agg.Errors(); len(s) != 2 {
136 t.Errorf("expected two-elements slice, got %#v", s)
137 }
138 if s := agg.Errors()[0].Error(); s != "abc" {
139 t.Errorf("expected '[abc, 123]', got %q", s)
140 }
141
142 err = agg.(error)
143 if err == nil {
144 t.Errorf("expected non-nil")
145 }
146 if s := err.Error(); s != "[abc, 123]" {
147 t.Errorf("expected '[abc, 123]', got %q", s)
148 }
149 }
150
151 func TestDedupeAggregate(t *testing.T) {
152 var slice = []error{fmt.Errorf("abc"), fmt.Errorf("abc")}
153 var agg Aggregate
154
155 agg = NewAggregate(slice)
156 if agg == nil {
157 t.Errorf("expected non-nil")
158 }
159 if s := agg.Error(); s != "abc" {
160 t.Errorf("expected 'abc', got %q", s)
161 }
162 if s := agg.Errors(); len(s) != 2 {
163 t.Errorf("expected two-elements slice, got %#v", s)
164 }
165 }
166
167 func TestDedupePluralAggregate(t *testing.T) {
168 var slice = []error{fmt.Errorf("abc"), fmt.Errorf("abc"), fmt.Errorf("123")}
169 var agg Aggregate
170
171 agg = NewAggregate(slice)
172 if agg == nil {
173 t.Errorf("expected non-nil")
174 }
175 if s := agg.Error(); s != "[abc, 123]" {
176 t.Errorf("expected '[abc, 123]', got %q", s)
177 }
178 if s := agg.Errors(); len(s) != 3 {
179 t.Errorf("expected three-elements slice, got %#v", s)
180 }
181 }
182
183 func TestFlattenAndDedupeAggregate(t *testing.T) {
184 var slice = []error{fmt.Errorf("abc"), fmt.Errorf("abc"), NewAggregate([]error{fmt.Errorf("abc")})}
185 var agg Aggregate
186
187 agg = NewAggregate(slice)
188 if agg == nil {
189 t.Errorf("expected non-nil")
190 }
191 if s := agg.Error(); s != "abc" {
192 t.Errorf("expected 'abc', got %q", s)
193 }
194 if s := agg.Errors(); len(s) != 3 {
195 t.Errorf("expected three-elements slice, got %#v", s)
196 }
197 }
198
199 func TestFlattenAggregate(t *testing.T) {
200 var slice = []error{fmt.Errorf("abc"), fmt.Errorf("abc"), NewAggregate([]error{fmt.Errorf("abc"), fmt.Errorf("def"), NewAggregate([]error{fmt.Errorf("def"), fmt.Errorf("ghi")})})}
201 var agg Aggregate
202
203 agg = NewAggregate(slice)
204 if agg == nil {
205 t.Errorf("expected non-nil")
206 }
207 if s := agg.Error(); s != "[abc, def, ghi]" {
208 t.Errorf("expected '[abc, def, ghi]', got %q", s)
209 }
210 if s := agg.Errors(); len(s) != 3 {
211 t.Errorf("expected three-elements slice, got %#v", s)
212 }
213 }
214
215 func TestFilterOut(t *testing.T) {
216 testCases := []struct {
217 err error
218 filter []Matcher
219 expected error
220 }{
221 {
222 nil,
223 []Matcher{},
224 nil,
225 },
226 {
227 aggregate{},
228 []Matcher{},
229 nil,
230 },
231 {
232 aggregate{fmt.Errorf("abc")},
233 []Matcher{},
234 aggregate{fmt.Errorf("abc")},
235 },
236 {
237 aggregate{fmt.Errorf("abc")},
238 []Matcher{func(err error) bool { return false }},
239 aggregate{fmt.Errorf("abc")},
240 },
241 {
242 aggregate{fmt.Errorf("abc")},
243 []Matcher{func(err error) bool { return true }},
244 nil,
245 },
246 {
247 aggregate{fmt.Errorf("abc")},
248 []Matcher{func(err error) bool { return false }, func(err error) bool { return false }},
249 aggregate{fmt.Errorf("abc")},
250 },
251 {
252 aggregate{fmt.Errorf("abc")},
253 []Matcher{func(err error) bool { return false }, func(err error) bool { return true }},
254 nil,
255 },
256 {
257 aggregate{fmt.Errorf("abc"), fmt.Errorf("def"), fmt.Errorf("ghi")},
258 []Matcher{func(err error) bool { return err.Error() == "def" }},
259 aggregate{fmt.Errorf("abc"), fmt.Errorf("ghi")},
260 },
261 {
262 aggregate{aggregate{fmt.Errorf("abc")}},
263 []Matcher{},
264 aggregate{aggregate{fmt.Errorf("abc")}},
265 },
266 {
267 aggregate{aggregate{fmt.Errorf("abc"), aggregate{fmt.Errorf("def")}}},
268 []Matcher{},
269 aggregate{aggregate{fmt.Errorf("abc"), aggregate{fmt.Errorf("def")}}},
270 },
271 {
272 aggregate{aggregate{fmt.Errorf("abc"), aggregate{fmt.Errorf("def")}}},
273 []Matcher{func(err error) bool { return err.Error() == "def" }},
274 aggregate{aggregate{fmt.Errorf("abc")}},
275 },
276 }
277 for i, testCase := range testCases {
278 err := FilterOut(testCase.err, testCase.filter...)
279 if !reflect.DeepEqual(testCase.expected, err) {
280 t.Errorf("%d: expected %v, got %v", i, testCase.expected, err)
281 }
282 }
283 }
284
285 func TestFlatten(t *testing.T) {
286 testCases := []struct {
287 agg Aggregate
288 expected Aggregate
289 }{
290 {
291 nil,
292 nil,
293 },
294 {
295 aggregate{},
296 nil,
297 },
298 {
299 aggregate{fmt.Errorf("abc")},
300 aggregate{fmt.Errorf("abc")},
301 },
302 {
303 aggregate{fmt.Errorf("abc"), fmt.Errorf("def"), fmt.Errorf("ghi")},
304 aggregate{fmt.Errorf("abc"), fmt.Errorf("def"), fmt.Errorf("ghi")},
305 },
306 {
307 aggregate{aggregate{fmt.Errorf("abc")}},
308 aggregate{fmt.Errorf("abc")},
309 },
310 {
311 aggregate{aggregate{aggregate{fmt.Errorf("abc")}}},
312 aggregate{fmt.Errorf("abc")},
313 },
314 {
315 aggregate{aggregate{fmt.Errorf("abc"), aggregate{fmt.Errorf("def")}}},
316 aggregate{fmt.Errorf("abc"), fmt.Errorf("def")},
317 },
318 {
319 aggregate{aggregate{aggregate{fmt.Errorf("abc")}, fmt.Errorf("def"), aggregate{fmt.Errorf("ghi")}}},
320 aggregate{fmt.Errorf("abc"), fmt.Errorf("def"), fmt.Errorf("ghi")},
321 },
322 }
323 for i, testCase := range testCases {
324 agg := Flatten(testCase.agg)
325 if !reflect.DeepEqual(testCase.expected, agg) {
326 t.Errorf("%d: expected %v, got %v", i, testCase.expected, agg)
327 }
328 }
329 }
330
331 func TestCreateAggregateFromMessageCountMap(t *testing.T) {
332 testCases := []struct {
333 name string
334 mcm MessageCountMap
335 expected Aggregate
336 }{
337 {
338 "input has single instance of one message",
339 MessageCountMap{"abc": 1},
340 aggregate{fmt.Errorf("abc")},
341 },
342 {
343 "input has multiple messages",
344 MessageCountMap{"abc": 2, "ghi": 1},
345 aggregate{fmt.Errorf("abc (repeated 2 times)"), fmt.Errorf("ghi")},
346 },
347 {
348 "input has multiple messages",
349 MessageCountMap{"ghi": 1, "abc": 2},
350 aggregate{fmt.Errorf("abc (repeated 2 times)"), fmt.Errorf("ghi")},
351 },
352 }
353
354 var expected, agg []error
355 for _, testCase := range testCases {
356 t.Run(testCase.name, func(t *testing.T) {
357 if testCase.expected != nil {
358 expected = testCase.expected.Errors()
359 sort.Slice(expected, func(i, j int) bool { return expected[i].Error() < expected[j].Error() })
360 }
361 if testCase.mcm != nil {
362 agg = CreateAggregateFromMessageCountMap(testCase.mcm).Errors()
363 sort.Slice(agg, func(i, j int) bool { return agg[i].Error() < agg[j].Error() })
364 }
365 if !reflect.DeepEqual(expected, agg) {
366 t.Errorf("expected %v, got %v", expected, agg)
367 }
368 })
369 }
370 }
371
372 func TestAggregateGoroutines(t *testing.T) {
373 testCases := []struct {
374 errs []error
375 expected map[string]bool
376 }{
377 {
378 []error{},
379 nil,
380 },
381 {
382 []error{nil},
383 nil,
384 },
385 {
386 []error{nil, nil},
387 nil,
388 },
389 {
390 []error{fmt.Errorf("1")},
391 map[string]bool{"1": true},
392 },
393 {
394 []error{fmt.Errorf("1"), nil},
395 map[string]bool{"1": true},
396 },
397 {
398 []error{fmt.Errorf("1"), fmt.Errorf("267")},
399 map[string]bool{"1": true, "267": true},
400 },
401 {
402 []error{fmt.Errorf("1"), nil, fmt.Errorf("1234")},
403 map[string]bool{"1": true, "1234": true},
404 },
405 {
406 []error{nil, fmt.Errorf("1"), nil, fmt.Errorf("1234"), fmt.Errorf("22")},
407 map[string]bool{"1": true, "1234": true, "22": true},
408 },
409 }
410 for i, testCase := range testCases {
411 funcs := make([]func() error, len(testCase.errs))
412 for i := range testCase.errs {
413 err := testCase.errs[i]
414 funcs[i] = func() error { return err }
415 }
416 agg := AggregateGoroutines(funcs...)
417 if agg == nil {
418 if len(testCase.expected) > 0 {
419 t.Errorf("%d: expected %v, got nil", i, testCase.expected)
420 }
421 continue
422 }
423 if len(agg.Errors()) != len(testCase.expected) {
424 t.Errorf("%d: expected %d errors in aggregate, got %v", i, len(testCase.expected), agg)
425 continue
426 }
427 for _, err := range agg.Errors() {
428 if !testCase.expected[err.Error()] {
429 t.Errorf("%d: expected %v, got aggregate containing %v", i, testCase.expected, err)
430 }
431 }
432 }
433 }
434
435 type alwaysMatchingError struct{}
436
437 func (_ alwaysMatchingError) Error() string {
438 return "error"
439 }
440
441 func (_ alwaysMatchingError) Is(_ error) bool {
442 return true
443 }
444
445 type someError struct{ msg string }
446
447 func (se someError) Error() string {
448 if se.msg != "" {
449 return se.msg
450 }
451 return "err"
452 }
453
454 func TestAggregateWithErrorsIs(t *testing.T) {
455 testCases := []struct {
456 name string
457 err error
458 matchAgainst error
459 expectMatch bool
460 }{
461 {
462 name: "no match",
463 err: aggregate{errors.New("my-error"), errors.New("my-other-error")},
464 matchAgainst: fmt.Errorf("no entry %s", "here"),
465 },
466 {
467 name: "match via .Is()",
468 err: aggregate{errors.New("forbidden"), alwaysMatchingError{}},
469 matchAgainst: errors.New("unauthorized"),
470 expectMatch: true,
471 },
472 {
473 name: "match via equality",
474 err: aggregate{errors.New("err"), someError{}},
475 matchAgainst: someError{},
476 expectMatch: true,
477 },
478 {
479 name: "match via nested aggregate",
480 err: aggregate{errors.New("closed today"), aggregate{aggregate{someError{}}}},
481 matchAgainst: someError{},
482 expectMatch: true,
483 },
484 {
485 name: "match via wrapped aggregate",
486 err: fmt.Errorf("wrap: %w", aggregate{errors.New("err"), someError{}}),
487 matchAgainst: someError{},
488 expectMatch: true,
489 },
490 }
491
492 for _, tc := range testCases {
493 t.Run(tc.name, func(t *testing.T) {
494 result := errors.Is(tc.err, tc.matchAgainst)
495 if result != tc.expectMatch {
496 t.Errorf("expected match: %t, got match: %t", tc.expectMatch, result)
497 }
498 })
499 }
500 }
501
502 type accessTrackingError struct {
503 wasAccessed bool
504 }
505
506 func (accessTrackingError) Error() string {
507 return "err"
508 }
509
510 func (ate *accessTrackingError) Is(_ error) bool {
511 ate.wasAccessed = true
512 return true
513 }
514
515 var _ error = &accessTrackingError{}
516
517 func TestErrConfigurationInvalidWithErrorsIsShortCircuitsOnFirstMatch(t *testing.T) {
518 errC := aggregate{&accessTrackingError{}, &accessTrackingError{}}
519 _ = errors.Is(errC, &accessTrackingError{})
520
521 var numAccessed int
522 for _, err := range errC {
523 if ate := err.(*accessTrackingError); ate.wasAccessed {
524 numAccessed++
525 }
526 }
527 if numAccessed != 1 {
528 t.Errorf("expected exactly one error to get accessed, got %d", numAccessed)
529 }
530 }
531
View as plain text