123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288 |
- 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)
- }
- }
- }
|