Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(49)

Side by Side Diff: scheduler/appengine/engine/cron/machine_test.go

Issue 2980943002: Add cron.Machine state machine. (Closed)
Patch Set: typos Created 3 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « scheduler/appengine/engine/cron/machine.go ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2017 The LUCI Authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 package cron
16
17 import (
18 "testing"
19 "time"
20
21 "github.com/luci/luci-go/scheduler/appengine/schedule"
22 . "github.com/smartystreets/goconvey/convey"
23 )
24
25 func TestMachine(t *testing.T) {
26 t.Parallel()
27
28 at0min, _ := schedule.Parse("0 * * * *", 0)
29 at45min, _ := schedule.Parse("45 * * * *", 0)
30 each30min, _ := schedule.Parse("with 30m interval", 0)
31 each10min, _ := schedule.Parse("with 10m interval", 0)
32 never, _ := schedule.Parse("triggered", 0)
33
34 Convey("Absolute schedule", t, func() {
35 tm := testMachine{
36 Now: parseTime("00:15"),
37 Schedule: at0min,
38 }
39
40 // Enabling the job schedules the first tick based on the schedu le.
41 err := tm.roll(func(m *Machine) error {
42 m.Enable()
43 return nil
44 })
45 So(err, ShouldBeNil)
46 So(tm.Actions, ShouldResemble, []Action{
47 TickLaterAction{
48 When: parseTime("01:00"),
49 TickNonce: 1,
50 },
51 })
52
53 // RewindIfNecessary does nothing, the tick is already set.
54 err = tm.roll(func(m *Machine) error {
55 m.RewindIfNecessary()
56 return nil
57 })
58 So(err, ShouldBeNil)
59 So(tm.Actions, ShouldBeNil)
60
61 // Early tick is ignored with an error.
62 tm.Now = parseTime("00:59")
63 err = tm.roll(func(m *Machine) error { return m.OnTimerTick(1) } )
64 So(err.Error(), ShouldEqual, "tick happened 60.0 sec before it w as expected")
65 So(tm.Actions, ShouldEqual, nil)
66
67 tm.Now = parseTime("01:01") // acceptable tick time (slightly la te)!
68
69 // A tick with wrong nonce is silently skipped.
70 err = tm.roll(func(m *Machine) error { return m.OnTimerTick(123) })
71 So(err, ShouldBeNil)
72 So(tm.Actions, ShouldEqual, nil)
73
74 // The correct tick comes. Invocation is started and new tick is scheduled.
75 err = tm.roll(func(m *Machine) error { return m.OnTimerTick(1) } )
76 So(err, ShouldBeNil)
77 So(tm.Actions, ShouldResemble, []Action{
78 StartInvocationAction{},
79 TickLaterAction{
80 When: parseTime("02:00"),
81 TickNonce: 2,
82 },
83 })
84
85 // Disabling the job.
86 err = tm.roll(func(m *Machine) error {
87 m.Disable()
88 return nil
89 })
90 So(err, ShouldBeNil)
91 So(tm.Actions, ShouldBeNil)
92
93 // It silently skips the tick now.
94 tm.Now = parseTime("02:00")
95 err = tm.roll(func(m *Machine) error { return m.OnTimerTick(2) } )
96 So(err, ShouldBeNil)
97 So(tm.Actions, ShouldEqual, nil)
98 })
99
100 Convey("Relative schedule", t, func() {
101 tm := testMachine{
102 Now: parseTime("00:00"),
103 Schedule: each30min,
104 }
105
106 // Enabling the job schedules the first tick based on the schedu le.
107 err := tm.roll(func(m *Machine) error {
108 m.Enable()
109 return nil
110 })
111 So(err, ShouldBeNil)
112 So(tm.Actions, ShouldResemble, []Action{
113 TickLaterAction{
114 When: parseTime("00:30"),
115 TickNonce: 1,
116 },
117 })
118
119 // RewindIfNecessary does nothing, the tick is already set.
120 err = tm.roll(func(m *Machine) error {
121 m.RewindIfNecessary()
122 return nil
123 })
124 So(err, ShouldBeNil)
125 So(tm.Actions, ShouldBeNil)
126
127 // Tick arrives (slightly late). The invocation is started, but the next
128 // tick is _not_ set.
129 tm.Now = parseTime("00:31")
130 err = tm.roll(func(m *Machine) error { return m.OnTimerTick(1) } )
131 So(err, ShouldBeNil)
132 So(tm.Actions, ShouldResemble, []Action{
133 StartInvocationAction{},
134 })
135
136 // Some time later (when invocation has presumably finished), re wind the
137 // clock. It sets a new tick 30min from now.
138 tm.Now = parseTime("00:40")
139 err = tm.roll(func(m *Machine) error {
140 m.RewindIfNecessary()
141 return nil
142 })
143 So(err, ShouldBeNil)
144 So(tm.Actions, ShouldResemble, []Action{
145 TickLaterAction{
146 When: parseTime("01:10"), // 40min + 30min
147 TickNonce: 2,
148 },
149 })
150 })
151
152 Convey("Relative schedule, distant future", t, func() {
153 tm := testMachine{
154 Now: parseTime("00:00"),
155 Schedule: never,
156 }
157
158 // Enabling the job does nothing.
159 err := tm.roll(func(m *Machine) error {
160 m.Enable()
161 return nil
162 })
163 So(err, ShouldBeNil)
164 So(tm.Actions, ShouldBeNil)
165
166 // Rewinding does nothing.
167 err = tm.roll(func(m *Machine) error {
168 m.RewindIfNecessary()
169 return nil
170 })
171 So(err, ShouldBeNil)
172 So(tm.Actions, ShouldBeNil)
173
174 // Ticking does nothing.
175 err = tm.roll(func(m *Machine) error { return m.OnTimerTick(1) } )
176 So(err, ShouldBeNil)
177 So(tm.Actions, ShouldBeNil)
178 })
179
180 Convey("Schedule changes", t, func() {
181 // Start with absolute.
182 tm := testMachine{
183 Now: parseTime("00:00"),
184 Schedule: at0min,
185 }
186
187 // The first tick is scheduled to 1h from now.
188 err := tm.roll(func(m *Machine) error {
189 m.Enable()
190 return nil
191 })
192 So(err, ShouldBeNil)
193 So(tm.Actions, ShouldResemble, []Action{
194 TickLaterAction{
195 When: parseTime("01:00"),
196 TickNonce: 1,
197 },
198 })
199
200 // 10 min later switch to the relative schedule. It reschedules the tick
201 // to 30 min since the _previous action_ (which was 'Enable').
202 tm.Now = parseTime("00:10")
203 tm.Schedule = each30min
204 err = tm.roll(func(m *Machine) error {
205 m.OnScheduleChange()
206 return nil
207 })
208 So(err, ShouldBeNil)
209 So(tm.Actions, ShouldResemble, []Action{
210 TickLaterAction{
211 When: parseTime("00:30"),
212 TickNonce: 2,
213 },
214 })
215
216 // The operation is idempotent. No new tick is scheduled when we try again.
217 tm.Now = parseTime("00:15")
218 err = tm.roll(func(m *Machine) error {
219 m.OnScheduleChange()
220 return nil
221 })
222 So(err, ShouldBeNil)
223 So(tm.Actions, ShouldBeNil)
224
225 // The scheduled tick comes. Since it is a relative schedule, no new tick
226 // is scheduled.
227 tm.Now = parseTime("00:30")
228 err = tm.roll(func(m *Machine) error { return m.OnTimerTick(2) } )
229 So(err, ShouldBeNil)
230 So(tm.Actions, ShouldResemble, []Action{
231 StartInvocationAction{},
232 })
233
234 // Some time later we switch it to another relative schedule. No thing
235 // happens, since we are waiting for a rewind now anyway.
236 tm.Now = parseTime("00:40")
237 tm.Schedule = each10min
238 err = tm.roll(func(m *Machine) error {
239 m.OnScheduleChange()
240 return nil
241 })
242 So(err, ShouldBeNil)
243 So(tm.Actions, ShouldBeNil)
244
245 // Now we switch back to the absolute schedule. It schedules a n ew tick.
246 tm.Now = parseTime("01:30")
247 tm.Schedule = at0min
248 err = tm.roll(func(m *Machine) error {
249 m.OnScheduleChange()
250 return nil
251 })
252 So(err, ShouldBeNil)
253 So(tm.Actions, ShouldResemble, []Action{
254 TickLaterAction{
255 When: parseTime("02:00"),
256 TickNonce: 3,
257 },
258 })
259
260 // Changing the absolute schedule moves the tick accordingly.
261 tm.Schedule = at45min
262 err = tm.roll(func(m *Machine) error {
263 m.OnScheduleChange()
264 return nil
265 })
266 So(err, ShouldBeNil)
267 So(tm.Actions, ShouldResemble, []Action{
268 TickLaterAction{
269 When: parseTime("01:45"),
270 TickNonce: 4,
271 },
272 })
273
274 // Switching to 'triggered' schedule "disarms" the current tick by replacing
275 // it with "tick in the distant future". This doesn't emit any a ctions,
276 // since we can't actually schedule tick in the distant future.
277 tm.Schedule = never
278 err = tm.roll(func(m *Machine) error {
279 m.OnScheduleChange()
280 return nil
281 })
282 So(err, ShouldBeNil)
283 So(tm.Actions, ShouldBeNil)
284 So(tm.State.LastTick, ShouldResemble, TickLaterAction{
285 When: schedule.DistantFuture,
286 TickNonce: 5,
287 })
288
289 // Enabling back absolute schedule places a new tick.
290 tm.Now = parseTime("01:30")
291 tm.Schedule = at0min
292 err = tm.roll(func(m *Machine) error {
293 m.OnScheduleChange()
294 return nil
295 })
296 So(err, ShouldBeNil)
297 So(tm.Actions, ShouldResemble, []Action{
298 TickLaterAction{
299 When: parseTime("02:00"),
300 TickNonce: 6,
301 },
302 })
303
304 // Schedule changes do nothing to disabled jobs.
305 tm.Schedule = at45min
306 err = tm.roll(func(m *Machine) error {
307 m.Disable()
308 m.OnScheduleChange()
309 return nil
310 })
311 So(err, ShouldBeNil)
312 So(tm.Actions, ShouldBeNil)
313 })
314
315 Convey("Petty code coverage", t, func() {
316 // Just to get 100% code coverage...
317 So((StartInvocationAction{}).IsAction(), ShouldBeTrue)
318 So((TickLaterAction{}).IsAction(), ShouldBeTrue)
319 })
320 }
321
322 func parseTime(str string) time.Time {
323 t, err := time.Parse(time.RFC822, "01 Jan 17 "+str+" UTC")
324 if err != nil {
325 panic(err)
326 }
327 return t
328 }
329
330 type testMachine struct {
331 State State
332 Schedule *schedule.Schedule
333 Now time.Time
334 Nonces int64
335 Actions []Action
336 }
337
338 func (t *testMachine) roll(cb func(*Machine) error) error {
339 m := Machine{
340 Now: t.Now,
341 Schedule: t.Schedule,
342 Nonce: func() int64 {
343 t.Nonces++
344 return t.Nonces
345 },
346 State: t.State,
347 }
348
349 if err := cb(&m); err != nil {
350 return err
351 }
352
353 t.State = m.State
354 t.Actions = m.Actions
355 return nil
356 }
OLDNEW
« no previous file with comments | « scheduler/appengine/engine/cron/machine.go ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698