clock: optionally wait until tickers are registered in TestClock

This commit is contained in:
Andras Banki-Horvath 2020-05-25 16:47:37 +02:00
parent 7f1a450a7f
commit e81061bda4
2 changed files with 57 additions and 1 deletions

View File

@ -10,6 +10,7 @@ type TestClock struct {
currentTime time.Time currentTime time.Time
timeChanMap map[time.Time][]chan time.Time timeChanMap map[time.Time][]chan time.Time
timeLock sync.Mutex timeLock sync.Mutex
tickSignal chan time.Duration
} }
// NewTestClock returns a new test clock. // 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. // Now returns the current (test) time.
func (c *TestClock) Now() time.Time { func (c *TestClock) Now() time.Time {
c.timeLock.Lock() c.timeLock.Lock()
@ -32,7 +46,14 @@ func (c *TestClock) Now() time.Time {
// duration has passed passed by the user set test time. // duration has passed passed by the user set test time.
func (c *TestClock) TickAfter(duration time.Duration) <-chan time.Time { func (c *TestClock) TickAfter(duration time.Duration) <-chan time.Time {
c.timeLock.Lock() 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) triggerTime := c.currentTime.Add(duration)
ch := make(chan time.Time, 1) ch := make(chan time.Time, 1)

View File

@ -1,8 +1,11 @@
package clock package clock
import ( import (
"fmt"
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert"
) )
var ( var (
@ -42,6 +45,7 @@ func TestTickAfter(t *testing.T) {
select { select {
case <-ticker: case <-ticker:
tick = true tick = true
case <-time.After(time.Millisecond): case <-time.After(time.Millisecond):
} }
@ -61,3 +65,34 @@ func TestTickAfter(t *testing.T) {
tickOrTimeOut(ticker2, true) tickOrTimeOut(ticker2, true)
tickOrTimeOut(ticker3, false) 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)
}