package errgroup import ( "errors" "fmt" "math" "net/http" "os" "testing" "time" "golang.org/x/net/context" ) type ABC struct { CBA int } func TestNormal(t *testing.T) { var ( abcs = make(map[int]*ABC) g Group err error ) for i := 0; i < 10; i++ { abcs[i] = &ABC{CBA: i} } g.Go(func() (err error) { abcs[1].CBA++ return }) g.Go(func() (err error) { abcs[2].CBA++ return }) if err = g.Wait(); err != nil { t.Log(err) } t.Log(abcs) } func sleep1s() error { time.Sleep(time.Second) return nil } func TestGOMAXPROCS(t *testing.T) { // 没有并发数限制 g := Group{} now := time.Now() g.Go(sleep1s) g.Go(sleep1s) g.Go(sleep1s) g.Go(sleep1s) g.Wait() sec := math.Round(time.Since(now).Seconds()) if sec != 1 { t.FailNow() } // 限制并发数 g2 := Group{} g2.GOMAXPROCS(2) now = time.Now() g2.Go(sleep1s) g2.Go(sleep1s) g2.Go(sleep1s) g2.Go(sleep1s) g2.Wait() sec = math.Round(time.Since(now).Seconds()) if sec != 2 { t.FailNow() } // context canceled var canceled bool g3, ctx := WithContext(context.Background()) g3.GOMAXPROCS(2) g3.Go(func() error { return fmt.Errorf("error for testing errgroup context") }) g3.Go(func() error { time.Sleep(time.Second) select { case <-ctx.Done(): canceled = true default: } return nil }) g3.Wait() if !canceled { t.FailNow() } } func TestRecover(t *testing.T) { var ( abcs = make(map[int]*ABC) g Group err error ) g.Go(func() (err error) { abcs[1].CBA++ return }) g.Go(func() (err error) { abcs[2].CBA++ return }) if err = g.Wait(); err != nil { t.Logf("error:%+v", err) return } t.FailNow() } func TestRecover2(t *testing.T) { var ( g Group err error ) g.Go(func() (err error) { panic("2233") }) if err = g.Wait(); err != nil { t.Logf("error:%+v", err) return } t.FailNow() } var ( Web = fakeSearch("web") Image = fakeSearch("image") Video = fakeSearch("video") ) type Result string type Search func(ctx context.Context, query string) (Result, error) func fakeSearch(kind string) Search { return func(_ context.Context, query string) (Result, error) { return Result(fmt.Sprintf("%s result for %q", kind, query)), nil } } // JustErrors illustrates the use of a Group in place of a sync.WaitGroup to // simplify goroutine counting and error handling. This example is derived from // the sync.WaitGroup example at https://golang.org/pkg/sync/#example_WaitGroup. func ExampleGroup_justErrors() { var g Group var urls = []string{ "http://www.golang.org/", "http://www.google.com/", "http://www.somestupidname.com/", } for _, url := range urls { // Launch a goroutine to fetch the URL. url := url // https://golang.org/doc/faq#closures_and_goroutines g.Go(func() error { // Fetch the URL. resp, err := http.Get(url) if err == nil { resp.Body.Close() } return err }) } // Wait for all HTTP fetches to complete. if err := g.Wait(); err == nil { fmt.Println("Successfully fetched all URLs.") } } // Parallel illustrates the use of a Group for synchronizing a simple parallel // task: the "Google Search 2.0" function from // https://talks.golang.org/2012/concurrency.slide#46, augmented with a Context // and error-handling. func ExampleGroup_parallel() { Google := func(ctx context.Context, query string) ([]Result, error) { g, ctx := WithContext(ctx) searches := []Search{Web, Image, Video} results := make([]Result, len(searches)) for i, search := range searches { i, search := i, search // https://golang.org/doc/faq#closures_and_goroutines g.Go(func() error { result, err := search(ctx, query) if err == nil { results[i] = result } return err }) } if err := g.Wait(); err != nil { return nil, err } return results, nil } results, err := Google(context.Background(), "golang") if err != nil { fmt.Fprintln(os.Stderr, err) return } for _, result := range results { fmt.Println(result) } // Output: // web result for "golang" // image result for "golang" // video result for "golang" } func TestZeroGroup(t *testing.T) { err1 := errors.New("errgroup_test: 1") err2 := errors.New("errgroup_test: 2") cases := []struct { errs []error }{ {errs: []error{}}, {errs: []error{nil}}, {errs: []error{err1}}, {errs: []error{err1, nil}}, {errs: []error{err1, nil, err2}}, } for _, tc := range cases { var g Group var firstErr error for i, err := range tc.errs { err := err g.Go(func() error { return err }) if firstErr == nil && err != nil { firstErr = err } if gErr := g.Wait(); gErr != firstErr { t.Errorf("after g.Go(func() error { return err }) for err in %v\n"+ "g.Wait() = %v; want %v", tc.errs[:i+1], err, firstErr) } } } } func TestWithContext(t *testing.T) { errDoom := errors.New("group_test: doomed") cases := []struct { errs []error want error }{ {want: nil}, {errs: []error{nil}, want: nil}, {errs: []error{errDoom}, want: errDoom}, {errs: []error{errDoom, nil}, want: errDoom}, } for _, tc := range cases { g, ctx := WithContext(context.Background()) for _, err := range tc.errs { err := err g.Go(func() error { return err }) } if err := g.Wait(); err != tc.want { t.Errorf("after %T.Go(func() error { return err }) for err in %v\n"+ "g.Wait() = %v; want %v", g, tc.errs, err, tc.want) } canceled := false select { case <-ctx.Done(): canceled = true default: } if !canceled { t.Errorf("after %T.Go(func() error { return err }) for err in %v\n"+ "ctx.Done() was not closed", g, tc.errs) } } }