1
2
3
4
5 package errgroup_test
6
7 import (
8 "context"
9 "errors"
10 "fmt"
11 "net/http"
12 "os"
13 "sync/atomic"
14 "testing"
15 "time"
16
17 "golang.org/x/sync/errgroup"
18 )
19
20 var (
21 Web = fakeSearch("web")
22 Image = fakeSearch("image")
23 Video = fakeSearch("video")
24 )
25
26 type Result string
27 type Search func(ctx context.Context, query string) (Result, error)
28
29 func fakeSearch(kind string) Search {
30 return func(_ context.Context, query string) (Result, error) {
31 return Result(fmt.Sprintf("%s result for %q", kind, query)), nil
32 }
33 }
34
35
36
37
38 func ExampleGroup_justErrors() {
39 g := new(errgroup.Group)
40 var urls = []string{
41 "http://www.golang.org/",
42 "http://www.google.com/",
43 "http://www.somestupidname.com/",
44 }
45 for _, url := range urls {
46
47 url := url
48 g.Go(func() error {
49
50 resp, err := http.Get(url)
51 if err == nil {
52 resp.Body.Close()
53 }
54 return err
55 })
56 }
57
58 if err := g.Wait(); err == nil {
59 fmt.Println("Successfully fetched all URLs.")
60 }
61 }
62
63
64
65
66
67 func ExampleGroup_parallel() {
68 Google := func(ctx context.Context, query string) ([]Result, error) {
69 g, ctx := errgroup.WithContext(ctx)
70
71 searches := []Search{Web, Image, Video}
72 results := make([]Result, len(searches))
73 for i, search := range searches {
74 i, search := i, search
75 g.Go(func() error {
76 result, err := search(ctx, query)
77 if err == nil {
78 results[i] = result
79 }
80 return err
81 })
82 }
83 if err := g.Wait(); err != nil {
84 return nil, err
85 }
86 return results, nil
87 }
88
89 results, err := Google(context.Background(), "golang")
90 if err != nil {
91 fmt.Fprintln(os.Stderr, err)
92 return
93 }
94 for _, result := range results {
95 fmt.Println(result)
96 }
97
98
99
100
101
102 }
103
104 func TestZeroGroup(t *testing.T) {
105 err1 := errors.New("errgroup_test: 1")
106 err2 := errors.New("errgroup_test: 2")
107
108 cases := []struct {
109 errs []error
110 }{
111 {errs: []error{}},
112 {errs: []error{nil}},
113 {errs: []error{err1}},
114 {errs: []error{err1, nil}},
115 {errs: []error{err1, nil, err2}},
116 }
117
118 for _, tc := range cases {
119 g := new(errgroup.Group)
120
121 var firstErr error
122 for i, err := range tc.errs {
123 err := err
124 g.Go(func() error { return err })
125
126 if firstErr == nil && err != nil {
127 firstErr = err
128 }
129
130 if gErr := g.Wait(); gErr != firstErr {
131 t.Errorf("after %T.Go(func() error { return err }) for err in %v\n"+
132 "g.Wait() = %v; want %v",
133 g, tc.errs[:i+1], err, firstErr)
134 }
135 }
136 }
137 }
138
139 func TestWithContext(t *testing.T) {
140 errDoom := errors.New("group_test: doomed")
141
142 cases := []struct {
143 errs []error
144 want error
145 }{
146 {want: nil},
147 {errs: []error{nil}, want: nil},
148 {errs: []error{errDoom}, want: errDoom},
149 {errs: []error{errDoom, nil}, want: errDoom},
150 }
151
152 for _, tc := range cases {
153 g, ctx := errgroup.WithContext(context.Background())
154
155 for _, err := range tc.errs {
156 err := err
157 g.Go(func() error { return err })
158 }
159
160 if err := g.Wait(); err != tc.want {
161 t.Errorf("after %T.Go(func() error { return err }) for err in %v\n"+
162 "g.Wait() = %v; want %v",
163 g, tc.errs, err, tc.want)
164 }
165
166 canceled := false
167 select {
168 case <-ctx.Done():
169 canceled = true
170 default:
171 }
172 if !canceled {
173 t.Errorf("after %T.Go(func() error { return err }) for err in %v\n"+
174 "ctx.Done() was not closed",
175 g, tc.errs)
176 }
177 }
178 }
179
180 func TestTryGo(t *testing.T) {
181 g := &errgroup.Group{}
182 n := 42
183 g.SetLimit(42)
184 ch := make(chan struct{})
185 fn := func() error {
186 ch <- struct{}{}
187 return nil
188 }
189 for i := 0; i < n; i++ {
190 if !g.TryGo(fn) {
191 t.Fatalf("TryGo should succeed but got fail at %d-th call.", i)
192 }
193 }
194 if g.TryGo(fn) {
195 t.Fatalf("TryGo is expected to fail but succeeded.")
196 }
197 go func() {
198 for i := 0; i < n; i++ {
199 <-ch
200 }
201 }()
202 g.Wait()
203
204 if !g.TryGo(fn) {
205 t.Fatalf("TryGo should success but got fail after all goroutines.")
206 }
207 go func() { <-ch }()
208 g.Wait()
209
210
211 g.SetLimit(1)
212 if !g.TryGo(fn) {
213 t.Fatalf("TryGo should success but got failed.")
214 }
215 if g.TryGo(fn) {
216 t.Fatalf("TryGo should fail but succeeded.")
217 }
218 go func() { <-ch }()
219 g.Wait()
220
221
222 g.SetLimit(0)
223 for i := 0; i < 1<<10; i++ {
224 if g.TryGo(fn) {
225 t.Fatalf("TryGo should fail but got succeded.")
226 }
227 }
228 g.Wait()
229 }
230
231 func TestGoLimit(t *testing.T) {
232 const limit = 10
233
234 g := &errgroup.Group{}
235 g.SetLimit(limit)
236 var active int32
237 for i := 0; i <= 1<<10; i++ {
238 g.Go(func() error {
239 n := atomic.AddInt32(&active, 1)
240 if n > limit {
241 return fmt.Errorf("saw %d active goroutines; want ≤ %d", n, limit)
242 }
243 time.Sleep(1 * time.Microsecond)
244 atomic.AddInt32(&active, -1)
245 return nil
246 })
247 }
248 if err := g.Wait(); err != nil {
249 t.Fatal(err)
250 }
251 }
252
253 func BenchmarkGo(b *testing.B) {
254 fn := func() {}
255 g := &errgroup.Group{}
256 b.ResetTimer()
257 b.ReportAllocs()
258 for i := 0; i < b.N; i++ {
259 g.Go(func() error { fn(); return nil })
260 }
261 g.Wait()
262 }
263
View as plain text