1/*
2Copyright 2015 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package errors
18
19import (
20	"errors"
21	"fmt"
22	"reflect"
23	"sort"
24	"testing"
25)
26
27func 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	// This is not normally possible, but pedantry demands I test it.
42	agg = aggregate(slice) // empty aggregate
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
55func 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	// Append a non-nil error
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
95func TestSingularAggregate(t *testing.T) {
96	var slice []error = []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
123func TestPluralAggregate(t *testing.T) {
124	var slice []error = []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
151func TestDedupeAggregate(t *testing.T) {
152	var slice []error = []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
167func TestDedupePluralAggregate(t *testing.T) {
168	var slice []error = []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
183func TestFlattenAndDedupeAggregate(t *testing.T) {
184	var slice []error = []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
199func TestFlattenAggregate(t *testing.T) {
200	var slice []error = []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
215func 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
285func 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
331func 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
372func TestAggregateGoroutines(t *testing.T) {
373	testCases := []struct {
374		errs     []error
375		expected map[string]bool // can't compare directly to Aggregate due to non-deterministic ordering
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
435type alwaysMatchingError struct{}
436
437func (_ alwaysMatchingError) Error() string {
438	return "error"
439}
440
441func (_ alwaysMatchingError) Is(_ error) bool {
442	return true
443}
444
445type someError struct{ msg string }
446
447func (se someError) Error() string {
448	if se.msg != "" {
449		return se.msg
450	}
451	return "err"
452}
453
454func 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
502type accessTrackingError struct {
503	wasAccessed bool
504}
505
506func (accessTrackingError) Error() string {
507	return "err"
508}
509
510func (ate *accessTrackingError) Is(_ error) bool {
511	ate.wasAccessed = true
512	return true
513}
514
515var _ error = &accessTrackingError{}
516
517func 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