-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathmock_timer.go
More file actions
150 lines (118 loc) · 3.32 KB
/
mock_timer.go
File metadata and controls
150 lines (118 loc) · 3.32 KB
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
package glock
import (
"time"
)
func sendTime(t *MockTimer) {
t.ch <- t.now
}
// MockTimer is an implementation of Timer that can be moved forward in time
// in increments for testing code that relies on timeouts or other time-sensitive
// constructs.
type MockTimer struct {
*advanceable
deadline time.Time
ch chan time.Time
stopped bool
f func(*MockTimer)
}
var _ Timer = &MockTimer{}
var _ Advanceable = &MockTimer{}
// NewTimer creates a new Timer tied to the internal MockClock time that functions
// similar to time.NewTimer().
func (c *MockClock) NewTimer(duration time.Duration) Timer {
return newMockTimerAt(c.advanceable, duration, sendTime)
}
// AfterFunc creates a new Timer tied to the internal MockClock time that functions
// similar to time.AfterFunc().
func (c *MockClock) AfterFunc(duration time.Duration, f func()) Timer {
return newMockTimerAt(c.advanceable, duration, func(mt *MockTimer) {
go f()
})
}
// NewMockTimer creates a new MockTimer with the internal time set to time.Now().
func NewMockTimer(duration time.Duration) *MockTimer {
return NewMockTimerAt(time.Now(), duration)
}
// NewMockTimerAt creates a new MockTimer with the internal time set to the given time.
func NewMockTimerAt(now time.Time, duration time.Duration) *MockTimer {
return newMockTimerAt(newAdvanceableAt(now), duration, sendTime)
}
func newMockTimerAt(
advanceable *advanceable,
duration time.Duration,
f func(*MockTimer),
) *MockTimer {
if duration == 0 {
panic("duration cannot be 0")
}
t := &MockTimer{
advanceable: advanceable,
deadline: advanceable.now.Add(duration),
ch: make(chan time.Time),
f: f,
}
go t.process()
advanceable.register(t)
return t
}
// Chan returns a channel which will receive the Timer's internal time at the
// point where the Timer is triggered.
func (t *MockTimer) Chan() <-chan time.Time {
return t.ch
}
// Reset will reset the deadline of the Timer to trigger after the new duration
// based on the Timer's internal current time. If the Timer was running when Reset
// was called it will return true.
func (t *MockTimer) Reset(duration time.Duration) bool {
t.cond.L.Lock()
defer t.cond.L.Unlock()
wasRunning := !t.stopped
t.deadline = t.now.Add(duration)
t.stopped = false
if !wasRunning {
go t.process()
t.advanceable.register(t)
}
t.cond.Broadcast()
return wasRunning
}
// Stop will stop the Timer from running.
func (t *MockTimer) Stop() bool {
t.cond.L.Lock()
defer t.cond.L.Unlock()
if t.stopped {
return false
}
t.stopped = true
t.cond.Broadcast()
return true
}
// BlockingAdvance will bump the timer's internal time by the given duration. If
// the new internal time passes the timer's trigger threshold, a signal will be sent.
// This method will not return until the signal is read by a consumer of the Timer's
// channel.
func (t *MockTimer) BlockingAdvance(duration time.Duration) {
t.m.Lock()
defer t.m.Unlock()
t.now = t.now.Add(duration)
t.tryExecute()
}
func (t *MockTimer) tryExecute() {
if !t.now.Before(t.deadline) {
t.stopped = true
t.cond.Broadcast()
t.f(t)
}
}
func (t *MockTimer) process() {
t.cond.L.Lock()
defer t.cond.L.Unlock()
for !t.stopped {
t.tryExecute()
t.cond.Wait()
}
}
// signal conforms to the subscriber interface.
func (t *MockTimer) signal(now time.Time) bool {
return !t.stopped
}