diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 12:36:04 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 12:36:04 +0000 |
commit | b09c6d56832eb1718c07d74abf3bc6ae3fe4e030 (patch) | |
tree | d2caec2610d4ea887803ec9e9c3cd77136c448ba /dependencies/pkg/mod/go.uber.org/multierr@v1.6.0/error_test.go | |
parent | Initial commit. (diff) | |
download | icingadb-upstream.tar.xz icingadb-upstream.zip |
Adding upstream version 1.1.0.upstream/1.1.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | dependencies/pkg/mod/go.uber.org/multierr@v1.6.0/error_test.go | 582 |
1 files changed, 582 insertions, 0 deletions
diff --git a/dependencies/pkg/mod/go.uber.org/multierr@v1.6.0/error_test.go b/dependencies/pkg/mod/go.uber.org/multierr@v1.6.0/error_test.go new file mode 100644 index 0000000..96c869e --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/multierr@v1.6.0/error_test.go @@ -0,0 +1,582 @@ +// Copyright (c) 2019 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package multierr + +import ( + "errors" + "fmt" + "io" + "sync" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// richFormatError is an error that prints a different output depending on +// whether %v or %+v was used. +type richFormatError struct{} + +func (r richFormatError) Error() string { + return fmt.Sprint(r) +} + +func (richFormatError) Format(f fmt.State, c rune) { + if c == 'v' && f.Flag('+') { + io.WriteString(f, "multiline\nmessage\nwith plus") + } else { + io.WriteString(f, "without plus") + } +} + +func appendN(initial, err error, n int) error { + errs := initial + for i := 0; i < n; i++ { + errs = Append(errs, err) + } + return errs +} + +func newMultiErr(errors ...error) error { + return &multiError{errors: errors} +} + +func TestCombine(t *testing.T) { + tests := []struct { + // Input + giveErrors []error + + // Resulting error + wantError error + + // %+v and %v string representations + wantMultiline string + wantSingleline string + }{ + { + giveErrors: nil, + wantError: nil, + }, + { + giveErrors: []error{}, + wantError: nil, + }, + { + giveErrors: []error{ + errors.New("foo"), + nil, + newMultiErr( + errors.New("bar"), + ), + nil, + }, + wantError: newMultiErr( + errors.New("foo"), + errors.New("bar"), + ), + wantMultiline: "the following errors occurred:\n" + + " - foo\n" + + " - bar", + wantSingleline: "foo; bar", + }, + { + giveErrors: []error{ + errors.New("foo"), + newMultiErr( + errors.New("bar"), + ), + }, + wantError: newMultiErr( + errors.New("foo"), + errors.New("bar"), + ), + wantMultiline: "the following errors occurred:\n" + + " - foo\n" + + " - bar", + wantSingleline: "foo; bar", + }, + { + giveErrors: []error{errors.New("great sadness")}, + wantError: errors.New("great sadness"), + wantMultiline: "great sadness", + wantSingleline: "great sadness", + }, + { + giveErrors: []error{ + errors.New("foo"), + errors.New("bar"), + }, + wantError: newMultiErr( + errors.New("foo"), + errors.New("bar"), + ), + wantMultiline: "the following errors occurred:\n" + + " - foo\n" + + " - bar", + wantSingleline: "foo; bar", + }, + { + giveErrors: []error{ + errors.New("great sadness"), + errors.New("multi\n line\nerror message"), + errors.New("single line error message"), + }, + wantError: newMultiErr( + errors.New("great sadness"), + errors.New("multi\n line\nerror message"), + errors.New("single line error message"), + ), + wantMultiline: "the following errors occurred:\n" + + " - great sadness\n" + + " - multi\n" + + " line\n" + + " error message\n" + + " - single line error message", + wantSingleline: "great sadness; " + + "multi\n line\nerror message; " + + "single line error message", + }, + { + giveErrors: []error{ + errors.New("foo"), + newMultiErr( + errors.New("bar"), + errors.New("baz"), + ), + errors.New("qux"), + }, + wantError: newMultiErr( + errors.New("foo"), + errors.New("bar"), + errors.New("baz"), + errors.New("qux"), + ), + wantMultiline: "the following errors occurred:\n" + + " - foo\n" + + " - bar\n" + + " - baz\n" + + " - qux", + wantSingleline: "foo; bar; baz; qux", + }, + { + giveErrors: []error{ + errors.New("foo"), + nil, + newMultiErr( + errors.New("bar"), + ), + nil, + }, + wantError: newMultiErr( + errors.New("foo"), + errors.New("bar"), + ), + wantMultiline: "the following errors occurred:\n" + + " - foo\n" + + " - bar", + wantSingleline: "foo; bar", + }, + { + giveErrors: []error{ + errors.New("foo"), + newMultiErr( + errors.New("bar"), + ), + }, + wantError: newMultiErr( + errors.New("foo"), + errors.New("bar"), + ), + wantMultiline: "the following errors occurred:\n" + + " - foo\n" + + " - bar", + wantSingleline: "foo; bar", + }, + { + giveErrors: []error{ + errors.New("foo"), + richFormatError{}, + errors.New("bar"), + }, + wantError: newMultiErr( + errors.New("foo"), + richFormatError{}, + errors.New("bar"), + ), + wantMultiline: "the following errors occurred:\n" + + " - foo\n" + + " - multiline\n" + + " message\n" + + " with plus\n" + + " - bar", + wantSingleline: "foo; without plus; bar", + }, + } + + for i, tt := range tests { + t.Run(fmt.Sprint(i), func(t *testing.T) { + err := Combine(tt.giveErrors...) + require.Equal(t, tt.wantError, err) + + if tt.wantMultiline != "" { + t.Run("Sprintf/multiline", func(t *testing.T) { + assert.Equal(t, tt.wantMultiline, fmt.Sprintf("%+v", err)) + }) + } + + if tt.wantSingleline != "" { + t.Run("Sprintf/singleline", func(t *testing.T) { + assert.Equal(t, tt.wantSingleline, fmt.Sprintf("%v", err)) + }) + + t.Run("Error()", func(t *testing.T) { + assert.Equal(t, tt.wantSingleline, err.Error()) + }) + + if s, ok := err.(fmt.Stringer); ok { + t.Run("String()", func(t *testing.T) { + assert.Equal(t, tt.wantSingleline, s.String()) + }) + } + } + }) + } +} + +func TestCombineDoesNotModifySlice(t *testing.T) { + errors := []error{ + errors.New("foo"), + nil, + errors.New("bar"), + } + + assert.NotNil(t, Combine(errors...)) + assert.Len(t, errors, 3) + assert.Nil(t, errors[1], 3) +} + +func TestAppend(t *testing.T) { + tests := []struct { + left error + right error + want error + }{ + { + left: nil, + right: nil, + want: nil, + }, + { + left: nil, + right: errors.New("great sadness"), + want: errors.New("great sadness"), + }, + { + left: errors.New("great sadness"), + right: nil, + want: errors.New("great sadness"), + }, + { + left: errors.New("foo"), + right: errors.New("bar"), + want: newMultiErr( + errors.New("foo"), + errors.New("bar"), + ), + }, + { + left: newMultiErr( + errors.New("foo"), + errors.New("bar"), + ), + right: errors.New("baz"), + want: newMultiErr( + errors.New("foo"), + errors.New("bar"), + errors.New("baz"), + ), + }, + { + left: errors.New("baz"), + right: newMultiErr( + errors.New("foo"), + errors.New("bar"), + ), + want: newMultiErr( + errors.New("baz"), + errors.New("foo"), + errors.New("bar"), + ), + }, + { + left: newMultiErr( + errors.New("foo"), + ), + right: newMultiErr( + errors.New("bar"), + ), + want: newMultiErr( + errors.New("foo"), + errors.New("bar"), + ), + }, + } + + for _, tt := range tests { + assert.Equal(t, tt.want, Append(tt.left, tt.right)) + } +} + +type notMultiErr struct{} + +var _ errorGroup = notMultiErr{} + +func (notMultiErr) Error() string { + return "great sadness" +} + +func (notMultiErr) Errors() []error { + return []error{errors.New("great sadness")} +} + +func TestErrors(t *testing.T) { + tests := []struct { + give error + want []error + + // Don't attempt to cast to errorGroup or *multiError + dontCast bool + }{ + {dontCast: true}, // nil + { + give: errors.New("hi"), + want: []error{errors.New("hi")}, + dontCast: true, + }, + { + // We don't yet support non-multierr errors. + give: notMultiErr{}, + want: []error{notMultiErr{}}, + dontCast: true, + }, + { + give: Combine( + errors.New("foo"), + errors.New("bar"), + ), + want: []error{ + errors.New("foo"), + errors.New("bar"), + }, + }, + { + give: Append( + errors.New("foo"), + errors.New("bar"), + ), + want: []error{ + errors.New("foo"), + errors.New("bar"), + }, + }, + { + give: Append( + errors.New("foo"), + Combine( + errors.New("bar"), + ), + ), + want: []error{ + errors.New("foo"), + errors.New("bar"), + }, + }, + { + give: Combine( + errors.New("foo"), + Append( + errors.New("bar"), + errors.New("baz"), + ), + errors.New("qux"), + ), + want: []error{ + errors.New("foo"), + errors.New("bar"), + errors.New("baz"), + errors.New("qux"), + }, + }, + } + + for i, tt := range tests { + t.Run(fmt.Sprint(i), func(t *testing.T) { + t.Run("Errors()", func(t *testing.T) { + require.Equal(t, tt.want, Errors(tt.give)) + }) + + if tt.dontCast { + return + } + + t.Run("multiError", func(t *testing.T) { + require.Equal(t, tt.want, tt.give.(*multiError).Errors()) + }) + + t.Run("errorGroup", func(t *testing.T) { + require.Equal(t, tt.want, tt.give.(errorGroup).Errors()) + }) + }) + } +} + +func createMultiErrWithCapacity() error { + // Create a multiError that has capacity for more errors so Append will + // modify the underlying array that may be shared. + return appendN(nil, errors.New("append"), 50) +} + +func TestAppendDoesNotModify(t *testing.T) { + initial := createMultiErrWithCapacity() + err1 := Append(initial, errors.New("err1")) + err2 := Append(initial, errors.New("err2")) + + // Make sure the error messages match, since we do modify the copyNeeded + // atomic, the values cannot be compared. + assert.EqualError(t, initial, createMultiErrWithCapacity().Error(), "Initial should not be modified") + + assert.EqualError(t, err1, Append(createMultiErrWithCapacity(), errors.New("err1")).Error()) + assert.EqualError(t, err2, Append(createMultiErrWithCapacity(), errors.New("err2")).Error()) +} + +func TestAppendRace(t *testing.T) { + initial := createMultiErrWithCapacity() + + var wg sync.WaitGroup + for i := 0; i < 10; i++ { + wg.Add(1) + go func() { + defer wg.Done() + + err := initial + for j := 0; j < 10; j++ { + err = Append(err, errors.New("err")) + } + }() + } + + wg.Wait() +} + +func TestErrorsSliceIsImmutable(t *testing.T) { + err1 := errors.New("err1") + err2 := errors.New("err2") + + err := Append(err1, err2) + gotErrors := Errors(err) + require.Equal(t, []error{err1, err2}, gotErrors, "errors must match") + + gotErrors[0] = nil + gotErrors[1] = errors.New("err3") + + require.Equal(t, []error{err1, err2}, Errors(err), + "errors must match after modification") +} + +func TestNilMultierror(t *testing.T) { + // For safety, all operations on multiError should be safe even if it is + // nil. + var err *multiError + + require.Empty(t, err.Error()) + require.Empty(t, err.Errors()) +} + +func TestAppendInto(t *testing.T) { + tests := []struct { + desc string + into *error + give error + want error + }{ + { + desc: "append into empty", + into: new(error), + give: errors.New("foo"), + want: errors.New("foo"), + }, + { + desc: "append into non-empty, non-multierr", + into: errorPtr(errors.New("foo")), + give: errors.New("bar"), + want: Combine( + errors.New("foo"), + errors.New("bar"), + ), + }, + { + desc: "append into non-empty multierr", + into: errorPtr(Combine( + errors.New("foo"), + errors.New("bar"), + )), + give: errors.New("baz"), + want: Combine( + errors.New("foo"), + errors.New("bar"), + errors.New("baz"), + ), + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + assert.True(t, AppendInto(tt.into, tt.give)) + assert.Equal(t, tt.want, *tt.into) + }) + } +} + +func TestAppendIntoNil(t *testing.T) { + t.Run("nil pointer panics", func(t *testing.T) { + assert.Panics(t, func() { + AppendInto(nil, errors.New("foo")) + }) + }) + + t.Run("nil error is no-op", func(t *testing.T) { + t.Run("empty left", func(t *testing.T) { + var err error + assert.False(t, AppendInto(&err, nil)) + assert.Nil(t, err) + }) + + t.Run("non-empty left", func(t *testing.T) { + err := errors.New("foo") + assert.False(t, AppendInto(&err, nil)) + assert.Equal(t, errors.New("foo"), err) + }) + }) +} + +func errorPtr(err error) *error { + return &err +} |