Merge pull request #4317 from bhandras/testclock_improvement

clock: optionally wait until tickers are registered in TestClock
This commit is contained in:
Olaoluwa Osuntokun 2020-05-26 18:02:25 -07:00 committed by GitHub
commit 03e6a18212
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 60 additions and 11 deletions

@ -10,6 +10,7 @@ type TestClock struct {
currentTime time.Time
timeChanMap map[time.Time][]chan time.Time
timeLock sync.Mutex
tickSignal chan time.Duration
}
// NewTestClock returns a new test clock.
@ -20,6 +21,19 @@ func NewTestClock(startTime time.Time) *TestClock {
}
}
// NewTestClockWithTickSignal will create a new test clock with an added
// channel which will be used to signal when a new ticker is registered.
// This is useful when creating a ticker on a separate goroutine and we'd
// like to wait for that to happen before advancing the test case.
func NewTestClockWithTickSignal(startTime time.Time,
tickSignal chan time.Duration) *TestClock {
testClock := NewTestClock(startTime)
testClock.tickSignal = tickSignal
return testClock
}
// Now returns the current (test) time.
func (c *TestClock) Now() time.Time {
c.timeLock.Lock()
@ -32,7 +46,14 @@ func (c *TestClock) Now() time.Time {
// duration has passed passed by the user set test time.
func (c *TestClock) TickAfter(duration time.Duration) <-chan time.Time {
c.timeLock.Lock()
defer c.timeLock.Unlock()
defer func() {
c.timeLock.Unlock()
// Signal that the ticker has been added.
if c.tickSignal != nil {
c.tickSignal <- duration
}
}()
triggerTime := c.currentTime.Add(duration)
ch := make(chan time.Time, 1)

@ -1,8 +1,11 @@
package clock
import (
"fmt"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
var (
@ -12,16 +15,11 @@ var (
func TestNow(t *testing.T) {
c := NewTestClock(testTime)
now := c.Now()
if now != testTime {
t.Fatalf("expected: %v, got: %v", testTime, now)
}
assert.Equal(t, testTime, now)
now = now.Add(time.Hour)
c.SetTime(now)
if c.Now() != now {
t.Fatalf("epected: %v, got: %v", now, c.Now())
}
assert.Equal(t, now, c.Now())
}
func TestTickAfter(t *testing.T) {
@ -42,12 +40,11 @@ func TestTickAfter(t *testing.T) {
select {
case <-ticker:
tick = true
case <-time.After(time.Millisecond):
}
if tick != expectTick {
t.Fatalf("expected tick: %v, ticked: %v", expectTick, tick)
}
assert.Equal(t, expectTick, tick)
}
tickOrTimeOut(ticker0, true)
@ -61,3 +58,34 @@ func TestTickAfter(t *testing.T) {
tickOrTimeOut(ticker2, true)
tickOrTimeOut(ticker3, false)
}
// TestTickSignal tests that TickAfter signals registration allowing
// safe time advancement.
func TestTickSignal(t *testing.T) {
const interval = time.Second
ch := make(chan time.Duration)
c := NewTestClockWithTickSignal(testTime, ch)
err := make(chan error, 1)
go func() {
select {
// TickAfter will signal registration but will not
// tick, unless we read the signal and set the time.
case <-c.TickAfter(interval):
err <- nil
// Signal timeout if tick didn't happen.
case <-time.After(time.Second):
err <- fmt.Errorf("timeout")
}
}()
tick := <-ch
// Expect that the interval is correctly passed over the channel.
assert.Equal(t, interval, tick)
// Once the ticker is registered, set the time to make it fire.
c.SetTime(testTime.Add(time.Second))
assert.NoError(t, <-err)
}