github.com/erda-project/erda-infra@v1.0.10-0.20240327085753-f3a249292aeb/pkg/parallel/group_test.go (about) 1 // Copyright (c) 2021 Terminus, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package parallel 16 17 import ( 18 "context" 19 "errors" 20 "fmt" 21 "net/http" 22 "os" 23 "testing" 24 ) 25 26 var ( 27 Web = fakeSearch("web") 28 Image = fakeSearch("image") 29 Video = fakeSearch("video") 30 ) 31 32 type Result string 33 type Search func(ctx context.Context, query string) (Result, error) 34 35 func fakeSearch(kind string) Search { 36 return func(_ context.Context, query string) (Result, error) { 37 return Result(fmt.Sprintf("%s result for %q", kind, query)), nil 38 } 39 } 40 41 // JustErrors illustrates the use of a Group in place of a sync.WaitGroup to 42 // simplify goroutine counting and error handling. This example is derived from 43 // the sync.WaitGroup example at https://golang.org/pkg/sync/#example_WaitGroup. 44 func ExampleGroup_justErrors() { 45 g := GoGroup(context.Background()) 46 var urls = []string{ 47 "http://www.golang.org/", 48 "http://www.google.com/", 49 "http://www.somestupidname.com/", 50 } 51 for _, url := range urls { 52 // Launch a goroutine to fetch the URL. 53 url := url // https://golang.org/doc/faq#closures_and_goroutines 54 g.Go(func(ctx context.Context) error { 55 // Fetch the URL. 56 resp, err := http.Get(url) 57 if err == nil { 58 resp.Body.Close() 59 } 60 return err 61 }) 62 } 63 // Wait for all HTTP fetches to complete. 64 if err := g.Wait(); err == nil { 65 fmt.Println("Successfully fetched all URLs.") 66 } 67 } 68 69 // Parallel illustrates the use of a Group for synchronizing a simple parallel 70 // task: the "Google Search 2.0" function from 71 // https://talks.golang.org/2012/concurrency.slide#46, augmented with a Context 72 // and error-handling. 73 func ExampleGroup_parallel() { 74 Google := func(ctx context.Context, query string) ([]Result, error) { 75 76 g := GoGroup(context.Background()) 77 78 searches := []Search{Web, Image, Video} 79 results := make([]Result, len(searches)) 80 for i, search := range searches { 81 i, search := i, search // https://golang.org/doc/faq#closures_and_goroutines 82 g.Go(func(ctx context.Context) error { 83 result, err := search(ctx, query) 84 if err == nil { 85 results[i] = result 86 } 87 return err 88 }) 89 } 90 if err := g.Wait(); err != nil { 91 return nil, err 92 } 93 return results, nil 94 } 95 96 results, err := Google(context.Background(), "golang") 97 if err != nil { 98 fmt.Fprintln(os.Stderr, err) 99 return 100 } 101 for _, result := range results { 102 fmt.Println(result) 103 } 104 105 // Output: 106 // web result for "golang" 107 // image result for "golang" 108 // video result for "golang" 109 } 110 111 func TestZeroGroup(t *testing.T) { 112 err1 := errors.New("errgroup_test: 1") 113 err2 := errors.New("errgroup_test: 2") 114 115 cases := []struct { 116 errs []error 117 }{ 118 {errs: []error{}}, 119 {errs: []error{nil}}, 120 {errs: []error{err1}}, 121 {errs: []error{err1, nil}}, 122 {errs: []error{err1, nil, err2}}, 123 } 124 125 for _, tc := range cases { 126 g := GoGroup(context.Background()) 127 128 var firstErr error 129 for i, err := range tc.errs { 130 err := err 131 g.Go(func(context.Context) error { return err }) 132 133 if firstErr == nil && err != nil { 134 firstErr = err 135 } 136 137 if gErr := g.Wait(); gErr != firstErr { 138 t.Errorf("after %T.Go(func() error { return err }) for err in %v\n"+ 139 "g.Wait() = %v; want %v", 140 g, tc.errs[:i+1], err, firstErr) 141 } 142 } 143 } 144 } 145 146 func TestWithContext(t *testing.T) { 147 errDoom := errors.New("group_test: doomed") 148 149 cases := []struct { 150 errs []error 151 want error 152 }{ 153 {want: nil}, 154 {errs: []error{nil}, want: nil}, 155 {errs: []error{errDoom}, want: errDoom}, 156 {errs: []error{errDoom, nil}, want: errDoom}, 157 } 158 159 for _, tc := range cases { 160 g := GoGroup(context.Background()) 161 162 for _, err := range tc.errs { 163 err := err 164 g.Go(func(context.Context) error { return err }) 165 } 166 167 if err := g.Wait(); err != tc.want { 168 t.Errorf("after %T.Go(func() error { return err }) for err in %v\n"+ 169 "g.Wait() = %v; want %v", 170 g, tc.errs, err, tc.want) 171 } 172 173 canceled := false 174 select { 175 case <-g.ctx.Done(): 176 canceled = true 177 default: 178 } 179 if !canceled { 180 t.Errorf("after %T.Go(func() error { return err }) for err in %v\n"+ 181 "ctx.Done() was not closed", 182 g, tc.errs) 183 } 184 } 185 }