Skip to content

Commit 7d21cef

Browse files
committed
fake clock: expose the number of waiters
By using `Waiters()`, tests can wait until the expected number of waiters are present before manipulating the clock's time, ensuring that the goroutine under test has reached a stable state regarding its timers. Change-Id: I64d7b89f5e178a72898885642818215e8ad73a9d
1 parent 1f6e0b7 commit 7d21cef

File tree

2 files changed

+82
-1
lines changed

2 files changed

+82
-1
lines changed

clock/testing/fake_clock.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,14 +221,27 @@ func (f *FakeClock) setTimeLocked(t time.Time) {
221221
f.waiters = newWaiters
222222
}
223223

224-
// HasWaiters returns true if After or AfterFunc has been called on f but not yet satisfied (so you can
224+
// HasWaiters returns true if Waiters() returns non-0 (so you can
225225
// write race-free tests).
226226
func (f *FakeClock) HasWaiters() bool {
227227
f.lock.RLock()
228228
defer f.lock.RUnlock()
229229
return len(f.waiters) > 0
230230
}
231231

232+
// Waiters returns the number of "waiters" on the clock (so you can write race-free
233+
// tests). A waiter exists for:
234+
// - every call to After that has not yet signaled its channel.
235+
// - every call to AfterFunc that has not yet called its callback.
236+
// - every timer created with NewTimer which is currently ticking.
237+
// - every ticker created with NewTicker which is currently ticking.
238+
// - every ticker created with Tick.
239+
func (f *FakeClock) Waiters() int {
240+
f.lock.RLock()
241+
defer f.lock.RUnlock()
242+
return len(f.waiters)
243+
}
244+
232245
// Sleep is akin to time.Sleep
233246
func (f *FakeClock) Sleep(d time.Duration) {
234247
f.Step(d)

clock/testing/fake_clock_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,3 +444,71 @@ func assertReadTime(t testing.TB, c <-chan time.Time) time.Time {
444444
}
445445
panic("unreachable")
446446
}
447+
448+
func TestFakeClockWaiters(t *testing.T) {
449+
startTime := time.Now()
450+
tc := NewFakeClock(startTime)
451+
452+
// Initial state
453+
if count := tc.Waiters(); count != 0 {
454+
t.Errorf("Expected 0 waiters initially, got %d", count)
455+
}
456+
457+
// Add a Timer
458+
timer1 := tc.NewTimer(1 * time.Second)
459+
if count := tc.Waiters(); count != 1 {
460+
t.Errorf("Expected 1 waiter after NewTimer, got %d", count)
461+
}
462+
463+
// Add an After
464+
_ = tc.After(2 * time.Second)
465+
if count := tc.Waiters(); count != 2 {
466+
t.Errorf("Expected 2 waiters after After, got %d", count)
467+
}
468+
469+
// Add a Ticker
470+
ticker := tc.NewTicker(3 * time.Second)
471+
if count := tc.Waiters(); count != 3 {
472+
t.Errorf("Expected 3 waiters after NewTicker, got %d", count)
473+
}
474+
475+
// Step past the first timer
476+
tc.Step(1 * time.Second)
477+
<-timer1.C() // Drain channel
478+
if count := tc.Waiters(); count != 2 {
479+
t.Errorf("Expected 2 waiters after first timer fired, got %d", count)
480+
}
481+
482+
// Step past the After
483+
tc.Step(1 * time.Second)
484+
// Note: After channel is implicitly drained by setTimeLocked
485+
if count := tc.Waiters(); count != 1 {
486+
t.Errorf("Expected 1 waiter after After fired, got %d", count)
487+
}
488+
489+
// Step past the Ticker (it should re-arm)
490+
tc.Step(1 * time.Second)
491+
<-ticker.C() // Drain channel
492+
if count := tc.Waiters(); count != 1 {
493+
t.Errorf("Expected 1 waiter after Ticker fired (should re-arm), got %d", count)
494+
}
495+
496+
// Stop the ticker (Note: fakeTicker.Stop is currently a no-op, so this won't change the count)
497+
// If fakeTicker.Stop were implemented to remove the waiter, the expected count would be 0.
498+
ticker.Stop()
499+
if count := tc.Waiters(); count != 1 {
500+
t.Errorf("Expected 1 waiter after stopping ticker (no-op), got %d", count)
501+
}
502+
503+
// Add another timer and stop it
504+
timer2 := tc.NewTimer(5 * time.Second)
505+
if count := tc.Waiters(); count != 2 {
506+
t.Errorf("Expected 2 waiters after adding second timer, got %d", count)
507+
}
508+
if stopped := timer2.Stop(); !stopped {
509+
t.Errorf("Expected timer2.Stop() to return true")
510+
}
511+
if count := tc.Waiters(); count != 1 {
512+
t.Errorf("Expected 1 waiter after stopping second timer, got %d", count)
513+
}
514+
}

0 commit comments

Comments
 (0)