rufus-scheduler 2.0.24 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/CHANGELOG.txt +76 -0
  2. data/CREDITS.txt +23 -0
  3. data/LICENSE.txt +1 -1
  4. data/README.md +1439 -0
  5. data/Rakefile +1 -5
  6. data/TODO.txt +149 -55
  7. data/lib/rufus/{sc → scheduler}/cronline.rb +167 -53
  8. data/lib/rufus/scheduler/job_array.rb +92 -0
  9. data/lib/rufus/scheduler/jobs.rb +633 -0
  10. data/lib/rufus/scheduler/locks.rb +95 -0
  11. data/lib/rufus/scheduler/util.rb +306 -0
  12. data/lib/rufus/scheduler/zones.rb +174 -0
  13. data/lib/rufus/scheduler/zotime.rb +154 -0
  14. data/lib/rufus/scheduler.rb +608 -27
  15. data/rufus-scheduler.gemspec +6 -4
  16. data/spec/basics_spec.rb +54 -0
  17. data/spec/cronline_spec.rb +479 -152
  18. data/spec/error_spec.rb +139 -0
  19. data/spec/job_array_spec.rb +39 -0
  20. data/spec/job_at_spec.rb +58 -0
  21. data/spec/job_cron_spec.rb +128 -0
  22. data/spec/job_every_spec.rb +104 -0
  23. data/spec/job_in_spec.rb +20 -0
  24. data/spec/job_interval_spec.rb +68 -0
  25. data/spec/job_repeat_spec.rb +357 -0
  26. data/spec/job_spec.rb +498 -109
  27. data/spec/lock_custom_spec.rb +47 -0
  28. data/spec/lock_flock_spec.rb +47 -0
  29. data/spec/lock_lockfile_spec.rb +61 -0
  30. data/spec/lock_spec.rb +59 -0
  31. data/spec/parse_spec.rb +263 -0
  32. data/spec/schedule_at_spec.rb +158 -0
  33. data/spec/schedule_cron_spec.rb +66 -0
  34. data/spec/schedule_every_spec.rb +109 -0
  35. data/spec/schedule_in_spec.rb +80 -0
  36. data/spec/schedule_interval_spec.rb +128 -0
  37. data/spec/scheduler_spec.rb +928 -124
  38. data/spec/spec_helper.rb +126 -0
  39. data/spec/threads_spec.rb +96 -0
  40. data/spec/zotime_spec.rb +396 -0
  41. metadata +56 -33
  42. data/README.rdoc +0 -661
  43. data/lib/rufus/otime.rb +0 -3
  44. data/lib/rufus/sc/jobqueues.rb +0 -160
  45. data/lib/rufus/sc/jobs.rb +0 -471
  46. data/lib/rufus/sc/rtime.rb +0 -363
  47. data/lib/rufus/sc/scheduler.rb +0 -636
  48. data/lib/rufus/sc/version.rb +0 -32
  49. data/spec/at_in_spec.rb +0 -47
  50. data/spec/at_spec.rb +0 -125
  51. data/spec/blocking_spec.rb +0 -64
  52. data/spec/cron_spec.rb +0 -134
  53. data/spec/every_spec.rb +0 -304
  54. data/spec/exception_spec.rb +0 -113
  55. data/spec/in_spec.rb +0 -150
  56. data/spec/mutex_spec.rb +0 -159
  57. data/spec/rtime_spec.rb +0 -137
  58. data/spec/schedulable_spec.rb +0 -97
  59. data/spec/spec_base.rb +0 -87
  60. data/spec/stress_schedule_unschedule_spec.rb +0 -159
  61. data/spec/timeout_spec.rb +0 -148
  62. data/test/kjw.rb +0 -113
  63. data/test/t.rb +0 -20
@@ -2,247 +2,1051 @@
2
2
  #
3
3
  # Specifying rufus-scheduler
4
4
  #
5
- # Sat Mar 21 17:43:23 JST 2009
5
+ # Wed Apr 17 06:00:59 JST 2013
6
6
  #
7
7
 
8
- require 'spec_base'
8
+ require 'spec_helper'
9
9
 
10
10
 
11
- describe SCHEDULER_CLASS do
11
+ describe Rufus::Scheduler do
12
12
 
13
- it 'stops' do
13
+ describe '#initialize' do
14
14
 
15
- var = nil
15
+ it 'starts the scheduler thread' do
16
16
 
17
- s = start_scheduler
18
- s.in('3s') { var = true }
17
+ scheduler = Rufus::Scheduler.new
19
18
 
20
- stop_scheduler(s)
19
+ t = Thread.list.find { |t|
20
+ t[:name] == "rufus_scheduler_#{scheduler.object_id}_scheduler"
21
+ }
21
22
 
22
- var.should == nil
23
- sleep 4
24
- var.should == nil
23
+ expect(t[:rufus_scheduler]).to eq(scheduler)
24
+ end
25
+
26
+ it 'sets :name and :rufus_scheduler in the scheduler thread local vars' do
27
+
28
+ scheduler = Rufus::Scheduler.new
29
+
30
+ expect(scheduler.thread[:name]).to eq(
31
+ "rufus_scheduler_#{scheduler.object_id}_scheduler"
32
+ )
33
+ expect(scheduler.thread[:rufus_scheduler]).to eq(
34
+ scheduler
35
+ )
36
+ end
37
+
38
+ it 'accepts a :frequency => integer option' do
39
+
40
+ scheduler = Rufus::Scheduler.new(:frequency => 2)
41
+
42
+ expect(scheduler.frequency).to eq(2)
43
+ end
44
+
45
+ it 'accepts a :frequency => "2h1m" option' do
46
+
47
+ scheduler = Rufus::Scheduler.new(:frequency => '2h1m')
48
+
49
+ expect(scheduler.frequency).to eq(3600 * 2 + 60)
50
+ end
51
+
52
+ it 'accepts a :thread_name option' do
53
+
54
+ scheduler = Rufus::Scheduler.new(:thread_name => 'oliphant')
55
+
56
+ t = Thread.list.find { |t| t[:name] == 'oliphant' }
57
+
58
+ expect(t[:rufus_scheduler]).to eq(scheduler)
59
+ end
60
+
61
+ #it 'accepts a :min_work_threads option' do
62
+ # scheduler = Rufus::Scheduler.new(:min_work_threads => 9)
63
+ # scheduler.min_work_threads.should == 9
64
+ #end
65
+
66
+ it 'accepts a :max_work_threads option' do
67
+
68
+ scheduler = Rufus::Scheduler.new(:max_work_threads => 9)
69
+
70
+ expect(scheduler.max_work_threads).to eq(9)
71
+ end
72
+ end
73
+
74
+ before :each do
75
+ @scheduler = Rufus::Scheduler.new
76
+ end
77
+ after :each do
78
+ @scheduler.shutdown
79
+ end
80
+
81
+ describe 'a schedule method' do
82
+
83
+ it 'passes the job to its block when it triggers' do
84
+
85
+ j = nil
86
+ job = @scheduler.schedule_in('0s') { |jj| j = jj }
87
+
88
+ sleep 0.4
89
+
90
+ expect(j).to eq(job)
91
+ end
92
+
93
+ it 'passes the trigger time as second block argument' do
94
+
95
+ t = nil
96
+ @scheduler.schedule_in('0s') { |jj, tt| t = tt }
97
+
98
+ sleep 0.4
99
+
100
+ expect(t.class).to eq(Time)
101
+ end
102
+
103
+ class MyHandler
104
+ attr_reader :counter
105
+ def initialize
106
+ @counter = 0
107
+ end
108
+ def call(job, time)
109
+ @counter = @counter + 1
110
+ end
111
+ end
112
+
113
+ it 'accepts a callable object instead of a block' do
114
+
115
+ mh = MyHandler.new
116
+
117
+ @scheduler.schedule_in('0s', mh)
118
+
119
+ sleep 0.4
120
+
121
+ expect(mh.counter).to eq(1)
122
+ end
123
+
124
+ class MyOtherHandler
125
+ attr_reader :counter
126
+ def initialize
127
+ @counter = 0
128
+ end
129
+ def call
130
+ @counter = @counter + 1
131
+ end
132
+ end
133
+
134
+ it 'accepts a callable obj instead of a block (#call with no args)' do
135
+
136
+ job = @scheduler.schedule_in('0s', MyOtherHandler.new)
137
+
138
+ sleep 0.4
139
+
140
+ expect(job.handler.counter).to eq(1)
141
+ end
142
+
143
+ it 'accepts a class as callable' do
144
+
145
+ job =
146
+ @scheduler.schedule_in('0s', Class.new do
147
+ attr_reader :value
148
+ def call
149
+ @value = 7
150
+ end
151
+ end)
152
+
153
+ sleep 0.4
154
+
155
+ expect(job.handler.value).to eq(7)
156
+ end
157
+
158
+ it 'raises if the scheduler is shutting down' do
159
+
160
+ @scheduler.shutdown
161
+
162
+ expect {
163
+ @scheduler.in('0s') { puts 'hhhhhhhhhhhello!!' }
164
+ }.to raise_error(Rufus::Scheduler::NotRunningError)
165
+ end
25
166
  end
26
167
 
27
- unless SCHEDULER_CLASS == Rufus::Scheduler::EmScheduler
168
+ describe '#in / #at' do
169
+
170
+ # scheduler.in(2.hours.from_now) { ... }
171
+
172
+ it 'accepts point in time and duration indifferently (#in)' do
28
173
 
29
- it 'sets a default scheduler thread name' do
174
+ seen = false
30
175
 
31
- s = start_scheduler
176
+ t = Time.now + 1
32
177
 
33
- s.instance_variable_get(:@thread)['name'].should match(
34
- /Rufus::Scheduler::.*Scheduler - \d+\.\d+\.\d+/)
178
+ @scheduler.in(t) { seen = true }
35
179
 
36
- stop_scheduler(s)
180
+ sleep 0.1 while seen != true
37
181
  end
38
182
 
39
- it 'sets the scheduler thread name' do
183
+ it 'accepts point in time and duration indifferently (#at)' do
40
184
 
41
- s = start_scheduler(:thread_name => 'nada')
42
- s.instance_variable_get(:@thread)['name'].should == 'nada'
185
+ seen = false
43
186
 
44
- stop_scheduler(s)
187
+ t = 1
188
+
189
+ @scheduler.at(t) { seen = true }
190
+
191
+ sleep 0.1 while seen != true
45
192
  end
46
193
  end
47
194
 
48
- it 'accepts a custom frequency' do
195
+ describe '#schedule' do
49
196
 
50
- var = nil
197
+ it 'accepts a duration and schedules an InJob' do
51
198
 
52
- s = start_scheduler(:frequency => 3.0)
199
+ j = @scheduler.schedule '1s' do; end
53
200
 
54
- s.in('1s') { var = true }
201
+ expect(j.class).to eq(Rufus::Scheduler::InJob)
202
+ expect(j.original).to eq('1s')
203
+ end
55
204
 
56
- sleep 1
57
- var.should == nil
205
+ it 'accepts a point in time and schedules an AtJob' do
58
206
 
59
- sleep 1
60
- var.should == nil
207
+ j = @scheduler.schedule '2070/12/24 23:00' do; end
61
208
 
62
- sleep 2
63
- var.should == true
209
+ expect(j.class).to eq(Rufus::Scheduler::AtJob)
210
+ expect(j.next_time.strftime('%Y %m %d')).to eq('2070 12 24')
211
+ end
212
+
213
+ it 'accepts a cron string and schedules a CronJob' do
214
+
215
+ j = @scheduler.schedule '* * * * *' do; end
64
216
 
65
- stop_scheduler(s)
217
+ expect(j.class).to eq(Rufus::Scheduler::CronJob)
218
+ end
66
219
  end
67
220
 
68
- context 'pause/resume' do
221
+ describe '#repeat' do
69
222
 
70
- before(:each) do
71
- @s = start_scheduler
223
+ it 'accepts a duration and schedules an EveryJob' do
224
+
225
+ j = @scheduler.repeat '1s' do; end
226
+
227
+ expect(j.class).to eq(Rufus::Scheduler::EveryJob)
72
228
  end
73
- after(:each) do
74
- stop_scheduler(@s)
229
+
230
+ it 'accepts a cron string and schedules a CronJob' do
231
+
232
+ j = @scheduler.repeat '* * * * *' do; end
233
+
234
+ expect(j.class).to eq(Rufus::Scheduler::CronJob)
75
235
  end
236
+ end
237
+
238
+ describe '#unschedule(job_or_work_id)' do
76
239
 
77
- describe '#pause' do
240
+ it 'accepts job ids' do
241
+
242
+ job = @scheduler.schedule_in '10d' do; end
243
+
244
+ expect(job.unscheduled_at).to eq(nil)
245
+
246
+ @scheduler.unschedule(job.id)
247
+
248
+ expect(job.unscheduled_at).not_to eq(nil)
249
+ end
250
+
251
+ it 'accepts jobs' do
252
+
253
+ job = @scheduler.schedule_in '10d' do; end
254
+
255
+ expect(job.unscheduled_at).to eq(nil)
256
+
257
+ @scheduler.unschedule(job)
258
+
259
+ expect(job.unscheduled_at).not_to eq(nil)
260
+ end
78
261
 
79
- it 'pauses a job (every)' do
262
+ it 'carefully unschedules repeat jobs' do
80
263
 
81
- $count = 0
264
+ counter = 0
82
265
 
83
- j = @s.every '1s' do
84
- $count = $count + 1
266
+ job =
267
+ @scheduler.schedule_every '0.5s' do
268
+ counter = counter + 1
85
269
  end
86
270
 
87
- @s.pause(j.job_id)
271
+ sleep 1.5
272
+ c = counter
88
273
 
89
- sleep 2.5
274
+ @scheduler.unschedule(job)
90
275
 
91
- j.paused?.should == true
92
- $count.should == 0
276
+ sleep 1.5
277
+ expect(counter).to eq(c)
278
+ end
279
+ end
280
+
281
+ describe '#uptime' do
282
+
283
+ it 'returns the uptime as a float' do
284
+
285
+ expect(@scheduler.uptime).to be >= 0.0
286
+ end
287
+ end
288
+
289
+ describe '#uptime_s' do
290
+
291
+ it 'returns the uptime as a human readable string' do
292
+
293
+ sleep 1
294
+
295
+ expect(@scheduler.uptime_s).to match(/^[12]s\d+$/)
296
+ end
297
+ end
298
+
299
+ describe '#join' do
300
+
301
+ it 'joins the scheduler thread' do
302
+
303
+ t = Thread.new { @scheduler.join; Thread.current['a'] = 'over' }
304
+
305
+ expect(t['a']).to eq(nil)
306
+
307
+ @scheduler.shutdown
308
+
309
+ sleep(1)
310
+
311
+ expect(t['a']).to eq('over')
312
+ end
313
+ end
314
+
315
+ describe '#job(job_id)' do
316
+
317
+ it 'returns nil if there is no corresponding Job instance' do
318
+
319
+ expect(@scheduler.job('nada')).to eq(nil)
320
+ end
321
+
322
+ it 'returns the corresponding Job instance' do
323
+
324
+ job_id = @scheduler.in '10d' do; end
325
+
326
+ sleep(1) # give it some time to get scheduled
327
+
328
+ expect(@scheduler.job(job_id).job_id).to eq(job_id)
329
+ end
330
+ end
331
+
332
+ # describe '#find_by_tag(t)' do
333
+ #
334
+ # it 'returns an empty list when there are no jobs with the given tag' do
335
+ #
336
+ # @scheduler.find_by_tag('nada').should == []
337
+ # end
338
+ #
339
+ # it 'returns all the jobs with the given tag' do
340
+ #
341
+ # @scheduler.in '10d', :tag => 't0' do; end
342
+ # @scheduler.every '2h', :tag => %w[ t0 t1 ] do; end
343
+ # @scheduler.every '3h' do; end
344
+ #
345
+ # @scheduler.find_by_tag('t0').map(&:original).should ==
346
+ # %w[ 2h 10d ]
347
+ # @scheduler.find_by_tag('t1').map(&:original).should ==
348
+ # %w[ 2h ]
349
+ # @scheduler.find_by_tag('t1', 't0').map(&:original).sort.should ==
350
+ # %w[ 2h ]
351
+ # end
352
+ # end
353
+
354
+ describe '#threads' do
355
+
356
+ it 'just lists the main thread (scheduler thread) when no job is scheduled' do
357
+
358
+ expect(@scheduler.threads).to eq([ @scheduler.thread ])
359
+ end
360
+
361
+ it 'lists all the threads a scheduler uses' do
362
+
363
+ @scheduler.in '0s' do
364
+ sleep(2)
93
365
  end
94
366
 
95
- it 'pauses a job (cron)' do
367
+ sleep 0.4
96
368
 
97
- $count = 0
369
+ expect(@scheduler.threads.size).to eq(2)
370
+ end
371
+ end
98
372
 
99
- j = @s.cron '* * * * * *' do
100
- $count = $count + 1
101
- end
373
+ describe '#work_threads(:all | :vacant)' do
102
374
 
103
- @s.pause(j.job_id)
375
+ it 'returns an empty array when the scheduler has not yet done anything' do
104
376
 
105
- sleep 2.5
377
+ expect(@scheduler.work_threads).to eq([])
378
+ expect(@scheduler.work_threads(:all)).to eq([])
379
+ expect(@scheduler.work_threads(:vacant)).to eq([])
380
+ end
381
+
382
+ it 'lists the [vacant] work threads in the pool' do
383
+
384
+ @scheduler.in '0s' do
385
+ sleep(0.2)
386
+ end
387
+ @scheduler.in '0s' do
388
+ sleep(2.0)
389
+ end
390
+
391
+ sleep 0.7
106
392
 
107
- j.paused?.should == true
108
- $count.should == 0
393
+ if @scheduler.work_threads.size == 1
394
+ expect(@scheduler.work_threads.size).to eq(1)
395
+ expect(@scheduler.work_threads(:all).size).to eq(1)
396
+ expect(@scheduler.work_threads(:vacant).size).to eq(0)
397
+ else
398
+ expect(@scheduler.work_threads.size).to eq(2)
399
+ expect(@scheduler.work_threads(:all).size).to eq(2)
400
+ expect(@scheduler.work_threads(:vacant).size).to eq(1)
109
401
  end
110
402
  end
403
+ end
404
+
405
+ describe '#work_threads(:active)' do
111
406
 
112
- describe '#resume' do
407
+ it 'returns [] when there are no jobs running' do
113
408
 
114
- it 'resumes a job (every)' do
409
+ expect(@scheduler.work_threads(:active)).to eq([])
410
+ end
115
411
 
116
- $count = 0
412
+ it 'returns the list of threads of the running jobs' do
117
413
 
118
- j = @s.every '1s' do
119
- $count = $count + 1
414
+ job =
415
+ @scheduler.schedule_in('0s') do
416
+ sleep 1
120
417
  end
121
418
 
122
- @s.pause(j.job_id)
419
+ sleep 0.4
123
420
 
124
- sleep 2.5
421
+ expect(@scheduler.work_threads(:active).size).to eq(1)
125
422
 
126
- c = $count
423
+ t = @scheduler.work_threads(:active).first
127
424
 
128
- @s.resume(j.job_id)
425
+ expect(t.class).to eq(Thread)
426
+ expect(t[@scheduler.thread_key]).to eq(true)
427
+ expect(t[:rufus_scheduler_job]).to eq(job)
428
+ expect(t[:rufus_scheduler_time]).not_to eq(nil)
429
+ end
129
430
 
130
- sleep 1.5
431
+ it 'does not return threads from other schedulers' do
131
432
 
132
- j.paused?.should == false
133
- ($count > c).should == true
134
- end
433
+ scheduler = Rufus::Scheduler.new
434
+
435
+ job =
436
+ @scheduler.schedule_in('0s') do
437
+ sleep(1)
438
+ end
439
+
440
+ sleep 0.4
441
+
442
+ expect(scheduler.work_threads(:active)).to eq([])
443
+
444
+ scheduler.shutdown
445
+ end
446
+ end
447
+
448
+ #describe '#min_work_threads' do
449
+ # it 'returns the min job thread count' do
450
+ # @scheduler.min_work_threads.should == 3
451
+ # end
452
+ #end
453
+ #describe '#min_work_threads=' do
454
+ # it 'sets the min job thread count' do
455
+ # @scheduler.min_work_threads = 1
456
+ # @scheduler.min_work_threads.should == 1
457
+ # end
458
+ #end
459
+
460
+ describe '#max_work_threads' do
461
+
462
+ it 'returns the max job thread count' do
463
+
464
+ expect(@scheduler.max_work_threads).to eq(28)
465
+ end
466
+ end
135
467
 
136
- it 'pauses a job (cron)' do
468
+ describe '#max_work_threads=' do
137
469
 
138
- $count = 0
470
+ it 'sets the max job thread count' do
139
471
 
140
- j = @s.cron '* * * * * *' do
141
- $count = $count + 1
472
+ @scheduler.max_work_threads = 14
473
+
474
+ expect(@scheduler.max_work_threads).to eq(14)
475
+ end
476
+ end
477
+
478
+ #describe '#kill_all_work_threads' do
479
+ #
480
+ # it 'kills all the work threads' do
481
+ #
482
+ # @scheduler.in '0s' do; sleep(5); end
483
+ # @scheduler.in '0s' do; sleep(5); end
484
+ # @scheduler.in '0s' do; sleep(5); end
485
+ #
486
+ # sleep 0.5
487
+ #
488
+ # @scheduler.work_threads.size.should == 3
489
+ #
490
+ # @scheduler.send(:kill_all_work_threads)
491
+ #
492
+ # sleep 0.5
493
+ #
494
+ # @scheduler.work_threads.size.should == 0
495
+ # end
496
+ #end
497
+
498
+ describe '#running_jobs' do
499
+
500
+ it 'returns [] when there are no running jobs' do
501
+
502
+ expect(@scheduler.running_jobs).to eq([])
503
+ end
504
+
505
+ it 'returns a list of running Job instances' do
506
+
507
+ job =
508
+ @scheduler.schedule_in('0s') do
509
+ sleep(1)
142
510
  end
143
511
 
144
- @s.pause(j.job_id)
512
+ sleep 0.4
513
+
514
+ expect(job.running?).to eq(true)
515
+ expect(@scheduler.running_jobs).to eq([ job ])
516
+ end
145
517
 
146
- sleep 2.5
518
+ it 'does not return twice the same job' do
147
519
 
148
- c = $count
520
+ job =
521
+ @scheduler.schedule_every('0.3s') do
522
+ sleep(5)
523
+ end
149
524
 
150
- @s.resume(j.job_id)
525
+ sleep 1.5
151
526
 
152
- sleep 1.5
527
+ expect(job.running?).to eq(true)
528
+ expect(@scheduler.running_jobs).to eq([ job ])
529
+ end
530
+ end
153
531
 
154
- j.paused?.should == false
155
- ($count > c).should == true
532
+ describe '#running_jobs(:tag/:tags => x)' do
533
+
534
+ it 'returns a list of running jobs filtered by tag' do
535
+
536
+ @scheduler.in '0.1s', :tag => 't0' do
537
+ sleep 3
538
+ end
539
+ @scheduler.in '0.2s', :tag => 't1' do
540
+ sleep 3
156
541
  end
542
+
543
+ sleep 0.49
544
+
545
+ expect(@scheduler.running_jobs(:tag => 't0').map(&:original)).to eq(
546
+ %w[ 0.1s ]
547
+ )
548
+ expect(@scheduler.running_jobs(:tag => 't1').map(&:original)).to eq(
549
+ %w[ 0.2s ]
550
+ )
551
+ expect(@scheduler.running_jobs(:tags => %w[ t0 t1 ]).map(&:original)).to eq(
552
+ []
553
+ )
157
554
  end
158
555
  end
159
556
 
160
- context 'trigger threads' do
557
+ describe '#occurrences(time0, time1)' do
161
558
 
162
- before(:each) do
163
- @s = start_scheduler
559
+ it 'returns a { job => [ times ] } of job occurrences' do
560
+
561
+ j0 = @scheduler.schedule_in '7m' do; end
562
+ j1 = @scheduler.schedule_at '10m' do; end
563
+ j2 = @scheduler.schedule_every '5m' do; end
564
+ j3 = @scheduler.schedule_interval '5m' do; end
565
+ j4 = @scheduler.schedule_cron '* * * * *' do; end
566
+
567
+ h = @scheduler.occurrences(Time.now + 4 * 60, Time.now + 11 * 60)
568
+
569
+ expect(h.size).to eq(5)
570
+ expect(h[j0]).to eq([ j0.next_time ])
571
+ expect(h[j1]).to eq([ j1.next_time ])
572
+ expect(h[j2].size).to eq(2)
573
+ expect(h[j3].size).to eq(2)
574
+ expect(h[j4].size).to eq(7)
575
+ end
576
+
577
+ it 'returns a [ [ time, job ], ... ] of job occurrences when :timeline' do
578
+
579
+ j0 = @scheduler.schedule_in '5m' do; end
580
+ j1 = @scheduler.schedule_in '10m' do; end
581
+
582
+ a =
583
+ @scheduler.occurrences(Time.now + 4 * 60, Time.now + 11 * 60, :timeline)
584
+
585
+ expect(a[0][0]).to be_within_1s_of(Time.now + 5 * 60)
586
+ expect(a[0][1]).to eq(j0)
587
+ expect(a[1][0]).to be_within_1s_of(Time.now + 10 * 60)
588
+ expect(a[1][1]).to eq(j1)
164
589
  end
165
- after(:each) do
166
- stop_scheduler(@s)
590
+
591
+ it 'respects :first_at for repeat jobs' do
592
+
593
+ j0 = @scheduler.schedule_every '5m', :first_in => '10m' do; end
594
+
595
+ h = @scheduler.occurrences(Time.now + 4 * 60, Time.now + 16 * 60)
596
+
597
+ expect(h[j0][0]).to be_within_1s_of(Time.now + 10 * 60)
598
+ expect(h[j0][1]).to be_within_1s_of(Time.now + 15 * 60)
167
599
  end
168
600
 
169
- describe '#trigger_threads' do
601
+ it 'respects :times for repeat jobs' do
170
602
 
171
- it 'returns an empty list when no jobs are running' do
603
+ j0 = @scheduler.schedule_every '1m', :times => 10 do; end
172
604
 
173
- @s.trigger_threads.should == []
174
- end
605
+ t0 = Time.parse((Time.now + 5 * 60).strftime('%Y-%m-%d %H:%M:01'))
606
+ t1 = t0 + 12 * 60 - 1
607
+
608
+ h = @scheduler.occurrences(t0, t1)
609
+
610
+ expect(h[j0].size).to eq(6)
611
+ end
612
+ end
613
+
614
+ describe '#timeline(time0, time1)' do
615
+
616
+ it 'returns a [ [ time, job ], ... ] of job occurrences' do
617
+
618
+ j0 = @scheduler.schedule_in '5m' do; end
619
+ j1 = @scheduler.schedule_in '10m' do; end
620
+
621
+ a = @scheduler.timeline(Time.now + 4 * 60, Time.now + 11 * 60)
622
+
623
+ expect(a[0][0]).to be_within_1s_of(Time.now + 5 * 60)
624
+ expect(a[0][1]).to eq(j0)
625
+ expect(a[1][0]).to be_within_1s_of(Time.now + 10 * 60)
626
+ expect(a[1][1]).to eq(j1)
627
+ end
628
+ end
629
+
630
+ #--
631
+ # management methods
632
+ #++
633
+
634
+ describe '#shutdown' do
635
+
636
+ it 'blanks the uptime' do
637
+
638
+ @scheduler.shutdown
639
+
640
+ expect(@scheduler.uptime).to eq(nil)
641
+ end
175
642
 
176
- it 'returns a list of the threads of the running jobs' do
643
+ it 'shuts the scheduler down' do
177
644
 
178
- @s.in('100') { sleep 10 }
645
+ @scheduler.shutdown
179
646
 
180
- sleep 0.5
647
+ sleep 0.100
648
+ sleep 0.400 if RUBY_VERSION < '1.9.0'
181
649
 
182
- @s.trigger_threads.collect { |e| e.class }.should == [ Thread ]
650
+ t = Thread.list.find { |t|
651
+ t[:name] == "rufus_scheduler_#{@scheduler.object_id}"
652
+ }
653
+
654
+ expect(t).to eq(nil)
655
+ end
656
+
657
+ it 'has a #stop alias' do
658
+
659
+ @scheduler.stop
660
+
661
+ expect(@scheduler.uptime).to eq(nil)
662
+ end
663
+
664
+ #it 'has a #close alias'
665
+ end
666
+
667
+ describe '#shutdown(:wait)' do
668
+
669
+ it 'shuts down and blocks until all the jobs ended their current runs' do
670
+
671
+ counter = 0
672
+
673
+ @scheduler.in '0s' do
674
+ sleep 1
675
+ counter = counter + 1
183
676
  end
677
+
678
+ sleep 0.4
679
+
680
+ @scheduler.shutdown(:wait)
681
+
682
+ expect(counter).to eq(1)
683
+ expect(@scheduler.uptime).to eq(nil)
684
+ expect(@scheduler.running_jobs).to eq([])
685
+ expect(@scheduler.threads).to eq([])
184
686
  end
687
+ end
688
+
689
+ describe '#shutdown(:kill)' do
185
690
 
186
- describe '#running_jobs' do
691
+ it 'kills all the jobs and then shuts down' do
187
692
 
188
- it 'returns an empty list when no jobs are running' do
693
+ counter = 0
189
694
 
190
- @s.running_jobs.should == []
695
+ @scheduler.in '0s' do
696
+ sleep 1
697
+ counter = counter + 1
698
+ end
699
+ @scheduler.at Time.now + 0.3 do
700
+ sleep 1
701
+ counter = counter + 1
191
702
  end
192
703
 
193
- it 'returns a list of the currently running jobs' do
704
+ sleep 0.4
705
+
706
+ @scheduler.shutdown(:kill)
707
+
708
+ sleep 1.4
709
+
710
+ expect(counter).to eq(0)
711
+ expect(@scheduler.uptime).to eq(nil)
712
+ expect(@scheduler.running_jobs).to eq([])
713
+ expect(@scheduler.threads).to eq([])
714
+ end
715
+ end
716
+
717
+ describe '#pause' do
718
+
719
+ it 'pauses the scheduler' do
720
+
721
+ job = @scheduler.schedule_in '1s' do; end
722
+
723
+ @scheduler.pause
724
+
725
+ sleep(3)
726
+
727
+ expect(job.last_time).to eq(nil)
728
+ end
729
+ end
730
+
731
+ describe '#resume' do
732
+
733
+ it 'works' do
734
+
735
+ job = @scheduler.schedule_in '2s' do; end
736
+
737
+ @scheduler.pause
738
+ sleep(1)
739
+ @scheduler.resume
740
+ sleep(2)
741
+
742
+ expect(job.last_time).not_to eq(nil)
743
+ end
744
+ end
745
+
746
+ describe '#paused?' do
747
+
748
+ it 'returns true if the scheduler is paused' do
749
+
750
+ @scheduler.pause
751
+ expect(@scheduler.paused?).to eq(true)
752
+ end
753
+
754
+ it 'returns false if the scheduler is not paused' do
755
+
756
+ expect(@scheduler.paused?).to eq(false)
757
+
758
+ @scheduler.pause
759
+ @scheduler.resume
760
+
761
+ expect(@scheduler.paused?).to eq(false)
762
+ end
763
+ end
764
+
765
+ describe '#down?' do
766
+
767
+ it 'returns true when the scheduler is down' do
768
+
769
+ @scheduler.shutdown
770
+
771
+ expect(@scheduler.down?).to eq(true)
772
+ end
773
+
774
+ it 'returns false when the scheduler is up' do
775
+
776
+ expect(@scheduler.down?).to eq(false)
777
+ end
778
+ end
779
+
780
+ describe '#up?' do
781
+
782
+ it 'returns true when the scheduler is up' do
783
+
784
+ expect(@scheduler.up?).to eq(true)
785
+ end
786
+ end
787
+
788
+ #--
789
+ # job methods
790
+ #++
791
+
792
+ describe '#jobs' do
793
+
794
+ it 'is empty at the beginning' do
795
+
796
+ expect(@scheduler.jobs).to eq([])
797
+ end
798
+
799
+ it 'returns the list of scheduled jobs' do
800
+
801
+ @scheduler.in '10d' do; end
802
+ @scheduler.in '1w' do; end
803
+
804
+ sleep(1)
194
805
 
195
- job = @s.in('100') { sleep 10 }
806
+ jobs = @scheduler.jobs
196
807
 
197
- sleep 0.5
808
+ expect(jobs.collect { |j| j.original }.sort).to eq(%w[ 10d 1w ])
809
+ end
810
+
811
+ it 'returns all the jobs (even those pending reschedule)' do
198
812
 
199
- @s.running_jobs.should == [ job ]
813
+ @scheduler.in '0s', :blocking => true do
814
+ sleep 2
200
815
  end
816
+
817
+ sleep 0.4
818
+
819
+ expect(@scheduler.jobs.size).to eq(1)
820
+ end
821
+
822
+ it 'does not return unscheduled jobs' do
823
+
824
+ job =
825
+ @scheduler.schedule_in '0s', :blocking => true do
826
+ sleep 2
827
+ end
828
+
829
+ sleep 0.4
830
+
831
+ job.unschedule
832
+
833
+ expect(@scheduler.jobs.size).to eq(0)
834
+ end
835
+ end
836
+
837
+ describe '#jobs(:tag / :tags => x)' do
838
+
839
+ it 'returns [] when there are no jobs with the corresponding tag' do
840
+
841
+ expect(@scheduler.jobs(:tag => 'nada')).to eq([])
842
+ expect(@scheduler.jobs(:tags => %w[ nada hello ])).to eq([])
843
+ end
844
+
845
+ it 'returns the jobs with the corresponding tag' do
846
+
847
+ @scheduler.in '10d', :tag => 't0' do; end
848
+ @scheduler.every '2h', :tag => %w[ t0 t1 ] do; end
849
+ @scheduler.every '3h' do; end
850
+
851
+ expect(@scheduler.jobs(:tags => 't0').map(&:original).sort).to eq(
852
+ %w[ 10d 2h ]
853
+ )
854
+ expect(@scheduler.jobs(:tags => 't1').map(&:original).sort).to eq(
855
+ %w[ 2h ]
856
+ )
857
+ expect(@scheduler.jobs(:tags => [ 't1', 't0' ]).map(&:original).sort).to eq(
858
+ %w[ 2h ]
859
+ )
860
+ end
861
+ end
862
+
863
+ describe '#every_jobs' do
864
+
865
+ it 'returns EveryJob instances' do
866
+
867
+ @scheduler.at '2030/12/12 12:10:00' do; end
868
+ @scheduler.in '10d' do; end
869
+ @scheduler.every '5m' do; end
870
+
871
+ jobs = @scheduler.every_jobs
872
+
873
+ expect(jobs.collect { |j| j.original }.sort).to eq(%w[ 5m ])
874
+ end
875
+ end
876
+
877
+ describe '#at_jobs' do
878
+
879
+ it 'returns AtJob instances' do
880
+
881
+ @scheduler.at '2030/12/12 12:10:00' do; end
882
+ @scheduler.in '10d' do; end
883
+ @scheduler.every '5m' do; end
884
+
885
+ jobs = @scheduler.at_jobs
886
+
887
+ expect(jobs.collect { |j| j.original }.sort).to eq([ '2030/12/12 12:10:00' ])
888
+ end
889
+ end
890
+
891
+ describe '#in_jobs' do
892
+
893
+ it 'returns InJob instances' do
894
+
895
+ @scheduler.at '2030/12/12 12:10:00' do; end
896
+ @scheduler.in '10d' do; end
897
+ @scheduler.every '5m' do; end
898
+
899
+ jobs = @scheduler.in_jobs
900
+
901
+ expect(jobs.collect { |j| j.original }.sort).to eq(%w[ 10d ])
902
+ end
903
+ end
904
+
905
+ describe '#cron_jobs' do
906
+
907
+ it 'returns CronJob instances' do
908
+
909
+ @scheduler.at '2030/12/12 12:10:00' do; end
910
+ @scheduler.in '10d' do; end
911
+ @scheduler.every '5m' do; end
912
+ @scheduler.cron '* * * * *' do; end
913
+
914
+ jobs = @scheduler.cron_jobs
915
+
916
+ expect(jobs.collect { |j| j.original }.sort).to eq([ '* * * * *' ])
201
917
  end
202
918
  end
203
919
 
204
- context 'termination' do
920
+ describe '#interval_jobs' do
205
921
 
206
- describe '#stop(true)' do
922
+ it 'returns IntervalJob instances' do
207
923
 
208
- it 'terminates the scheduler, blocking until all the jobs are unscheduled' do
924
+ @scheduler.at '2030/12/12 12:10:00' do; end
925
+ @scheduler.in '10d' do; end
926
+ @scheduler.every '5m' do; end
927
+ @scheduler.cron '* * * * *' do; end
928
+ @scheduler.interval '7m' do; end
209
929
 
210
- $every = nil
211
- $cron = nil
930
+ jobs = @scheduler.interval_jobs
212
931
 
213
- s = start_scheduler
214
- s.every '1s' do
215
- $every = :in
216
- sleep 0.5
217
- $every = :out
932
+ expect(jobs.collect { |j| j.original }.sort).to eq(%w[ 7m ])
933
+ end
934
+ end
935
+
936
+ #--
937
+ # callbacks
938
+ #++
939
+
940
+ describe '#on_pre_trigger' do
941
+
942
+ it 'is called right before a job triggers' do
943
+
944
+ $out = []
945
+
946
+ def @scheduler.on_pre_trigger(job)
947
+ $out << "pre #{job.id}"
948
+ end
949
+
950
+ job_id =
951
+ @scheduler.in '0.5s' do |job|
952
+ $out << job.id
218
953
  end
219
- s.cron '* * * * * *' do
220
- $cron = :in
221
- sleep 0.5
222
- $cron = :out
954
+
955
+ sleep 0.7
956
+
957
+ expect($out).to eq([ "pre #{job_id}", job_id ])
958
+ end
959
+
960
+ it 'accepts the job and the triggerTime as argument' do
961
+
962
+ $tt = nil
963
+
964
+ def @scheduler.on_pre_trigger(job, trigger_time)
965
+ $tt = trigger_time
966
+ end
967
+
968
+ start = Time.now
969
+
970
+ @scheduler.in '0.5s' do; end
971
+
972
+ sleep 0.7
973
+
974
+ expect($tt.class).to eq(Time)
975
+ expect($tt).to be > start
976
+ expect($tt).to be < Time.now
977
+ end
978
+
979
+ context 'when it returns false' do
980
+
981
+ it 'prevents the job from triggering' do
982
+
983
+ $out = []
984
+
985
+ def @scheduler.on_pre_trigger(job)
986
+ $out << "pre #{job.id}"
987
+ false
223
988
  end
224
989
 
225
- sleep 2
990
+ job_id =
991
+ @scheduler.in '0.5s' do |job|
992
+ $out << job.id
993
+ end
226
994
 
227
- s.stop(:terminate => true)
995
+ sleep 0.7
228
996
 
229
- s.jobs.size.should == 0
230
- $every.should == :out
231
- $cron.should == :out
997
+ expect($out).to eq([ "pre #{job_id}" ])
232
998
  end
233
999
  end
234
1000
  end
235
- end
236
1001
 
237
- describe 'Rufus::Scheduler#start_new' do
1002
+ describe '#on_post_trigger' do
1003
+
1004
+ it 'is called right after a job triggers' do
1005
+
1006
+ $out = []
1007
+
1008
+ def @scheduler.on_post_trigger(job)
1009
+ $out << "post #{job.id}"
1010
+ end
1011
+
1012
+ job_id =
1013
+ @scheduler.in '0.5s' do |job|
1014
+ $out << job.id
1015
+ end
238
1016
 
239
- it 'piggybacks EM if present and running' do
1017
+ sleep 0.7
1018
+
1019
+ expect($out).to eq([ job_id, "post #{job_id}" ])
1020
+ end
1021
+ end
240
1022
 
241
- s = Rufus::Scheduler.start_new
1023
+ #--
1024
+ # misc
1025
+ #++
242
1026
 
243
- s.class.should == SCHEDULER_CLASS
1027
+ describe '.singleton / .s' do
244
1028
 
245
- stop_scheduler(s)
1029
+ before(:each) do
1030
+
1031
+ Rufus::Scheduler.class_eval { @singleton = nil } # ;-)
1032
+ end
1033
+
1034
+ it 'returns a singleton instance of the scheduler' do
1035
+
1036
+ s0 = Rufus::Scheduler.singleton
1037
+ s1 = Rufus::Scheduler.s
1038
+
1039
+ expect(s0.class).to eq(Rufus::Scheduler)
1040
+ expect(s1.object_id).to eq(s0.object_id)
1041
+ end
1042
+
1043
+ it 'accepts initialization parameters' do
1044
+
1045
+ s = Rufus::Scheduler.singleton(:max_work_threads => 77)
1046
+ s = Rufus::Scheduler.singleton(:max_work_threads => 42)
1047
+
1048
+ expect(s.max_work_threads).to eq(77)
1049
+ end
246
1050
  end
247
1051
  end
248
1052