Factor PassiveClock out of clock.Clock

PassiveClock has the subset of Clock functionality that only involves
reading the clock.  Identifying this subset makes it possible to write
packages that are more clearly easy to test.

When a package is coded against Clock rather than PassiveClock this
adds two problems for the unit test functions.  One is that Clock
provides no way for the test function to know when the next activity
is scheduled for.  That could be added to FakeClock relatively easily.
The second problem is that when a package uses channels to schedule
future activity, once the Clock has advanced to such a future time the
Clock (and hence the test function) does not get informed when that
activity has completed.
This commit is contained in:
Mike Spreitzer 2019-06-11 00:53:56 -04:00
parent 369b765052
commit 2e2e6b82e0
2 changed files with 69 additions and 14 deletions

View File

@ -21,11 +21,18 @@ import (
"time"
)
// PassiveClock allows for injecting fake or real clocks into code
// that needs to read the current time but does not support scheduling
// activity in the future.
type PassiveClock interface {
Now() time.Time
Since(time.Time) time.Duration
}
// Clock allows for injecting fake or real clocks into code that
// needs to do arbitrary things based on time.
type Clock interface {
Now() time.Time
Since(time.Time) time.Duration
PassiveClock
After(time.Duration) <-chan time.Time
NewTimer(time.Duration) Timer
Sleep(time.Duration)
@ -66,10 +73,15 @@ func (RealClock) Sleep(d time.Duration) {
time.Sleep(d)
}
// FakeClock implements Clock, but returns an arbitrary time.
type FakeClock struct {
// FakePassiveClock implements PassiveClock, but returns an arbitrary time.
type FakePassiveClock struct {
lock sync.RWMutex
time time.Time
}
// FakeClock implements Clock, but returns an arbitrary time.
type FakeClock struct {
FakePassiveClock
// waiters are waiting for the fake time to pass their specified time
waiters []fakeClockWaiter
@ -82,26 +94,39 @@ type fakeClockWaiter struct {
destChan chan time.Time
}
func NewFakeClock(t time.Time) *FakeClock {
return &FakeClock{
func NewFakePassiveClock(t time.Time) *FakePassiveClock {
return &FakePassiveClock{
time: t,
}
}
func NewFakeClock(t time.Time) *FakeClock {
return &FakeClock{
FakePassiveClock: *NewFakePassiveClock(t),
}
}
// Now returns f's time.
func (f *FakeClock) Now() time.Time {
func (f *FakePassiveClock) Now() time.Time {
f.lock.RLock()
defer f.lock.RUnlock()
return f.time
}
// Since returns time since the time in f.
func (f *FakeClock) Since(ts time.Time) time.Duration {
func (f *FakePassiveClock) Since(ts time.Time) time.Duration {
f.lock.RLock()
defer f.lock.RUnlock()
return f.time.Sub(ts)
}
// Sets the time.
func (f *FakePassiveClock) SetTime(t time.Time) {
f.lock.Lock()
defer f.lock.Unlock()
f.time = t
}
// Fake version of time.After(d).
func (f *FakeClock) After(d time.Duration) <-chan time.Time {
f.lock.Lock()

View File

@ -33,20 +33,50 @@ var (
_ = Ticker(&fakeTicker{})
)
type SettablePassiveClock interface {
PassiveClock
SetTime(time.Time)
}
func exercisePassiveClock(t *testing.T, pc SettablePassiveClock) {
t1 := time.Now()
t2 := t1.Add(time.Hour)
pc.SetTime(t1)
tx := pc.Now()
if tx != t1 {
t.Errorf("SetTime(%#+v); Now() => %#+v", t1, tx)
}
dx := pc.Since(t1)
if dx != 0 {
t.Errorf("Since() => %v", dx)
}
pc.SetTime(t2)
dx = pc.Since(t1)
if dx != time.Hour {
t.Errorf("Since() => %v", dx)
}
tx = pc.Now()
if tx != t2 {
t.Errorf("Now() => %#+v", tx)
}
}
func TestFakePassiveClock(t *testing.T) {
startTime := time.Now()
tc := NewFakePassiveClock(startTime)
exercisePassiveClock(t, tc)
}
func TestFakeClock(t *testing.T) {
startTime := time.Now()
tc := NewFakeClock(startTime)
exercisePassiveClock(t, tc)
tc.SetTime(startTime)
tc.Step(time.Second)
now := tc.Now()
if now.Sub(startTime) != time.Second {
t.Errorf("input: %s now=%s gap=%s expected=%s", startTime, now, now.Sub(startTime), time.Second)
}
tt := tc.Now()
tc.SetTime(tt.Add(time.Hour))
if tc.Since(tt) != time.Hour {
t.Errorf("input: %s now=%s gap=%s expected=%s", tt, tc.Now(), tc.Since(tt), time.Hour)
}
}
func TestFakeClockSleep(t *testing.T) {