mirror of https://github.com/hashicorp/consul
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
263 lines
5.4 KiB
263 lines
5.4 KiB
// Copyright (c) HashiCorp, Inc. |
|
// SPDX-License-Identifier: MPL-2.0 |
|
|
|
package retry |
|
|
|
import ( |
|
"fmt" |
|
"testing" |
|
"time" |
|
|
|
"github.com/stretchr/testify/assert" |
|
"github.com/stretchr/testify/require" |
|
) |
|
|
|
// delta defines the time band a test run should complete in. |
|
var delta = 25 * time.Millisecond |
|
|
|
func TestRetryer(t *testing.T) { |
|
tests := []struct { |
|
desc string |
|
r Retryer |
|
}{ |
|
{"counter", &Counter{Count: 3, Wait: 100 * time.Millisecond}}, |
|
{"timer", &Timer{Timeout: 200 * time.Millisecond, Wait: 100 * time.Millisecond}}, |
|
} |
|
|
|
for _, tt := range tests { |
|
t.Run(tt.desc, func(t *testing.T) { |
|
var iters int |
|
start := time.Now() |
|
for tt.r.Continue() { |
|
iters++ |
|
} |
|
dur := time.Since(start) |
|
if got, want := iters, 3; got != want { |
|
t.Fatalf("got %d retries want %d", got, want) |
|
} |
|
// since the first iteration happens immediately |
|
// the retryer waits only twice for three iterations. |
|
// order of events: (true, (wait) true, (wait) true, false) |
|
if got, want := dur, 200*time.Millisecond; got < (want-delta) || got > (want+delta) { |
|
t.Fatalf("loop took %v want %v (+/- %v)", got, want, delta) |
|
} |
|
}) |
|
} |
|
} |
|
|
|
func TestBasics(t *testing.T) { |
|
t.Run("Error allows retry", func(t *testing.T) { |
|
i := 0 |
|
Run(t, func(r *R) { |
|
i++ |
|
t.Logf("i: %d; r: %#v", i, r) |
|
if i == 1 { |
|
r.Errorf("Errorf, i: %d", i) |
|
return |
|
} |
|
}) |
|
assert.Equal(t, i, 2) |
|
}) |
|
|
|
t.Run("Fatal returns from func, but does not fail test", func(t *testing.T) { |
|
i := 0 |
|
gotHere := false |
|
ft := &fakeT{T: t} |
|
Run(ft, func(r *R) { |
|
i++ |
|
t.Logf("i: %d; r: %#v", i, r) |
|
if i == 1 { |
|
r.Fatalf("Fatalf, i: %d", i) |
|
gotHere = true |
|
} |
|
}) |
|
|
|
assert.False(t, gotHere) |
|
assert.Equal(t, i, 2) |
|
// surprisingly, r.FailNow() *does not* trigger ft.FailNow()! |
|
assert.Equal(t, ft.fails, 0) |
|
}) |
|
|
|
t.Run("Func being run can panic with struct{}{}", func(t *testing.T) { |
|
gotPanic := false |
|
func() { |
|
defer func() { |
|
if p := recover(); p != nil { |
|
gotPanic = true |
|
} |
|
}() |
|
Run(t, func(r *R) { |
|
panic(struct{}{}) |
|
}) |
|
}() |
|
|
|
assert.True(t, gotPanic) |
|
}) |
|
} |
|
|
|
func TestRunWith(t *testing.T) { |
|
t.Run("calls FailNow after exceeding retries", func(t *testing.T) { |
|
ft := &fakeT{T: t} |
|
iter := 0 |
|
RunWith(&Counter{Count: 3, Wait: time.Millisecond}, ft, func(r *R) { |
|
iter++ |
|
r.FailNow() |
|
}) |
|
|
|
require.Equal(t, 3, iter) |
|
require.Equal(t, 1, ft.fails) |
|
}) |
|
|
|
t.Run("Stop ends the retrying", func(t *testing.T) { |
|
ft := &fakeT{T: t} |
|
iter := 0 |
|
RunWith(&Counter{Count: 5, Wait: time.Millisecond}, ft, func(r *R) { |
|
iter++ |
|
if iter == 2 { |
|
r.Stop(fmt.Errorf("do not proceed")) |
|
} |
|
r.Fatalf("not yet") |
|
}) |
|
|
|
// TODO: these should all be assert |
|
require.Equal(t, 2, iter) |
|
require.Equal(t, 1, ft.fails) |
|
require.Len(t, ft.out, 1) |
|
require.Contains(t, ft.out[0], "not yet\n") |
|
require.Contains(t, ft.out[0], "do not proceed\n") |
|
}) |
|
} |
|
|
|
func TestCleanup_Passthrough(t *testing.T) { |
|
|
|
} |
|
|
|
func TestCleanup(t *testing.T) { |
|
|
|
t.Run("basic", func(t *testing.T) { |
|
ft := &fakeT{T: t} |
|
cleanupsExecuted := 0 |
|
|
|
Run( |
|
ft, |
|
func(r *R) { |
|
r.Cleanup(func() { |
|
cleanupsExecuted += 1 |
|
}) |
|
}, |
|
WithImmediateCleanup(), |
|
WithRetryer(&Counter{Count: 2, Wait: time.Millisecond}), |
|
) |
|
|
|
require.Equal(t, 0, ft.fails) |
|
require.Equal(t, 1, cleanupsExecuted) |
|
}) |
|
t.Run("cleanup-panic-recovery", func(t *testing.T) { |
|
ft := &fakeT{T: t} |
|
cleanupsExecuted := 0 |
|
Run( |
|
ft, |
|
func(r *R) { |
|
r.Cleanup(func() { |
|
cleanupsExecuted += 1 |
|
}) |
|
|
|
r.Cleanup(func() { |
|
cleanupsExecuted += 1 |
|
panic(fmt.Errorf("fake test error")) |
|
}) |
|
|
|
r.Cleanup(func() { |
|
cleanupsExecuted += 1 |
|
}) |
|
|
|
// test is successful but should fail due to the cleanup panicing |
|
}, |
|
WithRetryer(&Counter{Count: 2, Wait: time.Millisecond}), |
|
WithImmediateCleanup(), |
|
) |
|
|
|
require.Equal(t, 3, cleanupsExecuted) |
|
require.Equal(t, 1, ft.fails) |
|
require.Contains(t, ft.out[0], "fake test error") |
|
}) |
|
|
|
t.Run("cleanup-per-retry", func(t *testing.T) { |
|
ft := &fakeT{T: t} |
|
iter := 0 |
|
cleanupsExecuted := 0 |
|
Run( |
|
ft, |
|
func(r *R) { |
|
if cleanupsExecuted != iter { |
|
r.Stop(fmt.Errorf("cleanups not executed between retries")) |
|
return |
|
} |
|
iter += 1 |
|
|
|
r.Cleanup(func() { |
|
cleanupsExecuted += 1 |
|
}) |
|
|
|
r.FailNow() |
|
}, |
|
WithRetryer(&Counter{Count: 3, Wait: time.Millisecond}), |
|
WithImmediateCleanup(), |
|
) |
|
|
|
require.Equal(t, 3, cleanupsExecuted) |
|
// ensure that r.Stop hadn't been called. If it was then we would |
|
// have log output |
|
require.Len(t, ft.out, 0) |
|
}) |
|
|
|
t.Run("passthrough-to-t", func(t *testing.T) { |
|
cleanupsExecuted := 0 |
|
|
|
require.True(t, t.Run("internal", func(t *testing.T) { |
|
iter := 0 |
|
Run( |
|
t, |
|
func(r *R) { |
|
iter++ |
|
|
|
r.Cleanup(func() { |
|
cleanupsExecuted += 1 |
|
}) |
|
|
|
// fail all but the last one to ensure the right number of cleanups |
|
// are eventually executed |
|
if iter < 3 { |
|
r.FailNow() |
|
} |
|
}, |
|
WithRetryer(&Counter{Count: 3, Wait: time.Millisecond}), |
|
) |
|
|
|
// at this point nothing should be cleaned up |
|
require.Equal(t, 0, cleanupsExecuted) |
|
})) |
|
|
|
// now since the subtest finished the test cleanup funcs |
|
// should have been executed. |
|
require.Equal(t, 3, cleanupsExecuted) |
|
}) |
|
} |
|
|
|
type fakeT struct { |
|
fails int |
|
out []string |
|
*testing.T |
|
} |
|
|
|
func (f *fakeT) Helper() {} |
|
|
|
func (f *fakeT) Log(args ...interface{}) { |
|
f.out = append(f.out, fmt.Sprint(args...)) |
|
} |
|
|
|
func (f *fakeT) FailNow() { |
|
f.fails++ |
|
} |
|
|
|
var _ TestingTB = &fakeT{}
|
|
|