mirror of
https://github.com/tailscale/tailscale.git
synced 2024-12-01 22:15:51 +00:00
350aab05e5
Consider the following pattern: err1 := foo() err2 := bar() err3 := baz() return multierr.New(err1, err2, err3) If err1, err2, and err3 are all nil, then multierr.New should not allocate. Thus, modify the logic of New to count the number of distinct error values and allocate the exactly needed slice. This also speeds up non-empty error situation since repeatedly growing with append is slow. Performance: name old time/op new time/op delta Empty-24 41.8ns ± 2% 6.4ns ± 1% -84.73% (p=0.000 n=10+10) NonEmpty-24 120ns ± 3% 69ns ± 1% -42.01% (p=0.000 n=9+10) name old alloc/op new alloc/op delta Empty-24 64.0B ± 0% 0.0B -100.00% (p=0.000 n=10+10) NonEmpty-24 168B ± 0% 88B ± 0% -47.62% (p=0.000 n=10+10) name old allocs/op new allocs/op delta Empty-24 1.00 ± 0% 0.00 -100.00% (p=0.000 n=10+10) NonEmpty-24 3.00 ± 0% 2.00 ± 0% -33.33% (p=0.000 n=10+10) Signed-off-by: Joe Tsai <joetsai@digital-static.net>
127 lines
3.2 KiB
Go
127 lines
3.2 KiB
Go
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package multierr_test
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"testing"
|
|
|
|
qt "github.com/frankban/quicktest"
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/google/go-cmp/cmp/cmpopts"
|
|
"tailscale.com/util/multierr"
|
|
)
|
|
|
|
func TestAll(t *testing.T) {
|
|
C := qt.New(t)
|
|
eqErr := qt.CmpEquals(cmpopts.EquateErrors())
|
|
|
|
type E = []error
|
|
N := multierr.New
|
|
|
|
a := errors.New("a")
|
|
b := errors.New("b")
|
|
c := errors.New("c")
|
|
d := errors.New("d")
|
|
x := errors.New("x")
|
|
abcd := E{a, b, c, d}
|
|
|
|
tests := []struct {
|
|
In E // input to New
|
|
WantNil bool // want nil returned?
|
|
WantSingle error // if non-nil, want this single error returned
|
|
WantErrors []error // if non-nil, want an Error composed of these errors returned
|
|
}{
|
|
{In: nil, WantNil: true},
|
|
|
|
{In: E{nil}, WantNil: true},
|
|
{In: E{nil, nil}, WantNil: true},
|
|
|
|
{In: E{a}, WantSingle: a},
|
|
{In: E{a, nil}, WantSingle: a},
|
|
{In: E{nil, a}, WantSingle: a},
|
|
{In: E{nil, a, nil}, WantSingle: a},
|
|
|
|
{In: E{a, b}, WantErrors: E{a, b}},
|
|
{In: E{nil, a, nil, b, nil}, WantErrors: E{a, b}},
|
|
|
|
{In: E{a, b, N(c, d)}, WantErrors: E{a, b, c, d}},
|
|
{In: E{a, N(b, c), d}, WantErrors: E{a, b, c, d}},
|
|
{In: E{N(a, b), c, d}, WantErrors: E{a, b, c, d}},
|
|
{In: E{N(a, b), N(c, d)}, WantErrors: E{a, b, c, d}},
|
|
{In: E{nil, N(a, nil, b), nil, N(c, d)}, WantErrors: E{a, b, c, d}},
|
|
|
|
{In: E{N(a, N(b, N(c, N(d))))}, WantErrors: E{a, b, c, d}},
|
|
{In: E{N(N(N(N(a), b), c), d)}, WantErrors: E{a, b, c, d}},
|
|
|
|
{In: E{N(abcd...)}, WantErrors: E{a, b, c, d}},
|
|
{In: E{N(abcd...), N(abcd...)}, WantErrors: E{a, b, c, d, a, b, c, d}},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
got := multierr.New(test.In...)
|
|
if test.WantNil {
|
|
C.Assert(got, qt.IsNil)
|
|
continue
|
|
}
|
|
if test.WantSingle != nil {
|
|
C.Assert(got, eqErr, test.WantSingle)
|
|
continue
|
|
}
|
|
ee, _ := got.(multierr.Error)
|
|
C.Assert(ee.Errors(), eqErr, test.WantErrors)
|
|
|
|
for _, e := range test.WantErrors {
|
|
C.Assert(ee.Is(e), qt.IsTrue)
|
|
}
|
|
C.Assert(ee.Is(x), qt.IsFalse)
|
|
}
|
|
}
|
|
|
|
func TestRange(t *testing.T) {
|
|
C := qt.New(t)
|
|
|
|
errA := errors.New("A")
|
|
errB := errors.New("B")
|
|
errC := errors.New("C")
|
|
errD := errors.New("D")
|
|
errCD := multierr.New(errC, errD)
|
|
errCD1 := fmt.Errorf("1:%w", errCD)
|
|
errE := errors.New("E")
|
|
errE1 := fmt.Errorf("1:%w", errE)
|
|
errE2 := fmt.Errorf("2:%w", errE1)
|
|
errF := errors.New("F")
|
|
root := multierr.New(errA, errB, errCD1, errE2, errF)
|
|
|
|
var got []error
|
|
want := []error{root, errA, errB, errCD1, errCD, errC, errD, errE2, errE1, errE, errF}
|
|
multierr.Range(root, func(err error) bool {
|
|
got = append(got, err)
|
|
return true
|
|
})
|
|
C.Assert(got, qt.CmpEquals(cmp.Comparer(func(x, y error) bool {
|
|
return x.Error() == y.Error()
|
|
})), want)
|
|
}
|
|
|
|
var sink error
|
|
|
|
func BenchmarkEmpty(b *testing.B) {
|
|
b.ReportAllocs()
|
|
for i := 0; i < b.N; i++ {
|
|
sink = multierr.New(nil, nil, nil, multierr.Error{})
|
|
}
|
|
}
|
|
|
|
func BenchmarkNonEmpty(b *testing.B) {
|
|
merr := multierr.New(io.ErrShortBuffer, io.ErrNoProgress)
|
|
b.ReportAllocs()
|
|
for i := 0; i < b.N; i++ {
|
|
sink = multierr.New(io.ErrUnexpectedEOF, merr, io.ErrClosedPipe)
|
|
}
|
|
}
|