| 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..10d17d65efe896cc1d543b420e5f559971a75c43
|
| --- /dev/null
|
| +++ b/go/src/infra/libs/clock/testclock/testclock.go
|
| @@ -0,0 +1,178 @@
|
| +// 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.
|
| +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()
|
| +}
|
| +
|
| +// invokeAt invokes the specified callback when the Clock has advanced at
|
| +// or after the specified threshold.
|
| +//
|
| +// The returned cancelFunc can be used to cancel the blocking. If the cancel
|
| +// function is invoked before the callback, the callback will not be invoked.
|
| +func (c *testClock) invokeAt(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
|
| + }
|
| +}
|
|
|