...

Source file src/golang.org/x/sync/errgroup/errgroup_test.go

Documentation: golang.org/x/sync/errgroup

     1  // Copyright 2016 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     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  // JustErrors illustrates the use of a Group in place of a sync.WaitGroup to
    36  // simplify goroutine counting and error handling. This example is derived from
    37  // the sync.WaitGroup example at https://golang.org/pkg/sync/#example_WaitGroup.
    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  		// Launch a goroutine to fetch the URL.
    47  		url := url // https://golang.org/doc/faq#closures_and_goroutines
    48  		g.Go(func() error {
    49  			// Fetch the URL.
    50  			resp, err := http.Get(url)
    51  			if err == nil {
    52  				resp.Body.Close()
    53  			}
    54  			return err
    55  		})
    56  	}
    57  	// Wait for all HTTP fetches to complete.
    58  	if err := g.Wait(); err == nil {
    59  		fmt.Println("Successfully fetched all URLs.")
    60  	}
    61  }
    62  
    63  // Parallel illustrates the use of a Group for synchronizing a simple parallel
    64  // task: the "Google Search 2.0" function from
    65  // https://talks.golang.org/2012/concurrency.slide#46, augmented with a Context
    66  // and error-handling.
    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 // https://golang.org/doc/faq#closures_and_goroutines
    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  	// Output:
    99  	// web result for "golang"
   100  	// image result for "golang"
   101  	// video result for "golang"
   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  	// Switch limit.
   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  	// Block all calls.
   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) // Give other goroutines a chance to increment active.
   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