OLD | NEW |
---|---|
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 package testclock | 5 package testclock |
6 | 6 |
7 import ( | 7 import ( |
8 "sync" | 8 "sync" |
9 "time" | 9 "time" |
10 | 10 |
11 "github.com/luci/luci-go/common/clock" | 11 "github.com/luci/luci-go/common/clock" |
12 "golang.org/x/net/context" | |
12 ) | 13 ) |
13 | 14 |
14 // TestClock is a Clock interface with additional methods to help instrument it. | 15 // TestClock is a Clock interface with additional methods to help instrument it. |
15 type TestClock interface { | 16 type TestClock interface { |
16 clock.Clock | 17 clock.Clock |
18 | |
19 // Set sets the test clock's time. | |
17 Set(time.Time) | 20 Set(time.Time) |
21 | |
22 // Add advances the test clock's time. | |
18 Add(time.Duration) | 23 Add(time.Duration) |
24 | |
25 // SetTimerCallback is a goroutine-safe method to set an instance-wide | |
26 // callback that is invoked when any timer begins. | |
19 SetTimerCallback(TimerCallback) | 27 SetTimerCallback(TimerCallback) |
20 } | 28 } |
21 | 29 |
22 // TimerCallback that can be invoked when a timer has been set. This is useful | 30 // TimerCallback that can be invoked when a timer has been set. This is useful |
23 // for sychronizing state when testing. | 31 // for sychronizing state when testing. |
24 type TimerCallback func(time.Duration, clock.Timer) | 32 type TimerCallback func(time.Duration, clock.Timer) |
25 | 33 |
26 type cancelFunc func() | |
27 | |
28 // testClock is a test-oriented implementation of the 'Clock' interface. | 34 // testClock is a test-oriented implementation of the 'Clock' interface. |
29 // | 35 // |
30 // This implementation's Clock responses are configurable by modifying its | 36 // This implementation's Clock responses are configurable by modifying its |
31 // member variables. Time-based events are explicitly triggered by sending on a | 37 // member variables. Time-based events are explicitly triggered by sending on a |
32 // Timer instance's channel. | 38 // Timer instance's channel. |
33 type testClock struct { | 39 type testClock struct { |
34 sync.Mutex | 40 sync.Mutex |
35 | 41 |
36 now time.Time // The current clock time. | 42 now time.Time // The current clock time. |
37 timerCond *sync.Cond // Condition used to manage timer blocking. | 43 timerCond *sync.Cond // Condition used to manage timer blocking. |
(...skipping 12 matching lines...) Expand all Loading... | |
50 return &c | 56 return &c |
51 } | 57 } |
52 | 58 |
53 func (c *testClock) Now() time.Time { | 59 func (c *testClock) Now() time.Time { |
54 c.Lock() | 60 c.Lock() |
55 defer c.Unlock() | 61 defer c.Unlock() |
56 | 62 |
57 return c.now | 63 return c.now |
58 } | 64 } |
59 | 65 |
60 func (c *testClock) Sleep(d time.Duration) { | 66 func (c *testClock) Sleep(ctx context.Context, d time.Duration) error { |
61 » <-c.After(d) | 67 » ar := <-c.After(ctx, d) |
68 » return ar.Err | |
62 } | 69 } |
63 | 70 |
64 func (c *testClock) NewTimer() clock.Timer { | 71 func (c *testClock) NewTimer(ctx context.Context) clock.Timer { |
65 » return newTimer(c) | 72 » t := newTimer(ctx, c) |
73 » return t | |
66 } | 74 } |
67 | 75 |
68 func (c *testClock) After(d time.Duration) <-chan time.Time { | 76 func (c *testClock) After(ctx context.Context, d time.Duration) <-chan clock.Tim erResult { |
69 » t := c.NewTimer() | 77 » t := newTimer(ctx, c) |
70 t.Reset(d) | 78 t.Reset(d) |
71 » return t.GetC() | 79 » return t.afterC |
72 } | 80 } |
73 | 81 |
74 func (c *testClock) Set(t time.Time) { | 82 func (c *testClock) Set(t time.Time) { |
75 c.Lock() | 83 c.Lock() |
76 defer c.Unlock() | 84 defer c.Unlock() |
77 | 85 |
78 c.setTimeLocked(t) | 86 c.setTimeLocked(t) |
79 } | 87 } |
80 | 88 |
81 func (c *testClock) Add(d time.Duration) { | 89 func (c *testClock) Add(d time.Duration) { |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
113 if callback := c.getTimerCallback(); callback != nil { | 121 if callback := c.getTimerCallback(); callback != nil { |
114 callback(d, t) | 122 callback(d, t) |
115 } | 123 } |
116 } | 124 } |
117 | 125 |
118 // invokeAt invokes the specified callback when the Clock has advanced at | 126 // invokeAt invokes the specified callback when the Clock has advanced at |
119 // or after the specified threshold. | 127 // or after the specified threshold. |
120 // | 128 // |
121 // The returned cancelFunc can be used to cancel the blocking. If the cancel | 129 // The returned cancelFunc can be used to cancel the blocking. If the cancel |
122 // function is invoked before the callback, the callback will not be invoked. | 130 // function is invoked before the callback, the callback will not be invoked. |
123 func (c *testClock) invokeAt(threshold time.Time, callback func(time.Time)) canc elFunc { | 131 func (c *testClock) invokeAt(ctx context.Context, threshold time.Time, callback func(time.Time, error)) { |
132 » finishedC := make(chan struct{}) | |
124 stopC := make(chan struct{}) | 133 stopC := make(chan struct{}) |
125 finishedC := make(chan struct{}) | |
126 | 134 |
127 // Our control goroutine will monitor both time and stop signals. It wil l | 135 // Our control goroutine will monitor both time and stop signals. It wil l |
128 // terminate when either the current time has exceeded our time threshol d or | 136 // terminate when either the current time has exceeded our time threshol d or |
129 » // it has been signalled to terminate via stopC. | 137 » // it has been signalled to terminate via Context. |
130 // | 138 // |
131 // The lock that we take our here is owned by the following goroutine. | 139 // The lock that we take our here is owned by the following goroutine. |
132 c.Lock() | 140 c.Lock() |
133 go func() { | 141 go func() { |
134 defer close(finishedC) | 142 defer close(finishedC) |
143 | |
135 defer func() { | 144 defer func() { |
136 now := c.now | 145 now := c.now |
137 c.Unlock() | 146 c.Unlock() |
138 | 147 |
139 » » » if callback != nil { | 148 » » » // If we finished naturally, but our Context is Done, pr efer the Context |
dnj (Google)
2016/02/10 03:40:08
memlock tests were depending on this preferring a
| |
140 » » » » callback(now) | 149 » » » // error. |
150 » » » var err error | |
151 » » » select { | |
152 » » » case <-ctx.Done(): | |
153 » » » » err = ctx.Err() | |
154 » » » default: | |
155 » » » » break | |
141 } | 156 } |
157 callback(now, err) | |
142 }() | 158 }() |
143 | 159 |
144 for { | 160 for { |
145 // Determine if we are past our signalling threshold. We can safely access | 161 // Determine if we are past our signalling threshold. We can safely access |
146 // our clock's time member directly, since we hold its l ock from the | 162 // our clock's time member directly, since we hold its l ock from the |
147 // condition firing. | 163 // condition firing. |
148 if !c.now.Before(threshold) { | 164 if !c.now.Before(threshold) { |
149 return | 165 return |
150 } | 166 } |
151 | 167 |
152 // Wait for a signal from our clock's condition. | 168 // Wait for a signal from our clock's condition. |
153 c.timerCond.Wait() | 169 c.timerCond.Wait() |
154 | 170 |
155 // Have we been stopped? | 171 // Have we been stopped? |
156 select { | 172 select { |
157 case <-stopC: | 173 case <-stopC: |
158 callback = nil | |
159 return | 174 return |
160 | 175 |
161 default: | 176 default: |
162 // Nope. | 177 // Nope. |
163 } | 178 } |
164 } | 179 } |
165 }() | 180 }() |
166 | 181 |
167 » return func() { | 182 » // This goroutine will monitor our Context and unblock if its Done chann el |
168 » » // Signal our goroutine to stop. | 183 » // has an error. |
169 » » close(stopC) | 184 » go func() { |
185 » » select { | |
186 » » case <-finishedC: | |
187 » » » // Our timer has expired, so no need to further monitor. | |
188 » » » return | |
170 | 189 |
171 » » // Lock, then Broadcast. This ensures that if the goroutine is r unning, it | 190 » » case <-ctx.Done(): |
172 » » // will be at a Wait() and therefore this Broadcast will wake it . | 191 » » » // If we finish at the same time our Context is canceled , we don't need to |
173 » » func() { | 192 » » » // wake our monitor (determinism). |
174 » » » c.Lock() | 193 » » » select { |
175 » » » defer c.Unlock() | 194 » » » case <-finishedC: |
195 » » » » return | |
196 » » » default: | |
197 » » » » break | |
198 » » » } | |
199 | |
200 » » » // Our Context has been canceled. Forward this to our mo nitor goroutine | |
201 » » » // and wake our condition. | |
202 » » » close(stopC) | |
176 c.timerCond.Broadcast() | 203 c.timerCond.Broadcast() |
177 » » }() | 204 » » } |
205 » }() | |
206 } | |
178 | 207 |
179 » » // Block until it has finished. | 208 // GetTags returns the tags associated with the specified timer. If the timer |
180 » » <-finishedC | 209 // has no tags, an empty slice (nil) will be returned. |
210 func GetTags(t clock.Timer) []string { | |
211 » if tt, ok := t.(*timer); ok { | |
212 » » return clock.Tags(tt.ctx) | |
181 } | 213 } |
214 return nil | |
182 } | 215 } |
216 | |
217 // HasTags tests if a given timer has the same tags. | |
218 func HasTags(t clock.Timer, tags ...string) bool { | |
219 timerTags := GetTags(t) | |
220 if len(timerTags) != len(tags) { | |
221 return false | |
222 } | |
223 for i, tag := range timerTags { | |
224 if tags[i] != tag { | |
225 return false | |
226 } | |
227 } | |
228 return true | |
229 } | |
OLD | NEW |