forked from roylee0704/gron
-
Notifications
You must be signed in to change notification settings - Fork 0
/
cron_test.go
396 lines (335 loc) · 9.09 KB
/
cron_test.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
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
package gron
import (
"context"
"fmt"
"reflect"
"sort"
"strings"
"sync"
"testing"
"time"
"github.com/fred07/gron/xtime"
)
// Most test jobs scheduled to run at 1 second mark.
// Test expects to fail after OneSecond: 1.05 seconds.
const OneSecond = 1*time.Second + 50*time.Millisecond
// start cron, stop cron successfully.
func TestNoEntries(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cron := New()
cron.Start(ctx)
select {
case <-time.After(OneSecond):
t.FailNow()
case <-stop(cron):
}
}
// hijack time.After, start the cron, simulate a delay.
// empty entries shall not trigger to run next job in next second.
func TestNoPhantomJobs(t *testing.T) {
entry := 0
// overriding internal state func
after = func(d time.Duration) <-chan time.Time {
entry++
return time.After(d)
}
defer func() {
after = time.After
}() // proper tear down
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cron := New()
cron.Start(ctx)
defer cron.Stop()
time.Sleep(1 * time.Millisecond) // simulate a delay
if entry > 1 {
t.Errorf("phantom job had run %d time(s).", entry)
}
}
// add a job, start a cron, expect it runs
func TestAddBeforeRun(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
done := make(chan struct{})
cron := New()
cron.AddFunc(Every(1*time.Second), func() { done <- struct{}{} })
cron.Start(ctx)
defer cron.Stop()
select {
case <-time.After(OneSecond):
t.FailNow()
case <-done:
}
}
// start a cron, add a job, expect it runs
func TestAddWhileRun(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
done := make(chan struct{})
cron := New()
cron.Start(ctx)
defer cron.Stop()
cron.AddFunc(Every(1*time.Second), func() { done <- struct{}{} })
select {
case <-time.After(OneSecond):
t.FailNow()
case <-done:
}
}
// Test that invoking stop() before start() silently returns,
// without blocking the stop channel
func TestStopWithoutStart(t *testing.T) {
cron := New()
cron.Stop()
}
// start cron, stop cron, add a job, verify job shouldn't run.
func TestJobsDontRunAfterStop(t *testing.T) {
wg := &sync.WaitGroup{}
wg.Add(1)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cron := New()
cron.Start(ctx)
cron.Stop()
cron.AddFunc(Every(1*time.Second), func() { wg.Done() })
select {
case <-time.After(OneSecond):
// no job has run
case <-wait(wg):
t.FailNow()
}
}
func TestGracefullyStop(t *testing.T) {
failCh := make(chan bool, 1)
successCh := make(chan bool, 1)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() {
cron := New()
cron.Start(ctx)
cron.AddFunc(Every(3*time.Second), func() {
time.Sleep(time.Duration(2) * time.Second) // make job running while stopping
successCh <- true // success signal
})
time.Sleep(time.Duration(4) * time.Second) // prevent stop too fast
cron.GracefullyStop() // call stop
failCh <- true // fail signal
}()
loop:
for {
select {
case <-successCh:
break loop
case <-failCh:
t.FailNow()
default:
}
}
}
// Test that entries are sorted correctly.
// Adds an immediate entry, make sure it runs immediately.
// Subsequent entries are checked and run at same instant, iff possessed
// same schedule as first entry.
func TestMultipleEntries(t *testing.T) {
wg := &sync.WaitGroup{}
wg.Add(3)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cron := New()
cron.AddFunc(Every(5*time.Minute), func() {})
cron.AddFunc(Every(1*time.Second), func() { wg.Done() })
cron.AddFunc(Every(1*time.Second), func() { wg.Done() })
cron.AddFunc(Every(1*time.Second), func() { wg.Done() })
cron.AddFunc(Every(4*xtime.Week), func() {})
cron.Start(ctx)
defer cron.Stop()
select {
case <-time.After(OneSecond):
t.FailNow()
case <-wait(wg):
}
}
// Test that job runs n times after (n * p) period.
func TestRunJobTwice(t *testing.T) {
wg := &sync.WaitGroup{}
wg.Add(2)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cron := New()
cron.Start(ctx)
defer cron.Stop()
cron.AddFunc(Every(1*xtime.Day), func() { wg.Done() })
cron.AddFunc(Every(1*xtime.Week), func() { wg.Done() })
cron.AddFunc(Every(1*time.Second), func() { wg.Done() })
select {
case <-time.After(2 * OneSecond):
t.FailNow()
case <-wait(wg):
}
}
// arbitrary job struct, with god's view enabled.
type arbitraryJob struct {
wg *sync.WaitGroup // god's update
id string // god's record
}
// implements runnable
func (j arbitraryJob) Run(wg *sync.WaitGroup) {
if j.wg != nil {
j.wg.Done()
}
}
// simple test on job types
func TestJobImplementer(t *testing.T) {
wg := &sync.WaitGroup{}
wg.Add(2)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cron := New()
cron.Add(Every(1*xtime.Day), arbitraryJob{wg, "job-1"}) // merely distraction
cron.Add(Every(1*time.Second), arbitraryJob{wg, "job-2"})
cron.Add(Every(1*xtime.Week), arbitraryJob{wg, "job-3"}) // merely distraction
cron.Start(ctx)
defer cron.Stop()
select {
case <-time.After(2 * OneSecond):
t.FailNow()
case <-wait(wg):
}
}
// Test that entries are in correct sequence after n run.
func TestEntryOrdering(t *testing.T) {
wg := &sync.WaitGroup{}
wg.Add(2)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cron := New()
cron.Add(Every(40*time.Second), arbitraryJob{wg, "job-1"})
cron.Add(Every(1*time.Second), arbitraryJob{wg, "job-2"})
cron.Add(Every(1*xtime.Week), arbitraryJob{wg, "job-3"})
cron.Add(Every(12*time.Second), arbitraryJob{wg, "job-4"})
cron.Add(Every(8*time.Second), arbitraryJob{wg, "job-5"})
cron.Start(ctx)
select {
case <-time.After(2 * OneSecond):
t.FailNow()
case <-wait(wg):
}
cron.Stop()
want := []string{"job-2", "job-5", "job-4", "job-1", "job-3"}
var got []string
for _, e := range cron.Entries() {
got = append(got, e.Job.(arbitraryJob).id)
}
for i := range got {
if want[i] != got[i] {
t.Errorf("incorrect sequencing: (want) %q != %q (got)", want, got)
break
}
}
}
// Test that entries are chronologically sorted
func TestByTimeSort(t *testing.T) {
tests := []struct {
entries string
want string
}{
// simple cases
{"10:05, 10:04, 10:03", "10:03, 10:04, 10:05"},
{"10:05, 10:04, 10:03", "10:03, 10:04, 10:05"},
// hours dominate
{"7:00, 8:00, 9:00", "7:00, 8:00, 9:00"},
{"9:00, 8:00, 7:00", "7:00, 8:00, 9:00"},
{"9:00, 8:49, 8:09", "8:09, 8:49, 9:00"},
// seconds dominate
{"00:00:01, 00:00:03, 00:00:30", "00:00:01, 00:00:03, 00:00:30"},
{"00:00:03, 00:00:01, 00:00:30", "00:00:01, 00:00:03, 00:00:30"},
{"00:05:10, 00:04:20, 00:03:30", "00:03:30, 00:04:20, 00:05:10"},
// days dominate
{
"Wed Jun 8 9:05 2016, Tue Jun 7 8:04 2016, Wed Jun 8 9:01 2016",
"Tue Jun 7 8:04 2016, Wed Jun 8 9:01 2016, Wed Jun 8 9:05 2016",
},
// months dominate
{
"Sun Jun 4 9:05 2016, Sun Feb 7 8:04 2016, Sun May 8 9:01 2016",
"Sun Feb 7 8:04 2016, Sun May 8 9:01 2016, Sun Jun 4 9:05 2016",
},
// zero hours sort as intended
{"00:00, 00:00, 00:10", "00:00, 00:00, 00:10"},
// zero minutes sort as intended
{"00:00:00, 00:00:00, 00:00:10", "00:00:00, 00:00:00, 00:00:10"},
// zero times (uninitialised time) should push to back of queue.
{
"2016-01-01 00:00, 2016-01-01 00:00, 0001-01-01 00:00",
"2016-01-01 00:00, 2016-01-01 00:00, 0001-01-01 00:00",
},
{
"2016-01-01 00:01, 2016-01-01 00:00, 0001-01-01 00:00",
"2016-01-01 00:00, 2016-01-01 00:01, 0001-01-01 00:00",
},
{
"0001-01-01 00:00, 2016-01-01 00:01, 2016-01-01 00:00",
"2016-01-01 00:00, 2016-01-01 00:01, 0001-01-01 00:00",
},
{
"2016-01-01 00:01, 0001-01-01 00:00, 2016-01-01 00:00",
"2016-01-01 00:00, 2016-01-01 00:01, 0001-01-01 00:00",
},
{
"0001-01-01 00:00, 0001-01-01 00:00, 2016-01-01 00:00",
"2016-01-01 00:00, 0001-01-01 00:00, 0001-01-01 00:00",
},
}
for i, test := range tests {
got := mockEntries(getTimes(test.entries))
sort.Sort(byTime(got))
want := mockEntries(getTimes(test.want))
if !reflect.DeepEqual(got, want) {
t.Errorf("entries[%d] out of order: \n%v (want)\n%v (got)", i, toS(want), toS(got))
}
}
}
func mockEntries(nexts []time.Time) []*Entry {
var entries []*Entry
for _, n := range nexts {
entries = append(entries, &Entry{Next: n})
}
return entries
}
// getTimes splits comma-separated time.
func getTimes(s string) []time.Time {
ts := strings.Split(s, ",")
ret := make([]time.Time, len(ts))
for i, t := range ts {
ret[i] = getTime(strings.Trim(t, " "))
}
return ret
}
// wrapper to stringify time instant t
type toS []*Entry
func (entries toS) String() string {
var ret string
for _, e := range entries {
ret += fmt.Sprintf("[%v] ", e.Next)
}
return ret
}
// wait signals back when WaitGroup has wait().
func wait(wg *sync.WaitGroup) <-chan bool {
done := make(chan bool)
go func() {
wg.Wait()
done <- true
}()
return done
}
// stop signals back when cron has stop()
func stop(c *Cron) <-chan bool {
done := make(chan bool)
go func() {
c.Stop()
done <- true
}()
return done
}