go-ethereum/common/countdown/countdown_test.go
Daniel Liu 0581dec6b7
fix(common/countdown): stabilize reset timing assertions (#2120)
Fix flaky countdown reset tests by performing an explicit second Reset before validating the second timeout window, and using a boundary-safe time check.

Observed failure:

--- FAIL: TestCountdownShouldReset (14.00s)
    countdown_test.go:53: Correctly reset the countdown once
    countdown_test.go:72: Countdown did not reset correctly second time
2026-03-10 18:42:58 +05:30

166 lines
4.6 KiB
Go

package countdown
import (
"errors"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestCountdownWillCallback(t *testing.T) {
var fakeI interface{}
called := make(chan int)
OnTimeoutFn := func(time.Time, interface{}) error {
called <- 1
return nil
}
countdown, err := NewExpCountDown(1000*time.Millisecond, 0, 0)
assert.Nil(t, err)
countdown.OnTimeoutFn = OnTimeoutFn
countdown.Reset(fakeI, 0, 0)
<-called
t.Log("Times up, successfully called OnTimeoutFn")
}
func TestCountdownShouldReset(t *testing.T) {
var fakeI interface{}
called := make(chan int)
OnTimeoutFn := func(time.Time, interface{}) error {
called <- 1
return nil
}
countdown, err := NewExpCountDown(5000*time.Millisecond, 0, 0)
assert.Nil(t, err)
countdown.OnTimeoutFn = OnTimeoutFn
// Check countdown did not start
assert.False(t, countdown.isInitilised())
countdown.Reset(fakeI, 0, 0)
// Now the countdown should already started
assert.True(t, countdown.isInitilised())
expectedCalledTime := time.Now().Add(9000 * time.Millisecond)
resetTimer := time.NewTimer(4000 * time.Millisecond)
firstReset:
for {
select {
case <-called:
if time.Now().After(expectedCalledTime) {
// Make sure the countdown runs forever
assert.True(t, countdown.isInitilised())
t.Log("Correctly reset the countdown once")
} else {
t.Fatalf("Countdown did not reset correctly first time")
}
break firstReset
case <-resetTimer.C:
countdown.Reset(fakeI, 0, 0)
}
}
// The countdown keeps running after calling the callback; it is still initialised.
// Reset it again to verify that an explicit Reset extends the timeout by a full interval.
assert.True(t, countdown.isInitilised())
countdown.Reset(fakeI, 0, 0)
expectedTimeAfterReset := time.Now().Add(5000 * time.Millisecond)
<-called
// Always initilised
assert.True(t, countdown.isInitilised())
if !time.Now().Before(expectedTimeAfterReset) {
t.Log("Correctly reset the countdown second time")
} else {
t.Fatalf("Countdown did not reset correctly second time")
}
}
func TestCountdownShouldResetEvenIfErrored(t *testing.T) {
var fakeI interface{}
called := make(chan int)
OnTimeoutFn := func(time.Time, interface{}) error {
called <- 1
return errors.New("ERROR!")
}
countdown, err := NewExpCountDown(5000*time.Millisecond, 0, 0)
assert.Nil(t, err)
countdown.OnTimeoutFn = OnTimeoutFn
// Check countdown did not start
assert.False(t, countdown.isInitilised())
countdown.Reset(fakeI, 0, 0)
// Now the countdown should already started
assert.True(t, countdown.isInitilised())
expectedCalledTime := time.Now().Add(9000 * time.Millisecond)
resetTimer := time.NewTimer(4000 * time.Millisecond)
firstReset:
for {
select {
case <-called:
if time.Now().After(expectedCalledTime) {
// Make sure the countdown runs forever
assert.True(t, countdown.isInitilised())
t.Log("Correctly reset the countdown once")
} else {
t.Fatalf("Countdown did not reset correctly first time")
}
break firstReset
case <-resetTimer.C:
countdown.Reset(fakeI, 0, 0)
}
}
// The countdown continues running (auto-resets) after calling the callback
// function; reset it again to verify it can still be reset after an error
assert.True(t, countdown.isInitilised())
countdown.Reset(fakeI, 0, 0)
expectedTimeAfterReset := time.Now().Add(5000 * time.Millisecond)
<-called
// Always initilised
assert.True(t, countdown.isInitilised())
if !time.Now().Before(expectedTimeAfterReset) {
t.Log("Correctly reset the countdown second time")
} else {
t.Fatalf("Countdown did not reset correctly second time")
}
}
func TestCountdownShouldBeAbleToStop(t *testing.T) {
var fakeI interface{}
called := make(chan int)
OnTimeoutFn := func(time.Time, interface{}) error {
called <- 1
return nil
}
countdown, err := NewExpCountDown(5000*time.Millisecond, 0, 0)
assert.Nil(t, err)
countdown.OnTimeoutFn = OnTimeoutFn
// Check countdown did not start
assert.False(t, countdown.isInitilised())
countdown.Reset(fakeI, 0, 0)
// Now the countdown should already started
assert.True(t, countdown.isInitilised())
// Try manually stop the timer before it triggers the callback
stopTimer := time.NewTimer(4000 * time.Millisecond)
<-stopTimer.C
countdown.StopTimer()
assert.False(t, countdown.isInitilised())
}
func TestCountdownShouldAvoidDeadlock(t *testing.T) {
var fakeI interface{}
called := make(chan int)
countdown, err := NewExpCountDown(5000*time.Millisecond, 0, 0)
assert.Nil(t, err)
OnTimeoutFn := func(time.Time, interface{}) error {
countdown.Reset(fakeI, 0, 0)
called <- 1
return nil
}
countdown.OnTimeoutFn = OnTimeoutFn
countdown.Reset(fakeI, 0, 0)
<-called
}