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