Skip to content

Commit ab90690

Browse files
authored
Merge pull request #39 from euank/​-t
Fix 'timer.Reset' for the fake clock
2 parents 733eca4 + 9734e1d commit ab90690

File tree

2 files changed

+106
-8
lines changed

2 files changed

+106
-8
lines changed

clock/testing/fake_clock.go

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ type fakeClockWaiter struct {
4545
fired bool
4646
}
4747

48+
// NewFakeClock constructs a fake clock set to the provided time.
4849
func NewFakeClock(t time.Time) *FakeClock {
4950
return &FakeClock{
5051
time: t,
@@ -65,7 +66,7 @@ func (f *FakeClock) Since(ts time.Time) time.Duration {
6566
return f.time.Sub(ts)
6667
}
6768

68-
// Fake version of time.After(d).
69+
// After is the fake version of time.After(d).
6970
func (f *FakeClock) After(d time.Duration) <-chan time.Time {
7071
f.lock.Lock()
7172
defer f.lock.Unlock()
@@ -78,7 +79,7 @@ func (f *FakeClock) After(d time.Duration) <-chan time.Time {
7879
return ch
7980
}
8081

81-
// Fake version of time.NewTimer(d).
82+
// NewTimer constructs a fake timer, akin to time.NewTimer(d).
8283
func (f *FakeClock) NewTimer(d time.Duration) clock.Timer {
8384
f.lock.Lock()
8485
defer f.lock.Unlock()
@@ -95,7 +96,11 @@ func (f *FakeClock) NewTimer(d time.Duration) clock.Timer {
9596
return timer
9697
}
9798

99+
// Tick constructs a fake ticker, akin to time.Tick
98100
func (f *FakeClock) Tick(d time.Duration) <-chan time.Time {
101+
if d <= 0 {
102+
return nil
103+
}
99104
f.lock.Lock()
100105
defer f.lock.Unlock()
101106
tickTime := f.time.Add(d)
@@ -110,14 +115,15 @@ func (f *FakeClock) Tick(d time.Duration) <-chan time.Time {
110115
return ch
111116
}
112117

113-
// Move clock by Duration, notify anyone that's called After, Tick, or NewTimer
118+
// Step moves the clock by Duration and notifies anyone that's called After,
119+
// Tick, or NewTimer.
114120
func (f *FakeClock) Step(d time.Duration) {
115121
f.lock.Lock()
116122
defer f.lock.Unlock()
117123
f.setTimeLocked(f.time.Add(d))
118124
}
119125

120-
// Sets the time.
126+
// SetTime sets the time.
121127
func (f *FakeClock) SetTime(t time.Time) {
122128
f.lock.Lock()
123129
defer f.lock.Unlock()
@@ -157,14 +163,15 @@ func (f *FakeClock) setTimeLocked(t time.Time) {
157163
f.waiters = newWaiters
158164
}
159165

160-
// Returns true if After has been called on f but not yet satisfied (so you can
166+
// HasWaiters returns true if After has been called on f but not yet satisfied (so you can
161167
// write race-free tests).
162168
func (f *FakeClock) HasWaiters() bool {
163169
f.lock.RLock()
164170
defer f.lock.RUnlock()
165171
return len(f.waiters) > 0
166172
}
167173

174+
// Sleep is akin to time.Sleep
168175
func (f *FakeClock) Sleep(d time.Duration) {
169176
f.Step(d)
170177
}
@@ -186,24 +193,25 @@ func (i *IntervalClock) Since(ts time.Time) time.Duration {
186193
return i.Time.Sub(ts)
187194
}
188195

189-
// Unimplemented, will panic.
196+
// After is unimplemented, will panic.
190197
// TODO: make interval clock use FakeClock so this can be implemented.
191198
func (*IntervalClock) After(d time.Duration) <-chan time.Time {
192199
panic("IntervalClock doesn't implement After")
193200
}
194201

195-
// Unimplemented, will panic.
202+
// NewTimer is unimplemented, will panic.
196203
// TODO: make interval clock use FakeClock so this can be implemented.
197204
func (*IntervalClock) NewTimer(d time.Duration) clock.Timer {
198205
panic("IntervalClock doesn't implement NewTimer")
199206
}
200207

201-
// Unimplemented, will panic.
208+
// Tick is unimplemented, will panic.
202209
// TODO: make interval clock use FakeClock so this can be implemented.
203210
func (*IntervalClock) Tick(d time.Duration) <-chan time.Time {
204211
panic("IntervalClock doesn't implement Tick")
205212
}
206213

214+
// Sleep is unimplemented, will panic.
207215
func (*IntervalClock) Sleep(d time.Duration) {
208216
panic("IntervalClock doesn't implement Sleep")
209217
}
@@ -250,5 +258,17 @@ func (f *fakeTimer) Reset(d time.Duration) bool {
250258
f.waiter.fired = false
251259
f.waiter.targetTime = f.fakeClock.time.Add(d)
252260

261+
var isWaiting bool
262+
for i := range f.fakeClock.waiters {
263+
w := f.fakeClock.waiters[i]
264+
if w == &f.waiter {
265+
isWaiting = true
266+
break
267+
}
268+
}
269+
if !isWaiting {
270+
f.fakeClock.waiters = append(f.fakeClock.waiters, &f.waiter)
271+
}
272+
253273
return active
254274
}

clock/testing/fake_clock_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,3 +194,81 @@ func TestFakeStop(t *testing.T) {
194194
t.Errorf("expected existing waiter to be cleaned up, but it is still present")
195195
}
196196
}
197+
198+
// This tests the pattern documented in the go docs here: https://golang.org/pkg/time/#Timer.Stop
199+
// This pattern is required to safely reset a timer, so should be common.
200+
// This also tests resetting the timer
201+
func TestFakeStopDrain(t *testing.T) {
202+
start := time.Time{}
203+
tc := NewFakeClock(start)
204+
timer := tc.NewTimer(time.Second)
205+
tc.Step(1 * time.Second)
206+
// Effectively `if !timer.Stop { <-t.C }` but with more asserts
207+
if timer.Stop() {
208+
t.Errorf("stop should report the timer had triggered")
209+
}
210+
if readTime := assertReadTime(t, timer.C()); !readTime.Equal(start.Add(1 * time.Second)) {
211+
t.Errorf("timer should have ticked after 1 second, got %v", readTime)
212+
}
213+
214+
timer.Reset(time.Second)
215+
if !tc.HasWaiters() {
216+
t.Errorf("expected a waiter to be present, but it is not")
217+
}
218+
select {
219+
case <-timer.C():
220+
t.Fatal("got time early on clock; haven't stepped yet")
221+
default:
222+
}
223+
tc.Step(1 * time.Second)
224+
if readTime := assertReadTime(t, timer.C()); !readTime.Equal(start.Add(2 * time.Second)) {
225+
t.Errorf("timer should have ticked again after reset + 1 more second, got %v", readTime)
226+
}
227+
}
228+
229+
func TestTimerNegative(t *testing.T) {
230+
tc := NewFakeClock(time.Now())
231+
timer := tc.NewTimer(-1 * time.Second)
232+
if !tc.HasWaiters() {
233+
t.Errorf("expected a waiter to be present, but it is not")
234+
}
235+
// force waiters to be called
236+
tc.Step(0)
237+
tick := assertReadTime(t, timer.C())
238+
if tick != tc.Now() {
239+
t.Errorf("expected -1s to turn into now: %v != %v", tick, tc.Now())
240+
}
241+
}
242+
243+
func TestTickNegative(t *testing.T) {
244+
// The stdlib 'Tick' returns nil for negative and zero values, so our fake
245+
// should too.
246+
tc := NewFakeClock(time.Now())
247+
if tick := tc.Tick(-1 * time.Second); tick != nil {
248+
t.Errorf("expected negative tick to be nil: %v", tick)
249+
}
250+
if tick := tc.Tick(0); tick != nil {
251+
t.Errorf("expected negative tick to be nil: %v", tick)
252+
}
253+
}
254+
255+
// assertReadTime asserts that the channel can be read and returns the time it
256+
// reads from the channel.
257+
func assertReadTime(t testing.TB, c <-chan time.Time) time.Time {
258+
type helper interface {
259+
Helper()
260+
}
261+
if h, ok := t.(helper); ok {
262+
h.Helper()
263+
}
264+
select {
265+
case ti, ok := <-c:
266+
if !ok {
267+
t.Fatalf("expected to read time from channel, but it was closed")
268+
}
269+
return ti
270+
default:
271+
t.Fatalf("expected to read time from channel, but couldn't")
272+
}
273+
panic("unreachable")
274+
}

0 commit comments

Comments
 (0)