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

View File

@ -33,20 +33,50 @@ var (
_ = Ticker(&fakeTicker{}) _ = 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) { func TestFakeClock(t *testing.T) {
startTime := time.Now() startTime := time.Now()
tc := NewFakeClock(startTime) tc := NewFakeClock(startTime)
exercisePassiveClock(t, tc)
tc.SetTime(startTime)
tc.Step(time.Second) tc.Step(time.Second)
now := tc.Now() now := tc.Now()
if now.Sub(startTime) != time.Second { if now.Sub(startTime) != time.Second {
t.Errorf("input: %s now=%s gap=%s expected=%s", startTime, now, 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) { func TestFakeClockSleep(t *testing.T) {