package safe import ( "context" "errors" "sync" "testing" "time" "github.com/cenkalti/backoff/v4" ) func TestNewPoolContext(t *testing.T) { type testKeyType string testKey := testKeyType("test") ctx := context.WithValue(context.Background(), testKey, "test") p := NewPool(ctx) p.GoCtx(func(ctx context.Context) { retCtxVal, ok := ctx.Value(testKey).(string) if !ok || retCtxVal != "test" { t.Errorf("Pool.Ctx() did not return a derived context, got %#v, expected context with test value", ctx) } }) p.Stop() } type fakeRoutine struct { sync.Mutex started bool startSig chan bool } func newFakeRoutine() *fakeRoutine { return &fakeRoutine{ startSig: make(chan bool), } } func (tr *fakeRoutine) routineCtx(ctx context.Context) { tr.Lock() tr.started = true tr.Unlock() tr.startSig <- true <-ctx.Done() } func TestPoolWithCtx(t *testing.T) { testRoutine := newFakeRoutine() testCases := []struct { desc string fn func(*Pool) }{ { desc: "GoCtx()", fn: func(p *Pool) { p.GoCtx(testRoutine.routineCtx) }, }, } for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { // These subtests cannot be run in parallel, since the testRoutine // is shared across the subtests. p := NewPool(context.Background()) timer := time.NewTimer(500 * time.Millisecond) defer timer.Stop() test.fn(p) defer p.Stop() testDone := make(chan bool, 1) go func() { <-testRoutine.startSig p.Stop() testDone <- true }() select { case <-timer.C: testRoutine.Lock() defer testRoutine.Unlock() t.Fatalf("Pool test did not complete in time, goroutine started equals '%t'", testRoutine.started) case <-testDone: return } }) } } func TestPoolCleanupWithGoPanicking(t *testing.T) { p := NewPool(context.Background()) timer := time.NewTimer(500 * time.Millisecond) defer timer.Stop() p.GoCtx(func(ctx context.Context) { panic("BOOM") }) testDone := make(chan bool, 1) go func() { p.Stop() testDone <- true }() select { case <-timer.C: t.Fatalf("Pool.Cleanup() did not complete in time with a panicking goroutine") case <-testDone: return } } func TestGoroutineRecover(t *testing.T) { // if recover fails the test will panic Go(func() { panic("BOOM") }) } func TestOperationWithRecover(t *testing.T) { operation := func() error { return nil } err := backoff.Retry(OperationWithRecover(operation), &backoff.StopBackOff{}) if err != nil { t.Fatalf("Error in OperationWithRecover: %s", err) } } func TestOperationWithRecoverPanic(t *testing.T) { operation := func() error { panic("BOOM") } err := backoff.Retry(OperationWithRecover(operation), &backoff.StopBackOff{}) if err == nil { t.Fatalf("Error in OperationWithRecover: %s", err) } } func TestOperationWithRecoverError(t *testing.T) { operation := func() error { return errors.New("ERROR") } err := backoff.Retry(OperationWithRecover(operation), &backoff.StopBackOff{}) if err == nil { t.Fatalf("Error in OperationWithRecover: %s", err) } }