Chromium Code Reviews| Index: go/src/infra/libs/clock/testclock/testclock.go |
| diff --git a/go/src/infra/libs/clock/testclock/testclock.go b/go/src/infra/libs/clock/testclock/testclock.go |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..d59df921a95f36519b42be6330cf39fd6598b28b |
| --- /dev/null |
| +++ b/go/src/infra/libs/clock/testclock/testclock.go |
| @@ -0,0 +1,172 @@ |
| +// Copyright 2014 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +package testclock |
| + |
| +import ( |
| + "sync" |
| + "time" |
| + |
| + "infra/libs/clock" |
| +) |
| + |
| +// TestClock is a Clock interface with additional methods to help instrument it. |
| +type TestClock interface { |
| + clock.Clock |
| + Set(time.Time) |
| + Add(time.Duration) |
| + SetTimerCallback(TimerCallback) |
| +} |
| + |
| +// TimerCallback that can be invoked when a timer has been set. This is useful for |
| +// sychronizing state when testing. |
|
iannucci
2015/06/03 17:37:20
I think we're still trying to wrap 80 on comments
dnj
2015/06/03 18:21:27
I did a best-effort run. I wonder if this is enfor
|
| +type TimerCallback func(clock.Timer) |
| + |
| +type cancelFunc func() |
| + |
| +// testClock is a test-oriented implementation of the 'Clock' interface. |
| +// |
| +// This implementation's Clock responses are configurable by modifying its member |
| +// variables. Time-based events are explicitly triggered by sending on a Timer |
| +// instance's channel. |
| +type testClock struct { |
| + sync.RWMutex |
| + |
| + now time.Time // The current clock time. |
| + timerCond *sync.Cond // Condition used to manage timer blocking. |
| + |
| + timerCallback TimerCallback // Optional callback when a timer has been set. |
| +} |
| + |
| +var _ TestClock = (*testClock)(nil) |
| + |
| +// New returns a TestClock instance set at the specified time. |
| +func New(now time.Time) TestClock { |
| + c := testClock{ |
| + now: now, |
| + } |
| + c.timerCond = sync.NewCond(&c) |
| + return &c |
| +} |
| + |
| +func (c *testClock) Now() time.Time { |
| + c.RLock() |
| + defer c.RUnlock() |
| + |
| + return c.now |
| +} |
| + |
| +func (c *testClock) Sleep(d time.Duration) { |
| + <-c.After(d) |
| +} |
| + |
| +func (c *testClock) NewTimer() clock.Timer { |
| + return newTimer(c) |
| +} |
| + |
| +func (c *testClock) After(d time.Duration) <-chan time.Time { |
| + t := c.NewTimer() |
| + t.Reset(d) |
| + return t.GetC() |
| +} |
| + |
| +func (c *testClock) Set(t time.Time) { |
| + c.Lock() |
| + defer c.Unlock() |
| + |
| + c.setTimeLocked(t) |
| +} |
| + |
| +func (c *testClock) Add(d time.Duration) { |
| + c.Lock() |
| + defer c.Unlock() |
| + |
| + c.setTimeLocked(c.now.Add(d)) |
| +} |
| + |
| +func (c *testClock) setTimeLocked(t time.Time) { |
| + if t.Before(c.now) { |
| + panic("Cannot go backwards in time. You're not Doc Brown.") |
| + } |
| + c.now = t |
| + |
| + // Unblock any blocking timers that are waiting on our lock. |
| + c.pokeTimers(true) |
| +} |
| + |
| +func (c *testClock) SetTimerCallback(callback TimerCallback) { |
| + c.Lock() |
| + defer c.Unlock() |
| + |
| + c.timerCallback = callback |
| +} |
| + |
| +func (c *testClock) getTimerCallback() TimerCallback { |
| + c.Lock() |
| + defer c.Unlock() |
| + |
| + return c.timerCallback |
| +} |
| + |
| +func (c *testClock) signalTimerSet(t clock.Timer) { |
| + callback := c.getTimerCallback() |
| + if callback != nil { |
| + callback(t) |
| + } |
| +} |
| + |
| +func (c *testClock) pokeTimers(locked bool) { |
| + if !locked { |
| + c.Lock() |
| + defer c.Unlock() |
| + } |
| + |
| + c.timerCond.Broadcast() |
| +} |
| + |
| +func (c *testClock) whenTimePasses(threshold time.Time, callback func(time.Time)) cancelFunc { |
| + stopC := make(chan struct{}) |
| + finishedC := make(chan struct{}) |
| + |
| + // Our control goroutine will monitor both time and stop signals. It will only terminate |
| + // when stopC has been closed. |
| + // |
| + // The lock that we take our here is owned by the following goroutine. |
| + c.Lock() |
| + go func() { |
| + defer func() { |
| + now := c.now |
| + c.Unlock() |
| + close(finishedC) |
| + callback(now) |
| + }() |
| + |
| + for { |
| + // Determine if we are past our signalling threshold. We can safely access our |
| + // clock's time member directly, since we hold its lock from the condition firing. |
| + if !c.now.Before(threshold) { |
| + return |
| + } |
| + |
| + // Wait for a signal from our clock's condition. |
| + c.timerCond.Wait() |
| + |
| + // Have we been stopped? |
| + select { |
| + case <-stopC: |
| + return |
| + |
| + default: |
| + // Nope. |
| + } |
| + } |
| + }() |
| + |
| + return func() { |
| + // Close our stop channel and block pending goroutine termination. |
| + close(stopC) |
| + c.pokeTimers(false) |
| + <-finishedC |
| + } |
| +} |