forked from pact-im/go-pkg
-
Notifications
You must be signed in to change notification settings - Fork 0
/
fakeclock.go
329 lines (286 loc) · 7.7 KB
/
fakeclock.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
// Package fakeclock provides support for testing users of a clock.
package fakeclock
import (
"math"
"sync"
"time"
"go.pact.im/x/clock"
)
var _ interface {
clock.Scheduler
clock.NowScheduler
clock.TimerScheduler
clock.TickerScheduler
} = (*Clock)(nil)
// moment represents a scheduled event.
type moment interface {
// next returns the duration until the next occurrence of this event, or
// false if it is the last event.
next(now time.Time) (time.Duration, bool)
}
// Clock is a fake clock.Scheduler interface implementation that is safe for
// concurrent use by multiple goroutines.
//
// Use Next, Set, Add and AddDate methods to change clock time. Advancing the
// time triggers scheduled events, timers and tickers.
//
// Note that the order in which events scheduled for the same time are triggered
// is undefined, but it is guaranteed that all events that are not after the new
// current time are triggered on clock time change (even if old time is equal to
// the next time value).
//
// The zero Clock defaults to zero time and is ready for use.
type Clock struct {
mu sync.Mutex
now time.Time
sched map[moment]time.Time
}
// Unix returns a clock set to the Unix epoch time. That is, it is set to
// 1970-01-01 00:00:00 UTC.
func Unix() *Clock {
return Time(time.Unix(0, 0))
}
// Go returns a clock set to the Go initial release date. That is, it is set to
// 2009-11-10 23:00:00 UTC.
func Go() *Clock {
return Time(time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC))
}
// Y2038 returns a clock set to d duration after the Year 2038 problem time.
// That is, it is set to the given duration after 2038-01-19 03:14:07 UTC, the
// latest time that can be properly encoded as a 32-bit integer that is a number
// of seconds after the Unix epoch.
func Y2038(d time.Duration) *Clock {
t := time.Unix(math.MaxInt32, 0)
return Time(t.Add(d))
}
// Time returns a clock set to the given now time.
func Time(now time.Time) *Clock {
return &Clock{
now: now,
sched: map[moment]time.Time{},
}
}
// Now returns the current clock time.
func (c *Clock) Now() time.Time {
c.mu.Lock()
defer c.mu.Unlock()
return c.now
}
// Next advances the time to the next timer or ticker event and returns the new
// current time. If there are events in the past, the time is not changed. It
// returns false if there are no scheduled events. Otherwise it returns true and
// runs at least one scheduled event.
func (c *Clock) Next() (time.Time, bool) {
c.mu.Lock()
defer c.mu.Unlock()
ok := len(c.sched) > 0
c.now = c.next(c.now)
c.advance(c.now)
return c.now, ok
}
// Set sets the given time to be the current clock time. It is possible to set
// t that is before the current clock time.
func (c *Clock) Set(t time.Time) {
c.mu.Lock()
defer c.mu.Unlock()
c.now = t
c.advance(c.now)
}
// Add adds the given duration to the current time and returns the resulting
// clock time. It is possible to add a negative duration.
//
// It is safe for concurrent use and is a shorthand for
// now := c.Now().Add(d)
// c.Set(now)
func (c *Clock) Add(d time.Duration) time.Time {
c.mu.Lock()
defer c.mu.Unlock()
c.now = c.now.Add(d)
c.advance(c.now)
return c.now
}
// AddDate adds the duration corresponding to the given number of years, months
// and days relative to the current time and returns the resulting clock time.
// It is possible to add negative values.
//
// It is safe for concurrent use and is a shorthand for
// now := c.Now().AddDate(years, months, days)
// c.Set(now)
func (c *Clock) AddDate(years, months, days int) time.Time {
c.mu.Lock()
defer c.mu.Unlock()
c.now = c.now.AddDate(years, months, days)
c.advance(c.now)
return c.now
}
// Schedule implements the clock.Scheduler interface.
func (c *Clock) Schedule(d time.Duration, f func(now time.Time)) clock.Event {
t := &event{
c: c,
f: f,
}
c.reset(t, d, nil)
return t
}
// Timer implements the clock.TimerScheduler interface.
func (c *Clock) Timer(d time.Duration) clock.Timer {
t := &timer{
c: c,
ch: make(chan time.Time, 1),
}
c.reset(t, d, nil)
return t
}
// Ticker implements the clock.TickerScheduler interface. Note that the returned
// ticker does not adjust the time interval or drop ticks to make up for slow
// receivers.
func (c *Clock) Ticker(d time.Duration) clock.Ticker {
if d <= 0 {
panic("non-positive interval for Ticker")
}
t := &ticker{
c: c,
ch: make(chan time.Time, 1),
}
c.reset(t, d, &t.d)
return t
}
// stop removes the given moment from the set of scheduled events. It returns
// true if stop prevented the event from firing. Note that stop acquires the
// underlying lock.
func (c *Clock) stop(m moment) bool {
c.mu.Lock()
defer c.mu.Unlock()
_, ok := c.sched[m]
delete(c.sched, m)
return ok
}
// reset resets the given moment to run d duration after the current time.
// Note that reset acquires the underlying lock. Reset returns true if it
// rescheduled an event.
func (c *Clock) reset(m moment, d time.Duration, dp *time.Duration) bool {
c.mu.Lock()
defer c.mu.Unlock()
if dp != nil {
*dp = d
}
return c.schedule(m, c.now.Add(d))
}
// schedule schedules the given moment to run on next clock advance. It returns
// true if an event was rescheduled.
func (c *Clock) schedule(m moment, when time.Time) bool {
if c.sched == nil {
c.sched = map[moment]time.Time{}
}
_, ok := c.sched[m]
c.sched[m] = when
return ok
}
// next returns the time of the next scheduled event. It returns the given now
// time if there are no events or some of them are in the past.
func (c *Clock) next(now time.Time) time.Time {
if len(c.sched) == 0 {
return now
}
var min time.Time
for _, t := range c.sched {
min = t
break
}
for _, t := range c.sched {
if !t.After(now) {
return now
}
if t.After(min) {
continue
}
min = t
}
return min
}
// advance runs the scheduled events for the current clock time.
func (c *Clock) advance(now time.Time) {
for m, t := range c.sched {
if t.After(now) {
continue
}
next, ok := m.next(now)
if !ok {
delete(c.sched, m)
continue
}
_ = c.schedule(m, now.Add(next))
}
}
// event implements the moment and clock.Event interfaces.
type event struct {
c *Clock
f func(time.Time)
}
// Stop implements the clock.Event interface.
func (t *event) Stop() bool {
return t.c.stop(t)
}
// Reset implements the clock.Event interface.
func (t *event) Reset(d time.Duration) bool {
return t.c.reset(t, d, nil)
}
// next implements the moment interface.
func (t *event) next(now time.Time) (time.Duration, bool) {
go t.f(now)
return 0, false
}
// timer implements the moment and clock.Timer interfaces.
type timer struct {
c *Clock
ch chan time.Time
}
// C implements the clock.Timer interface.
func (t *timer) C() <-chan time.Time {
return t.ch
}
// Stop implements the clock.Timer interface.
func (t *timer) Stop() bool {
return t.c.stop(t)
}
// Reset implements the clock.Timer interface.
func (t *timer) Reset(d time.Duration) {
_ = t.c.reset(t, d, nil)
}
// next implements the moment interface.
func (t *timer) next(now time.Time) (time.Duration, bool) {
select {
case t.ch <- now:
default:
}
return 0, false
}
// ticker implements the moment and clock.Ticker interfaces.
type ticker struct {
c *Clock
ch chan time.Time
d time.Duration
}
// C implements the clock.Ticker interface.
func (t *ticker) C() <-chan time.Time {
return t.ch
}
// Stop implements the clock.Ticker interface.
func (t *ticker) Stop() {
_ = t.c.stop(t)
}
// Reset implements the clock.Ticker interface.
func (t *ticker) Reset(d time.Duration) {
if d <= 0 {
panic("non-positive interval for Ticker.Reset")
}
_ = t.c.reset(t, d, &t.d)
}
// next implements the moment interface.
func (t *ticker) next(now time.Time) (time.Duration, bool) {
select {
case t.ch <- now:
default:
}
return t.d, true
}