OLD | NEW |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 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 "fmt" |
| 9 "runtime" |
| 10 "strings" |
| 11 "sync" |
8 "testing" | 12 "testing" |
9 "time" | 13 "time" |
10 | 14 |
11 "github.com/luci/luci-go/common/clock" | 15 "github.com/luci/luci-go/common/clock" |
| 16 "golang.org/x/net/context" |
| 17 |
12 . "github.com/smartystreets/goconvey/convey" | 18 . "github.com/smartystreets/goconvey/convey" |
13 ) | 19 ) |
14 | 20 |
| 21 // trashTimer is a useless implementation of clock.Timer specifically designed |
| 22 // to exist and not be a test timer type. |
| 23 type trashTimer struct { |
| 24 clock.Timer |
| 25 } |
| 26 |
15 func TestTestTimer(t *testing.T) { | 27 func TestTestTimer(t *testing.T) { |
16 t.Parallel() | 28 t.Parallel() |
17 | 29 |
18 Convey(`A testing clock instance`, t, func() { | 30 Convey(`A testing clock instance`, t, func() { |
19 » » now := time.Date(2015, 01, 01, 00, 00, 00, 00, time.UTC) | 31 » » ctx, cancelFunc := context.WithCancel(context.Background()) |
20 » » c := New(now) | 32 » » defer cancelFunc() |
| 33 |
| 34 » » now := TestTimeLocal |
| 35 » » clk := New(now) |
21 | 36 |
22 Convey(`A timer instance`, func() { | 37 Convey(`A timer instance`, func() { |
23 » » » t := c.NewTimer() | 38 » » » t := clk.NewTimer(ctx) |
24 | 39 |
25 Convey(`Should have a non-nil C.`, func() { | 40 Convey(`Should have a non-nil C.`, func() { |
26 » » » » So(t.GetC(), ShouldBeNil) | 41 » » » » So(t.GetC(), ShouldNotBeNil) |
27 }) | 42 }) |
28 | 43 |
29 Convey(`When activated`, func() { | 44 Convey(`When activated`, func() { |
30 So(t.Reset(1*time.Second), ShouldBeFalse) | 45 So(t.Reset(1*time.Second), ShouldBeFalse) |
31 | 46 |
32 Convey(`When reset, should return active.`, func
() { | 47 Convey(`When reset, should return active.`, func
() { |
33 So(t.Reset(1*time.Hour), ShouldBeTrue) | 48 So(t.Reset(1*time.Hour), ShouldBeTrue) |
34 So(t.GetC(), ShouldNotBeNil) | 49 So(t.GetC(), ShouldNotBeNil) |
35 }) | 50 }) |
36 | 51 |
37 Convey(`When stopped, should return active.`, fu
nc() { | 52 Convey(`When stopped, should return active.`, fu
nc() { |
38 So(t.Stop(), ShouldBeTrue) | 53 So(t.Stop(), ShouldBeTrue) |
39 » » » » » So(t.GetC(), ShouldBeNil) | 54 » » » » » So(t.GetC(), ShouldNotBeNil) |
40 | 55 |
41 Convey(`And when stopped again, should r
eturn inactive.`, func() { | 56 Convey(`And when stopped again, should r
eturn inactive.`, func() { |
42 So(t.Stop(), ShouldBeFalse) | 57 So(t.Stop(), ShouldBeFalse) |
43 » » » » » » So(t.GetC(), ShouldBeNil) | 58 » » » » » » So(t.GetC(), ShouldNotBeNil) |
44 }) | 59 }) |
45 }) | 60 }) |
46 | 61 |
47 Convey(`When stopped after expiring, should not
have a signal.`, func() { | 62 Convey(`When stopped after expiring, should not
have a signal.`, func() { |
48 » » » » » c.Add(1 * time.Second) | 63 » » » » » clk.Add(1 * time.Second) |
49 So(t.Stop(), ShouldBeTrue) | 64 So(t.Stop(), ShouldBeTrue) |
50 | 65 |
51 var signalled bool | 66 var signalled bool |
52 select { | 67 select { |
53 case <-t.GetC(): | 68 case <-t.GetC(): |
54 signalled = true | 69 signalled = true |
55 default: | 70 default: |
56 break | 71 break |
57 } | 72 } |
58 So(signalled, ShouldBeFalse) | 73 So(signalled, ShouldBeFalse) |
59 }) | 74 }) |
60 }) | 75 }) |
61 | 76 |
62 Convey(`Should successfully signal.`, func() { | 77 Convey(`Should successfully signal.`, func() { |
63 So(t.Reset(1*time.Second), ShouldBeFalse) | 78 So(t.Reset(1*time.Second), ShouldBeFalse) |
64 » » » » c.Add(1 * time.Second) | 79 » » » » clk.Add(1 * time.Second) |
65 | 80 |
66 » » » » So(t.GetC(), ShouldNotBeNil) | 81 » » » » So(<-t.GetC(), ShouldResemble, clock.TimerResult
{Time: now.Add(1 * time.Second)}) |
67 » » » » So(<-t.GetC(), ShouldResemble, now.Add(1*time.Se
cond)) | 82 » » » }) |
| 83 |
| 84 » » » Convey(`Should signal immediately if the timer is in the
past.`, func() { |
| 85 » » » » So(t.Reset(-1*time.Second), ShouldBeFalse) |
| 86 » » » » So(<-t.GetC(), ShouldResemble, clock.TimerResult
{Time: now}) |
| 87 » » » }) |
| 88 |
| 89 » » » Convey(`Will trigger immediately if the Context is cance
led when reset.`, func() { |
| 90 » » » » cancelFunc() |
| 91 |
| 92 » » » » // Works for the first timer? |
| 93 » » » » So(t.Reset(time.Hour), ShouldBeFalse) |
| 94 » » » » So((<-t.GetC()).Err, ShouldEqual, context.Cancel
ed) |
| 95 |
| 96 » » » » // Works for the second timer? |
| 97 » » » » So(t.Reset(time.Hour), ShouldBeFalse) |
| 98 » » » » So((<-t.GetC()).Err, ShouldEqual, context.Cancel
ed) |
| 99 » » » }) |
| 100 |
| 101 » » » Convey(`Will trigger when the Context is canceled.`, fun
c() { |
| 102 » » » » clk.SetTimerCallback(func(time.Duration, clock.T
imer) { |
| 103 » » » » » cancelFunc() |
| 104 » » » » }) |
| 105 |
| 106 » » » » // Works for the first timer? |
| 107 » » » » So(t.Reset(time.Hour), ShouldBeFalse) |
| 108 » » » » So((<-t.GetC()).Err, ShouldEqual, context.Cancel
ed) |
| 109 |
| 110 » » » » // Works for the second timer? |
| 111 » » » » So(t.Reset(time.Hour), ShouldBeFalse) |
| 112 » » » » So((<-t.GetC()).Err, ShouldEqual, context.Cancel
ed) |
| 113 » » » }) |
| 114 |
| 115 » » » Convey(`Will use the same channel when reset.`, func() { |
| 116 » » » » timerC := t.GetC() |
| 117 » » » » t.Reset(time.Second) |
| 118 » » » » clk.Add(time.Second) |
| 119 » » » » So(<-timerC, ShouldResemble, clock.TimerResult{T
ime: now.Add(1 * time.Second)}) |
| 120 » » » }) |
| 121 |
| 122 » » » Convey(`Will not signal the timer channel if stopped.`,
func() { |
| 123 » » » » t.Reset(time.Second) |
| 124 » » » » t.Stop() |
| 125 » » » » clk.Add(time.Second) |
| 126 |
| 127 » » » » runtime.Gosched() |
| 128 |
| 129 » » » » triggered := false |
| 130 » » » » select { |
| 131 » » » » case <-t.GetC(): |
| 132 » » » » » triggered = true |
| 133 » » » » default: |
| 134 » » » » » break |
| 135 » » » » } |
| 136 » » » » So(triggered, ShouldBeFalse) |
| 137 » » » }) |
| 138 |
| 139 » » » Convey(`Can set and retrieve timer tags.`, func() { |
| 140 » » » » var tagMu sync.Mutex |
| 141 » » » » tagMap := map[string]struct{}{} |
| 142 |
| 143 » » » » // On the last timer callback, advance time past
the timer threshold. |
| 144 » » » » timers := make([]clock.Timer, 10) |
| 145 » » » » count := 0 |
| 146 » » » » clk.SetTimerCallback(func(_ time.Duration, t clo
ck.Timer) { |
| 147 » » » » » tagMu.Lock() |
| 148 » » » » » defer tagMu.Unlock() |
| 149 |
| 150 » » » » » tagMap[strings.Join(GetTags(t), "")] = s
truct{}{} |
| 151 » » » » » count++ |
| 152 » » » » » if count == len(timers) { |
| 153 » » » » » » clk.Add(time.Second) |
| 154 » » » » » } |
| 155 » » » » }) |
| 156 |
| 157 » » » » for i := range timers { |
| 158 » » » » » t := clk.NewTimer(clock.Tag(ctx, fmt.Spr
intf("%d", i))) |
| 159 » » » » » t.Reset(time.Second) |
| 160 » » » » » timers[i] = t |
| 161 » » » » } |
| 162 |
| 163 » » » » // Wait until all timers have expired. |
| 164 » » » » for _, t := range timers { |
| 165 » » » » » <-t.GetC() |
| 166 » » » » } |
| 167 |
| 168 » » » » // Did we see all of their tags? |
| 169 » » » » for i := range timers { |
| 170 » » » » » _, ok := tagMap[fmt.Sprintf("%d", i)] |
| 171 » » » » » So(ok, ShouldBeTrue) |
| 172 » » » » } |
| 173 » » » }) |
| 174 |
| 175 » » » Convey(`A non-test timer has a nil tag.`, func() { |
| 176 » » » » t := trashTimer{} |
| 177 » » » » So(GetTags(t), ShouldBeNil) |
68 }) | 178 }) |
69 }) | 179 }) |
70 | 180 |
71 » » Convey(`Multiple goroutines using the timer...`, func() { | 181 » » Convey(`Multiple goroutines using timers...`, func() { |
72 // Mark when timers are started, so we can ensure that o
ur signalling | 182 // Mark when timers are started, so we can ensure that o
ur signalling |
73 // happens after the timers have been instantiated. | 183 // happens after the timers have been instantiated. |
74 timerStartedC := make(chan bool) | 184 timerStartedC := make(chan bool) |
75 » » » c.SetTimerCallback(func(_ time.Duration, _ clock.Timer)
{ | 185 » » » clk.SetTimerCallback(func(_ time.Duration, _ clock.Timer
) { |
76 timerStartedC <- true | 186 timerStartedC <- true |
77 }) | 187 }) |
78 | 188 |
79 » » » resultC := make(chan time.Time) | 189 » » » resultC := make(chan clock.TimerResult) |
80 for i := time.Duration(0); i < 5; i++ { | 190 for i := time.Duration(0); i < 5; i++ { |
81 go func(d time.Duration) { | 191 go func(d time.Duration) { |
82 » » » » » timer := c.NewTimer() | 192 » » » » » timer := clk.NewTimer(ctx) |
83 timer.Reset(d) | 193 timer.Reset(d) |
84 resultC <- <-timer.GetC() | 194 resultC <- <-timer.GetC() |
85 }(i * time.Second) | 195 }(i * time.Second) |
86 <-timerStartedC | 196 <-timerStartedC |
87 } | 197 } |
88 | 198 |
89 moreResults := func() bool { | 199 moreResults := func() bool { |
90 select { | 200 select { |
91 case <-resultC: | 201 case <-resultC: |
92 return true | 202 return true |
93 default: | 203 default: |
94 return false | 204 return false |
95 } | 205 } |
96 } | 206 } |
97 | 207 |
98 // Advance the clock to +2s. Three timers should signal. | 208 // Advance the clock to +2s. Three timers should signal. |
99 » » » c.Set(now.Add(2 * time.Second)) | 209 » » » clk.Set(now.Add(2 * time.Second)) |
100 <-resultC | 210 <-resultC |
101 <-resultC | 211 <-resultC |
102 <-resultC | 212 <-resultC |
103 So(moreResults(), ShouldBeFalse) | 213 So(moreResults(), ShouldBeFalse) |
104 | 214 |
105 // Advance clock to +3s. One timer should signal. | 215 // Advance clock to +3s. One timer should signal. |
106 » » » c.Set(now.Add(3 * time.Second)) | 216 » » » clk.Set(now.Add(3 * time.Second)) |
107 <-resultC | 217 <-resultC |
108 So(moreResults(), ShouldBeFalse) | 218 So(moreResults(), ShouldBeFalse) |
109 | 219 |
110 // Advance clock to +10s. One final timer should signal. | 220 // Advance clock to +10s. One final timer should signal. |
111 » » » c.Set(now.Add(10 * time.Second)) | 221 » » » clk.Set(now.Add(10 * time.Second)) |
112 <-resultC | 222 <-resultC |
113 So(moreResults(), ShouldBeFalse) | 223 So(moreResults(), ShouldBeFalse) |
114 }) | 224 }) |
115 }) | 225 }) |
116 } | 226 } |
| 227 |
| 228 func TestTimerTags(t *testing.T) { |
| 229 t.Parallel() |
| 230 |
| 231 Convey(`A context with tags {"A", "B"}`, t, func() { |
| 232 c := clock.Tag(clock.Tag(context.Background(), "A"), "B") |
| 233 |
| 234 Convey(`Has tags, {"A", "B"}`, func() { |
| 235 So(clock.Tags(c), ShouldResemble, []string{"A", "B"}) |
| 236 }) |
| 237 |
| 238 Convey(`Will be retained by a testclock.Timer.`, func() { |
| 239 tc := New(TestTimeUTC) |
| 240 t := tc.NewTimer(c) |
| 241 |
| 242 So(GetTags(t), ShouldResemble, []string{"A", "B"}) |
| 243 |
| 244 Convey(`The timer tests positive for tags {"A", "B"}`, f
unc() { |
| 245 So(HasTags(t, "A", "B"), ShouldBeTrue) |
| 246 }) |
| 247 |
| 248 Convey(`The timer tests negative for tags {"A"}, {"B"},
and {"A", "C"}`, func() { |
| 249 So(HasTags(t, "A"), ShouldBeFalse) |
| 250 So(HasTags(t, "B"), ShouldBeFalse) |
| 251 So(HasTags(t, "A", "C"), ShouldBeFalse) |
| 252 }) |
| 253 }) |
| 254 |
| 255 Convey(`A non-test timer tests negative for {"A"} and {"A", "B"}
.`, func() { |
| 256 t := &trashTimer{} |
| 257 |
| 258 So(HasTags(t, "A"), ShouldBeFalse) |
| 259 So(HasTags(t, "A", "B"), ShouldBeFalse) |
| 260 }) |
| 261 }) |
| 262 } |
OLD | NEW |