waiterr/waiterr_test.go

143 lines
3.4 KiB
Go

package waiterr_test
import (
"context"
"errors"
"testing"
"testing/synctest"
"github.com/nalgeon/be"
"codeberg.org/danjones000/waiterr"
)
func TestGo(t *testing.T) {
we := waiterr.New()
err := errors.New("uh-oh")
var run bool
we.Go(func() error {
run = true
return err
})
be.Err(t, we.Wait(), err)
be.True(t, run)
}
func TestWait(t *testing.T) {
we := waiterr.New()
er1 := errors.New("uh-oh")
er2 := errors.New("oops")
we.Go(func() error { return er1 })
we.Go(func() error { return nil })
we.Go(func() error { return er2 })
err := we.Wait()
be.Err(t, err, er1, er2)
if ers, ok := err.(interface{ Unwrap() []error }); ok {
all := ers.Unwrap()
be.Equal(t, len(all), 2)
be.True(t, all[0] == er1 || all[0] == er2)
be.True(t, all[1] == er2 || all[1] == er1)
} else {
t.Fatal("Returned error should have Unwrap method")
}
}
func TestWaitForError(tt *testing.T) {
tt.Run("first error", func(t *testing.T) {
we := waiterr.New()
er1 := errors.New("uh-oh")
er2 := errors.New("oops")
we.Go(func() error { return nil })
we.Go(func() error { return er1 })
we.Go(func() error { return er2 })
err := we.WaitForError()
// Due to how goroutines run, it is possible that either of those return first. This is an acceptable limitation
be.True(t, err == er1 || err == er2)
})
tt.Run("no error", func(t *testing.T) {
we := waiterr.New()
we.Go(func() error { return nil })
we.Go(func() error { return nil })
we.Go(func() error { return nil })
err := we.WaitForError()
be.Err(t, err, nil)
})
tt.Run("first error set", func(tt2 *testing.T) {
we := waiterr.New()
expectedErr := errors.New("pre-set error")
synctest.Test(tt2, func(t *testing.T) {
we.Go(func() error { return expectedErr })
// synctest.Wait ensures that the gorouting has finished before anything else.
synctest.Wait()
we.Go(func() error { return errors.New("another error") })
synctest.Wait()
we.Go(func() error { return nil })
actualErr := we.WaitForError()
be.Err(t, actualErr, expectedErr)
})
})
}
func TestUnwrap(tt *testing.T) {
tt.Run("two errors", func(t *testing.T) {
we := waiterr.New()
er1 := errors.New("error one")
er2 := errors.New("error two")
we.Go(func() error { return er1 })
we.Go(func() error { return nil })
we.Go(func() error { return er2 })
we.Go(func() error { return nil })
_ = we.Wait() // Ensure all goroutines complete
unwrapped := we.Unwrap()
be.Equal(t, len(unwrapped), 2)
be.True(t, (unwrapped[0] == er1 && unwrapped[1] == er2) || (unwrapped[0] == er2 && unwrapped[1] == er1))
})
tt.Run("no errors", func(t *testing.T) {
weNoErr := waiterr.New()
weNoErr.Go(func() error { return nil })
weNoErr.Go(func() error { return nil })
_ = weNoErr.Wait()
be.Equal(t, weNoErr.Unwrap(), nil)
})
}
func TestWithContext(tt *testing.T) {
tt.Run("with error", func(tt2 *testing.T) {
er1 := errors.New("uh-oh")
er2 := errors.New("oops")
synctest.Test(tt2, func(t *testing.T) {
we, ctx := waiterr.WithContext(t.Context())
we.Go(func() error { return er1 })
synctest.Wait() // Ensure it finishes first
we.Go(func() error { return er2 })
er := context.Cause(ctx)
be.Err(t, er, er1)
be.True(t, !errors.Is(er, er2))
})
})
tt.Run("no error", func(t *testing.T) {
we, ctx := waiterr.WithContext(t.Context())
we.Go(func() error { return nil })
we.Go(func() error { return nil })
er := we.Wait()
be.Err(t, er, nil)
be.Err(t, context.Cause(ctx), context.Canceled)
})
}