Skip to content

Commit 1014a7c

Browse files
authored
AppendInvoke: Merge test, real example (#48)
Follow up to #47 with the following changes. - The tests for AppendInvoke with Close duplicate the tests for AppendInvoke. These can be merged, and the unit tests for Close only need to verify whether the Invoker returned by multierr.Close calls the Close function in the provided io.Closer. - Replace AppendInvoke example with something more realistic. - Update changelog.
1 parent a402392 commit 1014a7c

File tree

6 files changed

+113
-71
lines changed

6 files changed

+113
-71
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
Releases
22
========
33

4+
v1.7.0 (unreleased)
5+
===================
6+
7+
- Add `AppendInvoke` to append into errors from `defer` blocks.
8+
9+
410
v1.6.0 (2020-09-14)
511
===================
612

appendinvoke_example_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright (c) 2021 Uber Technologies, Inc.
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy
4+
// of this software and associated documentation files (the "Software"), to deal
5+
// in the Software without restriction, including without limitation the rights
6+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
// copies of the Software, and to permit persons to whom the Software is
8+
// furnished to do so, subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in
11+
// all copies or substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
// THE SOFTWARE.
20+
21+
package multierr_test
22+
23+
import (
24+
"fmt"
25+
"io/ioutil"
26+
"log"
27+
"os"
28+
"path/filepath"
29+
30+
"go.uber.org/multierr"
31+
)
32+
33+
func ExampleAppendInvoke() {
34+
if err := run(); err != nil {
35+
log.Fatal(err)
36+
}
37+
}
38+
39+
func run() (err error) {
40+
dir, err := ioutil.TempDir("", "multierr")
41+
// We create a temporary directory and defer its deletion when this
42+
// function returns.
43+
//
44+
// If we failed to delete the temporary directory, we append its
45+
// failure into the returned value with multierr.AppendInvoke.
46+
//
47+
// This uses a custom invoker that we implement below.
48+
defer multierr.AppendInvoke(&err, RemoveAll(dir))
49+
50+
path := filepath.Join(dir, "example.txt")
51+
f, err := os.Create(path)
52+
if err != nil {
53+
return err
54+
}
55+
// Similarly, we defer closing the open file when the function returns,
56+
// and appends its failure, if any, into the returned error.
57+
//
58+
// This uses the multierr.Close invoker included in multierr.
59+
defer multierr.AppendInvoke(&err, multierr.Close(f))
60+
61+
if _, err := fmt.Fprintln(f, "hello"); err != nil {
62+
return err
63+
}
64+
65+
return nil
66+
}
67+
68+
// RemoveAll is a multierr.Invoker that deletes the provided directory and all
69+
// of its contents.
70+
type RemoveAll string
71+
72+
func (r RemoveAll) Invoke() error {
73+
return os.RemoveAll(string(r))
74+
}

error_test.go

Lines changed: 24 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -596,60 +596,49 @@ func TestAppendInvoke(t *testing.T) {
596596
errors.New("baz"),
597597
),
598598
},
599-
}
600-
for _, tt := range tests {
601-
t.Run(tt.desc, func(t *testing.T) {
602-
AppendInvoke(tt.into, tt.give)
603-
assert.Equal(t, tt.want, *tt.into)
604-
})
605-
}
606-
}
607-
608-
func TestAppendInvokeClose(t *testing.T) {
609-
tests := []struct {
610-
desc string
611-
into *error
612-
give error // error returned by Close()
613-
want error
614-
}{
615599
{
616-
desc: "append close nil into empty",
600+
desc: "close/empty",
617601
into: new(error),
618-
give: nil,
619-
want: nil,
602+
give: Close(newCloserMock(t, errors.New("foo"))),
603+
want: errors.New("foo"),
620604
},
621605
{
622-
desc: "append close into non-empty, non-multierr",
623-
into: errorPtr(errors.New("foo")),
624-
give: errors.New("bar"),
625-
want: Combine(
626-
errors.New("foo"),
627-
errors.New("bar"),
628-
),
606+
desc: "close/no fail",
607+
into: new(error),
608+
give: Close(newCloserMock(t, nil)),
609+
want: nil,
629610
},
630611
{
631-
desc: "append close into non-empty multierr",
632-
into: errorPtr(Combine(
633-
errors.New("foo"),
634-
errors.New("bar"),
635-
)),
636-
give: errors.New("baz"),
612+
desc: "close/non-empty",
613+
into: errorPtr(errors.New("foo")),
614+
give: Close(newCloserMock(t, errors.New("bar"))),
637615
want: Combine(
638616
errors.New("foo"),
639617
errors.New("bar"),
640-
errors.New("baz"),
641618
),
642619
},
643620
}
644621
for _, tt := range tests {
645622
t.Run(tt.desc, func(t *testing.T) {
646-
closer := newCloserMock(t, tt.give)
647-
AppendInvoke(tt.into, Close(closer))
623+
AppendInvoke(tt.into, tt.give)
648624
assert.Equal(t, tt.want, *tt.into)
649625
})
650626
}
651627
}
652628

629+
func TestClose(t *testing.T) {
630+
t.Run("fail", func(t *testing.T) {
631+
give := errors.New("great sadness")
632+
got := Close(newCloserMock(t, give)).Invoke()
633+
assert.Same(t, give, got)
634+
})
635+
636+
t.Run("success", func(t *testing.T) {
637+
got := Close(newCloserMock(t, nil)).Invoke()
638+
assert.Nil(t, got)
639+
})
640+
}
641+
653642
func TestAppendIntoNil(t *testing.T) {
654643
t.Run("nil pointer panics", func(t *testing.T) {
655644
assert.Panics(t, func() {
@@ -689,7 +678,6 @@ func newCloserMock(tb testing.TB, err error) io.Closer {
689678
tb.Error("closerMock wasn't closed before test end")
690679
}
691680
})
692-
693681
return closerMock(func() error {
694682
closed = true
695683
return err

example_test.go

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -94,38 +94,6 @@ func ExampleAppendInto() {
9494
// foo; baz
9595
}
9696

97-
func ExampleAppendInvoke() {
98-
var err error
99-
100-
var errFunc1 multierr.Invoker = multierr.Invoke(func() error {
101-
fmt.Println("call 1 failed")
102-
return errors.New("foo")
103-
})
104-
105-
var errFunc2 multierr.Invoker = multierr.Invoke(func() error {
106-
fmt.Println("call 2 did not fail")
107-
return nil
108-
})
109-
110-
var errFunc3 multierr.Invoker = multierr.Invoke(func() error {
111-
fmt.Println("call 3 failed")
112-
return errors.New("baz")
113-
})
114-
115-
defer func() {
116-
fmt.Println(err)
117-
}()
118-
defer multierr.AppendInvoke(&err, errFunc3)
119-
defer multierr.AppendInvoke(&err, errFunc2)
120-
defer multierr.AppendInvoke(&err, errFunc1)
121-
122-
// Output:
123-
// call 1 failed
124-
// call 2 did not fail
125-
// call 3 failed
126-
// foo; baz
127-
}
128-
12997
type fakeCloser func() error
13098

13199
func (f fakeCloser) Close() error {

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module go.uber.org/multierr
33
go 1.14
44

55
require (
6-
github.com/stretchr/testify v1.3.0
6+
github.com/stretchr/testify v1.7.0
77
go.uber.org/atomic v1.7.0
8+
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
89
)

go.sum

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
1-
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
21
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
32
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
43
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
54
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
65
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
76
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
8-
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
97
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
8+
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
9+
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
1010
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
1111
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
12+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
13+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
14+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
15+
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
16+
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 commit comments

Comments
 (0)