rufus-scheduler 2.0.24 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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