From 2e2e6b82e0c55294164de51b22b319fa2728a301 Mon Sep 17 00:00:00 2001 From: Mike Spreitzer Date: Tue, 11 Jun 2019 00:53:56 -0400 Subject: [PATCH] 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. --- .../apimachinery/pkg/util/clock/clock.go | 41 ++++++++++++++---- .../apimachinery/pkg/util/clock/clock_test.go | 42 ++++++++++++++++--- 2 files changed, 69 insertions(+), 14 deletions(-) diff --git a/staging/src/k8s.io/apimachinery/pkg/util/clock/clock.go b/staging/src/k8s.io/apimachinery/pkg/util/clock/clock.go index 0d739d961f1..1689e62e829 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/clock/clock.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/clock/clock.go @@ -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() diff --git a/staging/src/k8s.io/apimachinery/pkg/util/clock/clock_test.go b/staging/src/k8s.io/apimachinery/pkg/util/clock/clock_test.go index 9707acec4b8..16967d9a0c3 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/clock/clock_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/clock/clock_test.go @@ -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) {