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.
- data/CHANGELOG.txt +76 -0
- data/CREDITS.txt +23 -0
- data/LICENSE.txt +1 -1
- data/README.md +1439 -0
- data/Rakefile +1 -5
- data/TODO.txt +149 -55
- data/lib/rufus/{sc → scheduler}/cronline.rb +167 -53
- data/lib/rufus/scheduler/job_array.rb +92 -0
- data/lib/rufus/scheduler/jobs.rb +633 -0
- data/lib/rufus/scheduler/locks.rb +95 -0
- data/lib/rufus/scheduler/util.rb +306 -0
- data/lib/rufus/scheduler/zones.rb +174 -0
- data/lib/rufus/scheduler/zotime.rb +154 -0
- data/lib/rufus/scheduler.rb +608 -27
- data/rufus-scheduler.gemspec +6 -4
- data/spec/basics_spec.rb +54 -0
- data/spec/cronline_spec.rb +479 -152
- data/spec/error_spec.rb +139 -0
- data/spec/job_array_spec.rb +39 -0
- data/spec/job_at_spec.rb +58 -0
- data/spec/job_cron_spec.rb +128 -0
- data/spec/job_every_spec.rb +104 -0
- data/spec/job_in_spec.rb +20 -0
- data/spec/job_interval_spec.rb +68 -0
- data/spec/job_repeat_spec.rb +357 -0
- data/spec/job_spec.rb +498 -109
- data/spec/lock_custom_spec.rb +47 -0
- data/spec/lock_flock_spec.rb +47 -0
- data/spec/lock_lockfile_spec.rb +61 -0
- data/spec/lock_spec.rb +59 -0
- data/spec/parse_spec.rb +263 -0
- data/spec/schedule_at_spec.rb +158 -0
- data/spec/schedule_cron_spec.rb +66 -0
- data/spec/schedule_every_spec.rb +109 -0
- data/spec/schedule_in_spec.rb +80 -0
- data/spec/schedule_interval_spec.rb +128 -0
- data/spec/scheduler_spec.rb +928 -124
- data/spec/spec_helper.rb +126 -0
- data/spec/threads_spec.rb +96 -0
- data/spec/zotime_spec.rb +396 -0
- metadata +56 -33
- data/README.rdoc +0 -661
- data/lib/rufus/otime.rb +0 -3
- data/lib/rufus/sc/jobqueues.rb +0 -160
- data/lib/rufus/sc/jobs.rb +0 -471
- data/lib/rufus/sc/rtime.rb +0 -363
- data/lib/rufus/sc/scheduler.rb +0 -636
- data/lib/rufus/sc/version.rb +0 -32
- data/spec/at_in_spec.rb +0 -47
- data/spec/at_spec.rb +0 -125
- data/spec/blocking_spec.rb +0 -64
- data/spec/cron_spec.rb +0 -134
- data/spec/every_spec.rb +0 -304
- data/spec/exception_spec.rb +0 -113
- data/spec/in_spec.rb +0 -150
- data/spec/mutex_spec.rb +0 -159
- data/spec/rtime_spec.rb +0 -137
- data/spec/schedulable_spec.rb +0 -97
- data/spec/spec_base.rb +0 -87
- data/spec/stress_schedule_unschedule_spec.rb +0 -159
- data/spec/timeout_spec.rb +0 -148
- data/test/kjw.rb +0 -113
- data/test/t.rb +0 -20
data/spec/scheduler_spec.rb
CHANGED
@@ -2,247 +2,1051 @@
|
|
2
2
|
#
|
3
3
|
# Specifying rufus-scheduler
|
4
4
|
#
|
5
|
-
#
|
5
|
+
# Wed Apr 17 06:00:59 JST 2013
|
6
6
|
#
|
7
7
|
|
8
|
-
require '
|
8
|
+
require 'spec_helper'
|
9
9
|
|
10
10
|
|
11
|
-
describe
|
11
|
+
describe Rufus::Scheduler do
|
12
12
|
|
13
|
-
|
13
|
+
describe '#initialize' do
|
14
14
|
|
15
|
-
|
15
|
+
it 'starts the scheduler thread' do
|
16
16
|
|
17
|
-
|
18
|
-
s.in('3s') { var = true }
|
17
|
+
scheduler = Rufus::Scheduler.new
|
19
18
|
|
20
|
-
|
19
|
+
t = Thread.list.find { |t|
|
20
|
+
t[:name] == "rufus_scheduler_#{scheduler.object_id}_scheduler"
|
21
|
+
}
|
21
22
|
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
-
|
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
|
-
|
174
|
+
seen = false
|
30
175
|
|
31
|
-
|
176
|
+
t = Time.now + 1
|
32
177
|
|
33
|
-
|
34
|
-
/Rufus::Scheduler::.*Scheduler - \d+\.\d+\.\d+/)
|
178
|
+
@scheduler.in(t) { seen = true }
|
35
179
|
|
36
|
-
|
180
|
+
sleep 0.1 while seen != true
|
37
181
|
end
|
38
182
|
|
39
|
-
it '
|
183
|
+
it 'accepts point in time and duration indifferently (#at)' do
|
40
184
|
|
41
|
-
|
42
|
-
s.instance_variable_get(:@thread)['name'].should == 'nada'
|
185
|
+
seen = false
|
43
186
|
|
44
|
-
|
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
|
-
|
195
|
+
describe '#schedule' do
|
49
196
|
|
50
|
-
|
197
|
+
it 'accepts a duration and schedules an InJob' do
|
51
198
|
|
52
|
-
|
199
|
+
j = @scheduler.schedule '1s' do; end
|
53
200
|
|
54
|
-
|
201
|
+
expect(j.class).to eq(Rufus::Scheduler::InJob)
|
202
|
+
expect(j.original).to eq('1s')
|
203
|
+
end
|
55
204
|
|
56
|
-
|
57
|
-
var.should == nil
|
205
|
+
it 'accepts a point in time and schedules an AtJob' do
|
58
206
|
|
59
|
-
|
60
|
-
var.should == nil
|
207
|
+
j = @scheduler.schedule '2070/12/24 23:00' do; end
|
61
208
|
|
62
|
-
|
63
|
-
|
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
|
-
|
217
|
+
expect(j.class).to eq(Rufus::Scheduler::CronJob)
|
218
|
+
end
|
66
219
|
end
|
67
220
|
|
68
|
-
|
221
|
+
describe '#repeat' do
|
69
222
|
|
70
|
-
|
71
|
-
|
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
|
-
|
74
|
-
|
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
|
-
|
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
|
-
|
262
|
+
it 'carefully unschedules repeat jobs' do
|
80
263
|
|
81
|
-
|
264
|
+
counter = 0
|
82
265
|
|
83
|
-
|
84
|
-
|
266
|
+
job =
|
267
|
+
@scheduler.schedule_every '0.5s' do
|
268
|
+
counter = counter + 1
|
85
269
|
end
|
86
270
|
|
87
|
-
|
271
|
+
sleep 1.5
|
272
|
+
c = counter
|
88
273
|
|
89
|
-
|
274
|
+
@scheduler.unschedule(job)
|
90
275
|
|
91
|
-
|
92
|
-
|
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
|
-
|
367
|
+
sleep 0.4
|
96
368
|
|
97
|
-
|
369
|
+
expect(@scheduler.threads.size).to eq(2)
|
370
|
+
end
|
371
|
+
end
|
98
372
|
|
99
|
-
|
100
|
-
$count = $count + 1
|
101
|
-
end
|
373
|
+
describe '#work_threads(:all | :vacant)' do
|
102
374
|
|
103
|
-
|
375
|
+
it 'returns an empty array when the scheduler has not yet done anything' do
|
104
376
|
|
105
|
-
|
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
|
-
|
108
|
-
|
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
|
-
|
407
|
+
it 'returns [] when there are no jobs running' do
|
113
408
|
|
114
|
-
|
409
|
+
expect(@scheduler.work_threads(:active)).to eq([])
|
410
|
+
end
|
115
411
|
|
116
|
-
|
412
|
+
it 'returns the list of threads of the running jobs' do
|
117
413
|
|
118
|
-
|
119
|
-
|
414
|
+
job =
|
415
|
+
@scheduler.schedule_in('0s') do
|
416
|
+
sleep 1
|
120
417
|
end
|
121
418
|
|
122
|
-
|
419
|
+
sleep 0.4
|
123
420
|
|
124
|
-
|
421
|
+
expect(@scheduler.work_threads(:active).size).to eq(1)
|
125
422
|
|
126
|
-
|
423
|
+
t = @scheduler.work_threads(:active).first
|
127
424
|
|
128
|
-
|
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
|
-
|
431
|
+
it 'does not return threads from other schedulers' do
|
131
432
|
|
132
|
-
|
133
|
-
|
134
|
-
|
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
|
-
|
468
|
+
describe '#max_work_threads=' do
|
137
469
|
|
138
|
-
|
470
|
+
it 'sets the max job thread count' do
|
139
471
|
|
140
|
-
|
141
|
-
|
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
|
-
|
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
|
-
|
518
|
+
it 'does not return twice the same job' do
|
147
519
|
|
148
|
-
|
520
|
+
job =
|
521
|
+
@scheduler.schedule_every('0.3s') do
|
522
|
+
sleep(5)
|
523
|
+
end
|
149
524
|
|
150
|
-
|
525
|
+
sleep 1.5
|
151
526
|
|
152
|
-
|
527
|
+
expect(job.running?).to eq(true)
|
528
|
+
expect(@scheduler.running_jobs).to eq([ job ])
|
529
|
+
end
|
530
|
+
end
|
153
531
|
|
154
|
-
|
155
|
-
|
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
|
-
|
557
|
+
describe '#occurrences(time0, time1)' do
|
161
558
|
|
162
|
-
|
163
|
-
|
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
|
-
|
166
|
-
|
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
|
-
|
601
|
+
it 'respects :times for repeat jobs' do
|
170
602
|
|
171
|
-
|
603
|
+
j0 = @scheduler.schedule_every '1m', :times => 10 do; end
|
172
604
|
|
173
|
-
|
174
|
-
|
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
|
-
|
643
|
+
it 'shuts the scheduler down' do
|
177
644
|
|
178
|
-
|
645
|
+
@scheduler.shutdown
|
179
646
|
|
180
|
-
|
647
|
+
sleep 0.100
|
648
|
+
sleep 0.400 if RUBY_VERSION < '1.9.0'
|
181
649
|
|
182
|
-
|
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
|
-
|
691
|
+
it 'kills all the jobs and then shuts down' do
|
187
692
|
|
188
|
-
|
693
|
+
counter = 0
|
189
694
|
|
190
|
-
|
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
|
-
|
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
|
-
|
806
|
+
jobs = @scheduler.jobs
|
196
807
|
|
197
|
-
|
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
|
-
|
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
|
-
|
920
|
+
describe '#interval_jobs' do
|
205
921
|
|
206
|
-
|
922
|
+
it 'returns IntervalJob instances' do
|
207
923
|
|
208
|
-
|
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
|
-
|
211
|
-
$cron = nil
|
930
|
+
jobs = @scheduler.interval_jobs
|
212
931
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
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
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
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
|
-
|
990
|
+
job_id =
|
991
|
+
@scheduler.in '0.5s' do |job|
|
992
|
+
$out << job.id
|
993
|
+
end
|
226
994
|
|
227
|
-
|
995
|
+
sleep 0.7
|
228
996
|
|
229
|
-
|
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 '
|
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
|
-
|
1017
|
+
sleep 0.7
|
1018
|
+
|
1019
|
+
expect($out).to eq([ job_id, "post #{job_id}" ])
|
1020
|
+
end
|
1021
|
+
end
|
240
1022
|
|
241
|
-
|
1023
|
+
#--
|
1024
|
+
# misc
|
1025
|
+
#++
|
242
1026
|
|
243
|
-
|
1027
|
+
describe '.singleton / .s' do
|
244
1028
|
|
245
|
-
|
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
|
|