OLD | NEW |
(Empty) | |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 package testclock |
| 6 |
| 7 import ( |
| 8 "sync" |
| 9 "time" |
| 10 |
| 11 "infra/libs/clock" |
| 12 ) |
| 13 |
| 14 // TestClock is a Clock interface with additional methods to help instrument it. |
| 15 type TestClock interface { |
| 16 clock.Clock |
| 17 Set(time.Time) |
| 18 Add(time.Duration) |
| 19 SetTimerCallback(TimerCallback) |
| 20 } |
| 21 |
| 22 // TimerCallback that can be invoked when a timer has been set. This is useful |
| 23 // for sychronizing state when testing. |
| 24 type TimerCallback func(clock.Timer) |
| 25 |
| 26 type cancelFunc func() |
| 27 |
| 28 // testClock is a test-oriented implementation of the 'Clock' interface. |
| 29 // |
| 30 // This implementation's Clock responses are configurable by modifying its |
| 31 // member variables. Time-based events are explicitly triggered by sending on a |
| 32 // Timer instance's channel. |
| 33 type testClock struct { |
| 34 sync.RWMutex |
| 35 |
| 36 now time.Time // The current clock time. |
| 37 timerCond *sync.Cond // Condition used to manage timer blocking. |
| 38 |
| 39 timerCallback TimerCallback // Optional callback when a timer has been s
et. |
| 40 } |
| 41 |
| 42 var _ TestClock = (*testClock)(nil) |
| 43 |
| 44 // New returns a TestClock instance set at the specified time. |
| 45 func New(now time.Time) TestClock { |
| 46 c := testClock{ |
| 47 now: now, |
| 48 } |
| 49 c.timerCond = sync.NewCond(&c) |
| 50 return &c |
| 51 } |
| 52 |
| 53 func (c *testClock) Now() time.Time { |
| 54 c.RLock() |
| 55 defer c.RUnlock() |
| 56 |
| 57 return c.now |
| 58 } |
| 59 |
| 60 func (c *testClock) Sleep(d time.Duration) { |
| 61 <-c.After(d) |
| 62 } |
| 63 |
| 64 func (c *testClock) NewTimer() clock.Timer { |
| 65 return newTimer(c) |
| 66 } |
| 67 |
| 68 func (c *testClock) After(d time.Duration) <-chan time.Time { |
| 69 t := c.NewTimer() |
| 70 t.Reset(d) |
| 71 return t.GetC() |
| 72 } |
| 73 |
| 74 func (c *testClock) Set(t time.Time) { |
| 75 c.Lock() |
| 76 defer c.Unlock() |
| 77 |
| 78 c.setTimeLocked(t) |
| 79 } |
| 80 |
| 81 func (c *testClock) Add(d time.Duration) { |
| 82 c.Lock() |
| 83 defer c.Unlock() |
| 84 |
| 85 c.setTimeLocked(c.now.Add(d)) |
| 86 } |
| 87 |
| 88 func (c *testClock) setTimeLocked(t time.Time) { |
| 89 if t.Before(c.now) { |
| 90 panic("Cannot go backwards in time. You're not Doc Brown.") |
| 91 } |
| 92 c.now = t |
| 93 |
| 94 // Unblock any blocking timers that are waiting on our lock. |
| 95 c.pokeTimers(true) |
| 96 } |
| 97 |
| 98 func (c *testClock) SetTimerCallback(callback TimerCallback) { |
| 99 c.Lock() |
| 100 defer c.Unlock() |
| 101 |
| 102 c.timerCallback = callback |
| 103 } |
| 104 |
| 105 func (c *testClock) getTimerCallback() TimerCallback { |
| 106 c.Lock() |
| 107 defer c.Unlock() |
| 108 |
| 109 return c.timerCallback |
| 110 } |
| 111 |
| 112 func (c *testClock) signalTimerSet(t clock.Timer) { |
| 113 callback := c.getTimerCallback() |
| 114 if callback != nil { |
| 115 callback(t) |
| 116 } |
| 117 } |
| 118 |
| 119 func (c *testClock) pokeTimers(locked bool) { |
| 120 if !locked { |
| 121 c.Lock() |
| 122 defer c.Unlock() |
| 123 } |
| 124 |
| 125 c.timerCond.Broadcast() |
| 126 } |
| 127 |
| 128 // invokeAt invokes the specified callback when the Clock has advanced at |
| 129 // or after the specified threshold. |
| 130 // |
| 131 // The returned cancelFunc can be used to cancel the blocking. If the cancel |
| 132 // function is invoked before the callback, the callback will not be invoked. |
| 133 func (c *testClock) invokeAt(threshold time.Time, callback func(time.Time)) canc
elFunc { |
| 134 stopC := make(chan struct{}) |
| 135 finishedC := make(chan struct{}) |
| 136 |
| 137 // Our control goroutine will monitor both time and stop signals. It wil
l only |
| 138 // terminate when stopC has been closed. |
| 139 // |
| 140 // The lock that we take our here is owned by the following goroutine. |
| 141 c.Lock() |
| 142 go func() { |
| 143 defer func() { |
| 144 now := c.now |
| 145 c.Unlock() |
| 146 close(finishedC) |
| 147 callback(now) |
| 148 }() |
| 149 |
| 150 for { |
| 151 // Determine if we are past our signalling threshold. We
can safely access |
| 152 // our clock's time member directly, since we hold its l
ock from the |
| 153 // condition firing. |
| 154 if !c.now.Before(threshold) { |
| 155 return |
| 156 } |
| 157 |
| 158 // Wait for a signal from our clock's condition. |
| 159 c.timerCond.Wait() |
| 160 |
| 161 // Have we been stopped? |
| 162 select { |
| 163 case <-stopC: |
| 164 return |
| 165 |
| 166 default: |
| 167 // Nope. |
| 168 } |
| 169 } |
| 170 }() |
| 171 |
| 172 return func() { |
| 173 // Close our stop channel and block pending goroutine terminatio
n. |
| 174 close(stopC) |
| 175 c.pokeTimers(false) |
| 176 <-finishedC |
| 177 } |
| 178 } |
OLD | NEW |