<|start_filename|>errgroup.go<|end_filename|>
// Package errgroup provides a Group that is capable of collecting errors
// as it waits for a collection of goroutines to finish.
package errgroup

import (
	"bytes"
	"sync"
)

// MultiError allows returning a group of errors as one error.
type MultiError []error

// Error returns a concatenated string of all contained errors.
func (m MultiError) Error() string {
	l := len(m)
	if l == 0 {
		panic("MultiError with no errors")
	}
	if l == 1 {
		panic("MultiError with only 1 error")
	}
	var b bytes.Buffer
	b.WriteString("multiple errors: ")
	for i, e := range m {
		b.WriteString(e.Error())
		if i != l-1 {
			b.WriteString(" | ")
		}
	}
	return b.String()
}

// NewMultiError returns nil if all input errors passed in are nil. Otherwise,
// it coalesces all input errors into a single error instance. Useful for
// code like this:
//
//   func doThisAndThat() error {
//     err1 := tryThis()
//     err2 := tryThat()
//     return errgroup.NewMultiError(err1, err2)
//   }
//
func NewMultiError(errs ...error) error {
	var multiErr MultiError
	for _, err := range errs {
		if err != nil {
			multiErr = append(multiErr, err)
		}
	}

	if len(multiErr) == 1 {
		return multiErr[0]
	} else if len(multiErr) > 1 {
		return multiErr
	}
	return nil
}

// Group is similar to a sync.WaitGroup, but allows for collecting errors.
// The collected errors are never reset, so unlike a sync.WaitGroup, this Group
// can only be used _once_. That is, you may only call Wait on it once.
type Group struct {
	wg     sync.WaitGroup
	mu     sync.Mutex
	errors MultiError
}

// Add adds delta, which may be negative. See sync.WaitGroup.Add documentation
// for details.
func (g *Group) Add(delta int) {
	g.wg.Add(delta)
}

// Done decrements the Group counter.
func (g *Group) Done() {
	g.wg.Done()
}

// Error adds an error to return in Wait. The error must not be nil.
func (g *Group) Error(e error) {
	if e == nil {
		panic("error must not be nil")
	}
	g.mu.Lock()
	defer g.mu.Unlock()
	g.errors = append(g.errors, e)
}

// Wait blocks until the Group counter is zero. If no errors were recorded, it
// returns nil. If one error was recorded, it returns it as is. If more than
// one error was recorded it returns a MultiError which is a slice of errors.
func (g *Group) Wait() error {
	g.wg.Wait()
	g.mu.Lock()
	defer g.mu.Unlock()
	errors := g.errors
	l := len(errors)
	if l == 0 {
		return nil
	}
	if l == 1 {
		return errors[0]
	}
	return errors
}


<|start_filename|>errgroup_test.go<|end_filename|>
package errgroup_test

import (
	"errors"
	"testing"

	"github.com/facebookgo/ensure"
	"github.com/facebookgo/errgroup"
)

func TestNada(t *testing.T) {
	t.Parallel()
	var g errgroup.Group
	ensure.Nil(t, g.Wait())
}

func TestOneError(t *testing.T) {
	t.Parallel()
	e := errors.New("")
	var g errgroup.Group
	g.Error(e)
	ensure.True(t, g.Wait() == e)
}

func TestTwoErrors(t *testing.T) {
	t.Parallel()
	e1 := errors.New("e1")
	e2 := errors.New("e2")
	var g errgroup.Group
	g.Error(e1)
	g.Error(e2)
	ensure.DeepEqual(t, g.Wait().Error(), "multiple errors: e1 | e2")
}

func TestInvalidNilError(t *testing.T) {
	defer ensure.PanicDeepEqual(t, "error must not be nil")
	(&errgroup.Group{}).Error(nil)
}

func TestInvalidZeroLengthMultiError(t *testing.T) {
	defer ensure.PanicDeepEqual(t, "MultiError with no errors")
	(errgroup.MultiError{}).Error()
}

func TestInvalidOneLengthMultiError(t *testing.T) {
	defer ensure.PanicDeepEqual(t, "MultiError with only 1 error")
	(errgroup.MultiError{errors.New("")}).Error()
}

func TestAddDone(t *testing.T) {
	t.Parallel()
	var g errgroup.Group
	l := 10
	g.Add(l)
	for i := 0; i < l; i++ {
		go g.Done()
	}
	ensure.Nil(t, g.Wait())
}

func TestNewMultiError(t *testing.T) {
	t.Parallel()

	ensure.Nil(t, errgroup.NewMultiError())
	ensure.Nil(t, errgroup.NewMultiError(nil))
	ensure.Nil(t, errgroup.NewMultiError(nil, nil))

	errA := errors.New("err a")
	errB := errors.New("err b")

	// When only one non-nil error is provided, NewMultiError should return that
	// error without wrapping it in a MultiError.
	ensure.DeepEqual(t, errgroup.NewMultiError(errA), errA)
	ensure.DeepEqual(t, errgroup.NewMultiError(errA, nil), errA)
	ensure.DeepEqual(t, errgroup.NewMultiError(nil, errB), errB)

	// When more than one non-nil error is provided, than NewMultiError should
	// return a MultiError instance.
	multiErrAB := errgroup.NewMultiError(errA, errB).(errgroup.MultiError)
	multiErrNilAB := errgroup.NewMultiError(nil, errA, errB).(errgroup.MultiError)
	multiErrANilB := errgroup.NewMultiError(errA, nil, errB).(errgroup.MultiError)
	multiErrABNil := errgroup.NewMultiError(errA, errB, nil).(errgroup.MultiError)

	expected := errgroup.MultiError{errA, errB}
	ensure.DeepEqual(t, multiErrAB, expected)
	ensure.DeepEqual(t, multiErrNilAB, expected)
	ensure.DeepEqual(t, multiErrANilB, expected)
	ensure.DeepEqual(t, multiErrABNil, expected)
}
