rufus-scheduler 2.0.19 → 2.0.20

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 CHANGED
@@ -2,6 +2,15 @@
2
2
  = rufus-scheduler CHANGELOG.txt
3
3
 
4
4
 
5
+ == rufus-scheduler - 2.0.20 released 2013/07/15
6
+
7
+ - add Rufus::Scheduler.new (so that rs 3.0 quickstarts are OK with 2.0.20)
8
+ - implement CronLine#previous_time(now=Time.now) (Idea Matteo Cerutti)
9
+ - throw ArgumentError for invalid cron lines (Thanks Aimee Rose)
10
+ - cron 0 vs 24 hour case straightening (Thanks Aimee Rose)
11
+ - support for sun#L or sun#-2 in cron lines
12
+
13
+
5
14
  == rufus-scheduler - 2.0.19 released 2013/05/07
6
15
 
7
16
  - raise ArgumentError on <= 0.0 "every" frequency (Thanks Lucy Fu)
data/CREDITS.txt CHANGED
@@ -4,6 +4,9 @@
4
4
 
5
5
  == Contributors
6
6
 
7
+ - Thomas Sevestre (https://github.com/thomassevestre) :exception option
8
+ - Matteo Cerutti - last_time / previous_time idea (and initial implementation)
9
+ - Aimee Rose (https://github.com/AimeeRose) cronline and > 24
7
10
  - Lucy Fu (https://github.com/lfu) arg error on <= 0 "every" freq
8
11
  - Rainux Luo (https://github.com/rainux) multiple mutexes
9
12
  - Andrew Davey (https://github.com/asdavey) "L" in cron lines
@@ -22,6 +25,7 @@
22
25
 
23
26
  == Feedback
24
27
 
28
+ - Hongli Lai - Scheduler#stop(:terminate => true) request
25
29
  - Tero Tilus - raises on unsupported/unknown options
26
30
  - Louis Coilliot - Scheduler#running_jobs
27
31
  - Henrique G. Testa - pause/resume concept
data/README.rdoc CHANGED
@@ -98,12 +98,18 @@ The timezones are the ones supported by the 'tzinfo' rubygem (http://tzinfo.ruby
98
98
 
99
99
  The timezone support was contributed by Tanzeeb Khalili.
100
100
 
101
- Since 2.0.9, "monthdays" are supported
101
+ "monthdays" are supported
102
102
 
103
103
  scheduler.cron '0 22 * * sun#1,sun#2' do
104
104
  # every first and second sunday of the month, at 22:00
105
105
  end
106
106
 
107
+ It's also OK (since 2.0.19) to use L (for last monthday) or negative numbers.
108
+
109
+ scheduler.cron '0 22 * * sun#-1' do
110
+ # every last sunday of the month, at 22:00
111
+ end
112
+
107
113
 
108
114
  == scheduler.join
109
115
 
@@ -132,6 +138,18 @@ You shouldn't be exposed to this issue when using EventMachine, since while runn
132
138
  DO NOT CALL this #join method if you're running rufus-scheduler from Rails or Sinatra or any application that's already some kind of 'daemon'. It's not necessary! #join is meant for small standalone scripts.
133
139
 
134
140
 
141
+ == schedule.stop
142
+
143
+ scheduler.stop
144
+
145
+ This call stops the scheduler. It doesn't unschedule jobs. If there are running jobs, they're left running.
146
+
147
+ If you need to stop the scheduler and wait for all the jobs currently running to finish (without killing them), rufus-scheduler 2.0.20 brings a new :terminate => true option.
148
+
149
+ scheduler.stop(:terminate => true)
150
+ # returns once all the jobs have been unscheduled and no jobs is running
151
+
152
+
135
153
  == block parameters
136
154
 
137
155
  Scheduled blocks accept 0 or 1 parameter (this unique parameter is the job
@@ -319,7 +337,7 @@ The chronic gem may help (http://chronic.rubyforge.org/) :
319
337
  require 'chronic' # sudo gem install chronic
320
338
 
321
339
  scheduler.every '3h', :first_at => Chronic.parse('this tuesday 5:00') do
322
- # do something starting this tueday
340
+ # do something starting this tuesday
323
341
  end
324
342
 
325
343
  Note : setting a :first_at/:first_in in the past will get rufus-scheduler to trigger for all the past schedules until now. Adding :discard_past => true will prevent this.
@@ -520,6 +538,15 @@ For backward compatibility, overriding #log_exception is still OK :
520
538
 
521
539
  Note that an every job or a cron job will stay scheduled even if it experiences an exception.
522
540
 
541
+ By default, all exceptions are rescued. It's easy to customize that behaviour :
542
+
543
+ scheduler = Rufus::Scheduler::PlainScheduler.start_new(:exception => StandardError)
544
+ # or
545
+ #scheduler = Rufus::Scheduler::EmScheduler.start_new(:exception => StandardError)
546
+
547
+ scheduler.in "3s" do
548
+ exit
549
+ end
523
550
 
524
551
  == frequency
525
552
 
@@ -555,7 +582,7 @@ More and more ruby applications are using EventMachine. This flavour of the sche
555
582
 
556
583
  == with Passenger
557
584
 
558
- "it terminates for no apparent reason !"
585
+ "it terminates for no apparent reason!"
559
586
 
560
587
  https://github.com/jmettraux/rufus-scheduler/issues/issue/10
561
588
 
@@ -576,22 +603,35 @@ The 'tzinfo' rubygem.
576
603
  The ruby gem 'eventmachine' if you use Rufus::Scheduler::EmScheduler, else no other dependencies.
577
604
 
578
605
 
579
- == mailing list
606
+ == support
607
+
608
+ If you identify and pinpoint a bug, please use the issue tracker. If you are unsure whether the fault lies in rufus-scheduler or in your software, use the mailing list. The mailing list is Google-powered, so, yes, you can search it.
609
+
610
+ Please read carefully: http://www.chiark.greenend.org.uk/~sgtatham/bugs.html (then re-read it).
611
+
612
+
613
+ === mailing list
580
614
 
581
615
  On the rufus-ruby list :
582
616
 
583
617
  http://groups.google.com/group/rufus-ruby
584
618
 
619
+ Newcomers' first message is held for moderation in order to prevent spam. Further messages are not held.
620
+
621
+
622
+ === issue tracker
585
623
 
586
- == issue tracker
624
+ https://github.com/jmettraux/rufus-scheduler/issues
587
625
 
588
- http://rubyforge.org/tracker/?atid=18584&group_id=4812&func=browse
589
626
 
627
+ === irc
590
628
 
591
- == irc
629
+ If you come over to #ruote to ask for rufus-scheduler help, please make sure to 1) say hello 2) be polite 3) state that you're looking for rufus-scheduler help 4) remember that we cannot read your mind and guess whatever lies in your deployment.
592
630
 
593
631
  irc.freenode.net #ruote
594
632
 
633
+ If there is no answer on IRC, use the mailing list.
634
+
595
635
 
596
636
  == source
597
637
 
@@ -618,3 +658,4 @@ http://rufus.rubyforge.org
618
658
  == license
619
659
 
620
660
  MIT
661
+
@@ -33,6 +33,9 @@ module Rufus
33
33
  #
34
34
  class CronLine
35
35
 
36
+ DAY_S = 24 * 3600
37
+ WEEK_S = 7 * DAY_S
38
+
36
39
  # The string used for creating this cronline instance.
37
40
  #
38
41
  attr_reader :original
@@ -74,7 +77,7 @@ module Rufus
74
77
 
75
78
  raise ArgumentError.new(
76
79
  "invalid cronline: '#{line}'"
77
- ) if es && es.find { |e| ! e.is_a?(Integer) }
80
+ ) if es && es.find { |e| ! e.is_a?(Fixnum) }
78
81
  end
79
82
  end
80
83
 
@@ -125,25 +128,21 @@ module Rufus
125
128
  time = @timezone ? @timezone.utc_to_local(now.getutc) : now
126
129
 
127
130
  time = time - time.usec * 1e-6 + 1
128
- # little adjustment before starting
131
+ # small adjustment before starting
129
132
 
130
133
  loop do
131
134
 
132
135
  unless date_match?(time)
133
- time += (24 - time.hour) * 3600 - time.min * 60 - time.sec
134
- next
136
+ time += (24 - time.hour) * 3600 - time.min * 60 - time.sec; next
135
137
  end
136
138
  unless sub_match?(time, :hour, @hours)
137
- time += (60 - time.min) * 60 - time.sec
138
- next
139
+ time += (60 - time.min) * 60 - time.sec; next
139
140
  end
140
141
  unless sub_match?(time, :min, @minutes)
141
- time += 60 - time.sec
142
- next
142
+ time += 60 - time.sec; next
143
143
  end
144
144
  unless sub_match?(time, :sec, @seconds)
145
- time += 1
146
- next
145
+ time += 1; next
147
146
  end
148
147
 
149
148
  break
@@ -157,6 +156,28 @@ module Rufus
157
156
  time
158
157
  end
159
158
 
159
+ # Returns the previous the cronline matched. It's like next_time, but
160
+ # for the past.
161
+ #
162
+ def previous_time(now=Time.now)
163
+
164
+ # looks back by slices of two hours,
165
+ #
166
+ # finds for '* * * * sun', '* * 13 * *' and '0 12 13 * *'
167
+ # starting 1970, 1, 1 in 1.8 to 2 seconds (says Rspec)
168
+
169
+ start = current = now - 2 * 3600
170
+ result = nil
171
+
172
+ loop do
173
+ nex = next_time(current)
174
+ return (result ? result : previous_time(start)) if nex > now
175
+ result = current = nex
176
+ end
177
+
178
+ # never reached
179
+ end
180
+
160
181
  # Returns an array of 6 arrays (seconds, minutes, hours, days,
161
182
  # months, weekdays).
162
183
  # This method is used by the cronline unit tests.
@@ -190,15 +211,18 @@ module Rufus
190
211
 
191
212
  items.each do |it|
192
213
 
193
- if it.match(/#[12345]$/)
214
+ if m = it.match(/^(.+)#(l|-?[12345])$/)
194
215
 
195
216
  raise ArgumentError.new(
196
217
  "ranges are not supported for monthdays (#{it})"
197
- ) if it.index('-')
218
+ ) if m[1].index('-')
219
+
220
+ expr = it.gsub(/#l/, '#-1')
198
221
 
199
- (monthdays ||= []) << it
222
+ (monthdays ||= []) << expr
200
223
 
201
224
  else
225
+
202
226
  expr = it.dup
203
227
  WEEKDAYS.each_with_index { |a, i| expr.gsub!(/#{a}/, i.to_s) }
204
228
 
@@ -221,110 +245,109 @@ module Rufus
221
245
  def parse_item(item, min, max)
222
246
 
223
247
  return nil if item == '*'
224
- return [ 'L' ] if item == 'L'
225
- return parse_list(item, min, max) if item.index(',')
226
- return parse_range(item, min, max) if item.match(/[*-\/]/)
227
248
 
228
- i = item.to_i
229
-
230
- i = min if i < min
231
- i = max if i > max
232
-
233
- [ i ]
234
- end
235
-
236
- def parse_list(item, min, max)
237
-
238
- l = item.split(',').collect { |i| parse_range(i, min, max) }.flatten
249
+ r = item.split(',').map { |i| parse_range(i.strip, min, max) }.flatten
239
250
 
240
251
  raise ArgumentError.new(
241
252
  "found duplicates in #{item.inspect}"
242
- ) if l.uniq.size < l.size
253
+ ) if r.uniq.size < r.size
243
254
 
244
- l
255
+ r
245
256
  end
246
257
 
258
+ RANGE_REGEX = /^(\*|\d{1,2})(?:-(\d{1,2}))?(?:\/(\d{1,2}))?$/
259
+
247
260
  def parse_range(item, min, max)
248
261
 
249
- dash = item.index('-')
250
- slash = item.index('/')
262
+ return %w[ L ] if item == 'L'
251
263
 
252
- return parse_item(item, min, max) if (not slash) and (not dash)
264
+ m = item.match(RANGE_REGEX)
253
265
 
254
266
  raise ArgumentError.new(
255
- "'L' (end of month) is not accepted in ranges, " +
256
- "#{item.inspect} is not valid"
257
- ) if item.index('L')
267
+ "cannot parse #{item.inspect}"
268
+ ) unless m
258
269
 
259
- inc = slash ? item[slash + 1..-1].to_i : 1
270
+ sta = m[1]
271
+ sta = sta == '*' ? min : sta.to_i
260
272
 
261
- istart = -1
262
- iend = -1
273
+ edn = m[2]
274
+ edn = edn ? edn.to_i : sta
275
+ edn = max if m[1] == '*'
263
276
 
264
- if dash
277
+ inc = m[3]
278
+ inc = inc ? inc.to_i : 1
265
279
 
266
- istart = item[0..dash - 1].to_i
267
- iend = (slash ? item[dash + 1..slash - 1] : item[dash + 1..-1]).to_i
268
-
269
- else # case */x
270
-
271
- istart = min
272
- iend = max
273
- end
274
-
275
- istart = min if istart < min
276
- iend = max if iend > max
280
+ raise ArgumentError.new(
281
+ "#{item.inspect} is not in range #{min}..#{max}"
282
+ ) if sta < min or edn > max
277
283
 
278
- result = []
284
+ r = []
285
+ val = sta
279
286
 
280
- value = istart
281
287
  loop do
282
- result << value
283
- value = value + inc
284
- break if value > iend
288
+ v = val
289
+ v = 0 if max == 24 && v == 24
290
+ r << v
291
+ break if inc == 1 && val == edn
292
+ val += inc
293
+ break if inc > 1 && val > edn
294
+ val = min if val > max
285
295
  end
286
296
 
287
- result
297
+ r.uniq
288
298
  end
289
299
 
290
- def sub_match?(time, accessor, values=:none)
300
+ def sub_match?(time, accessor, values)
291
301
 
292
- value, values =
293
- if values == :none
294
- [ time, accessor ]
295
- else
296
- [ time.send(accessor), values ]
297
- end
302
+ value = time.send(accessor)
298
303
 
299
304
  return true if values.nil?
300
- return true if values.include?('L') && (time + 24 * 3600).day == 1
305
+ return true if values.include?('L') && (time + DAY_S).day == 1
306
+
307
+ return true if value == 0 && accessor == :hour && values.include?(24)
301
308
 
302
309
  values.include?(value)
303
310
  end
304
311
 
312
+ def monthday_match?(date, values)
313
+
314
+ return true if values.nil?
315
+
316
+ today_values = monthdays(date)
317
+
318
+ (today_values & values).any?
319
+ end
320
+
305
321
  def date_match?(date)
306
322
 
307
323
  return false unless sub_match?(date, :day, @days)
308
324
  return false unless sub_match?(date, :month, @months)
309
325
  return false unless sub_match?(date, :wday, @weekdays)
310
- return false unless sub_match?(CronLine.monthday(date), @monthdays)
326
+ return false unless monthday_match?(date, @monthdays)
311
327
  true
312
328
  end
313
329
 
314
- DAY_IN_SECONDS = 7 * 24 * 3600
330
+ def monthdays(date)
331
+
332
+ pos = 1
333
+ d = date.dup
315
334
 
316
- def self.monthday(date)
335
+ loop do
336
+ d = d - WEEK_S
337
+ break if d.month != date.month
338
+ pos = pos + 1
339
+ end
317
340
 
318
- count = 1
319
- date2 = date.dup
341
+ neg = -1
342
+ d = date.dup
320
343
 
321
344
  loop do
322
- date2 = date2 - DAY_IN_SECONDS
323
- break if date2.month != date.month
324
- count = count + 1
345
+ d = d + WEEK_S
346
+ break if d.month != date.month
347
+ neg = neg - 1
325
348
  end
326
349
 
327
- "#{WEEKDAYS[date.wday]}##{count}"
350
+ [ "#{WEEKDAYS[date.wday]}##{pos}", "#{WEEKDAYS[date.wday]}##{neg}" ]
328
351
  end
329
352
  end
330
353
  end
data/lib/rufus/sc/jobs.rb CHANGED
@@ -198,7 +198,7 @@ module Scheduler
198
198
 
199
199
  to_job.unschedule if to_job
200
200
 
201
- rescue Exception => e
201
+ rescue (@scheduler.options[:exception] || Exception) => e
202
202
 
203
203
  @scheduler.do_handle_exception(self, e)
204
204
  end
@@ -343,8 +343,6 @@ module Rufus
343
343
  at
344
344
  end
345
345
 
346
- protected # well, somehow
347
-
348
346
  DURATIONS2M = [
349
347
  [ 'y', 365 * 24 * 3600 ],
350
348
  [ 'M', 30 * 24 * 3600 ],
@@ -327,6 +327,21 @@ module Rufus::Scheduler
327
327
  }.compact
328
328
  end
329
329
 
330
+ # This is a blocking call, it will return when all the jobs have been
331
+ # unscheduled, waiting for any running one to finish before unscheduling
332
+ # it.
333
+ #
334
+ def terminate_all_jobs
335
+
336
+ all_jobs.each do |job_id, job|
337
+ job.unschedule
338
+ end
339
+
340
+ while running_jobs.size > 0
341
+ sleep 0.01
342
+ end
343
+ end
344
+
330
345
  protected
331
346
 
332
347
  # Returns a job queue instance.
@@ -455,9 +470,23 @@ module Rufus::Scheduler
455
470
  "#{self.class} - #{Rufus::Scheduler::VERSION}"
456
471
  end
457
472
 
473
+ # Stops this scheduler.
474
+ #
475
+ # == :terminate => true
476
+ #
477
+ # If the option :terminate is set to true,
478
+ # the method will return once all the jobs have been unscheduled and
479
+ # are done with their current run if any.
480
+ #
481
+ # (note that if a job is
482
+ # currently running, this method will wait for it to terminate, it
483
+ # will not interrupt the job run).
484
+ #
458
485
  def stop(opts={})
459
486
 
460
487
  @thread.exit
488
+
489
+ terminate_all_jobs if opts[:terminate]
461
490
  end
462
491
 
463
492
  def join
@@ -487,9 +516,24 @@ module Rufus::Scheduler
487
516
  end
488
517
  end
489
518
 
490
- def stop
519
+
520
+ # Stops this scheduler.
521
+ #
522
+ # == :terminate => true
523
+ #
524
+ # If the option :terminate is set to true,
525
+ # the method will return once all the jobs have been unscheduled and
526
+ # are done with their current run if any.
527
+ #
528
+ # (note that if a job is
529
+ # currently running, this method will wait for it to terminate, it
530
+ # will not interrupt the job run).
531
+ #
532
+ def stop(opts={})
491
533
 
492
534
  trap(@options[:signal] || 10)
535
+
536
+ terminate_all_jobs if opts[:terminate]
493
537
  end
494
538
  end
495
539
 
@@ -530,13 +574,27 @@ module Rufus::Scheduler
530
574
 
531
575
  # Stops the scheduler.
532
576
  #
577
+ # == :stop_em => true
578
+ #
533
579
  # If the :stop_em option is passed and set to true, it will stop the
534
580
  # EventMachine (but only if it started the EM by itself !).
535
581
  #
582
+ # == :terminate => true
583
+ #
584
+ # If the option :terminate is set to true,
585
+ # the method will return once all the jobs have been unscheduled and
586
+ # are done with their current run if any.
587
+ #
588
+ # (note that if a job is
589
+ # currently running, this method will wait for it to terminate, it
590
+ # will not interrupt the job run).
591
+ #
536
592
  def stop(opts={})
537
593
 
538
594
  @timer.cancel
539
595
 
596
+ terminate_all_jobs if opts[:terminate]
597
+
540
598
  EM.stop if opts[:stop_em] and @em_thread
541
599
  end
542
600
 
@@ -26,7 +26,7 @@
26
26
  module Rufus
27
27
  module Scheduler
28
28
 
29
- VERSION = '2.0.19'
29
+ VERSION = '2.0.20'
30
30
  end
31
31
  end
32
32
 
@@ -28,6 +28,13 @@ require 'rufus/sc/scheduler'
28
28
 
29
29
  module Rufus::Scheduler
30
30
 
31
+ # Starts and return a new instance of a PlainScheduler.
32
+ #
33
+ def self.new(opts={})
34
+
35
+ PlainScheduler.start_new(opts)
36
+ end
37
+
31
38
  # A quick way to get a scheduler up an running
32
39
  #
33
40
  # require 'rubygems'
@@ -24,13 +24,6 @@ describe Rufus::CronLine do
24
24
  cl(line).to_array.should == array
25
25
  end
26
26
 
27
- def local(*args)
28
- Time.local(*args)
29
- end
30
- def utc(*args)
31
- Time.utc(*args)
32
- end
33
-
34
27
  describe '.new' do
35
28
 
36
29
  it 'interprets cron strings correctly' do
@@ -54,6 +47,13 @@ describe Rufus::CronLine do
54
47
  to_a '1-5 * * * * *', [ [1,2,3,4,5], nil, nil, nil, nil, nil, nil, nil ]
55
48
 
56
49
  to_a '0 0 1 1 *', [ [0], [0], [0], [1], [1], nil, nil, nil ]
50
+
51
+ to_a '0 23-24 * * *', [ [0], [0], [23, 0], nil, nil, nil, nil, nil ]
52
+ #
53
+ # as reported by Aimee Rose in
54
+ # https://github.com/jmettraux/rufus-scheduler/issues/56
55
+
56
+ to_a '0 23-2 * * *', [ [0], [0], [23, 0, 1, 2], nil, nil, nil, nil, nil ]
57
57
  end
58
58
 
59
59
  it 'rejects invalid weekday expressions' do
@@ -84,7 +84,7 @@ describe Rufus::CronLine do
84
84
 
85
85
  to_a(
86
86
  '0 */2 * * *',
87
- [ [0], [0], (0..12).collect { |e| e * 2 }, nil, nil, nil, nil, nil ])
87
+ [ [0], [0], (0..11).collect { |e| e * 2 }, nil, nil, nil, nil, nil ])
88
88
  to_a(
89
89
  '0 7-23/2 * * *',
90
90
  [ [0], [0], (7..23).select { |e| e.odd? }, nil, nil, nil, nil, nil ])
@@ -105,7 +105,8 @@ describe Rufus::CronLine do
105
105
  to_a '09 * * * *', [ [0], [9], nil, nil, nil, nil, nil, nil ]
106
106
  to_a '09-12 * * * *', [ [0], [9, 10, 11, 12], nil, nil, nil, nil, nil, nil ]
107
107
  to_a '07-08 * * * *', [ [0], [7, 8], nil, nil, nil, nil, nil, nil ]
108
- to_a '* */08 * * *', [ [0], nil, [0, 8, 16, 24], nil, nil, nil, nil, nil ]
108
+ to_a '* */08 * * *', [ [0], nil, [0, 8, 16], nil, nil, nil, nil, nil ]
109
+ to_a '* */07 * * *', [ [0], nil, [0, 7, 14, 21], nil, nil, nil, nil, nil ]
109
110
  to_a '* 01-09/04 * * *', [ [0], nil, [1, 5, 9], nil, nil, nil, nil, nil ]
110
111
  to_a '* * * * 06', [ [0], nil, nil, nil, nil, [6], nil, nil ]
111
112
  end
@@ -132,6 +133,18 @@ describe Rufus::CronLine do
132
133
 
133
134
  lambda { cl '* L * * *'}.should raise_error(ArgumentError)
134
135
  end
136
+
137
+ it 'raises for out of range input' do
138
+
139
+ lambda { cl '60-62 * * * *'}.should raise_error(ArgumentError)
140
+ lambda { cl '62 * * * *'}.should raise_error(ArgumentError)
141
+ lambda { cl '60 * * * *'}.should raise_error(ArgumentError)
142
+ lambda { cl '* 25-26 * * *'}.should raise_error(ArgumentError)
143
+ lambda { cl '* 25 * * *'}.should raise_error(ArgumentError)
144
+ #
145
+ # as reported by Aimee Rose in
146
+ # https://github.com/jmettraux/rufus-scheduler/pull/58
147
+ end
135
148
  end
136
149
 
137
150
  describe '#next_time' do
@@ -151,9 +164,16 @@ describe Rufus::CronLine do
151
164
 
152
165
  nt('10 12 13 12 *', now).should == now + 29938200
153
166
  # this one is slow (1 year == 3 seconds)
167
+ #
168
+ # historical note:
169
+ # (comment made in 2006 or 2007, the underlying libs got better and
170
+ # that slowness is gone)
154
171
 
155
172
  nt('0 0 * * thu', now).should == now + 604800
156
173
 
174
+ nt('0 0 * * *', now).should == now + 24 * 3600
175
+ nt('0 24 * * *', now).should == now + 24 * 3600
176
+
157
177
  now = local(2008, 12, 31, 23, 59, 59, 0)
158
178
 
159
179
  nt('* * * * *', now).should == now + 1
@@ -225,34 +245,56 @@ describe Rufus::CronLine do
225
245
 
226
246
  it 'computes the next time correctly when there is a sun#2 involved' do
227
247
 
228
- now = local(1970, 1, 1)
248
+ nt('* * * * sun#1', local(1970, 1, 1)).should == local(1970, 1, 4)
249
+ nt('* * * * sun#2', local(1970, 1, 1)).should == local(1970, 1, 11)
229
250
 
230
- nt('* * * * sun#1', now).should == local(1970, 1, 4)
231
- nt('* * * * sun#2', now).should == local(1970, 1, 11)
251
+ nt('* * * * sun#2', local(1970, 1, 12)).should == local(1970, 2, 8)
252
+ end
232
253
 
233
- now = local(1970, 1, 12)
254
+ it 'computes the next time correctly when there is a sun#2,sun#3 involved' do
234
255
 
235
- nt('* * * * sun#2', now).should == local(1970, 2, 8)
256
+ nt('* * * * sun#2,sun#3', local(1970, 1, 1)).should == local(1970, 1, 11)
257
+ nt('* * * * sun#2,sun#3', local(1970, 1, 12)).should == local(1970, 1, 18)
236
258
  end
237
259
 
238
- it 'computes the next time correctly when there is a sun#2,sun#3 involved' do
260
+ it 'understands sun#L' do
239
261
 
240
- now = local(1970, 1, 1)
262
+ nt('* * * * sun#L', local(1970, 1, 1)).should == local(1970, 1, 25)
263
+ end
264
+
265
+ it 'understands sun#-1' do
266
+
267
+ nt('* * * * sun#-1', local(1970, 1, 1)).should == local(1970, 1, 25)
268
+ end
269
+
270
+ it 'understands sun#-2' do
271
+
272
+ nt('* * * * sun#-2', local(1970, 1, 1)).should == local(1970, 1, 18)
273
+ end
241
274
 
242
- nt('* * * * sun#2,sun#3', now).should == local(1970, 1, 11)
275
+ it 'computes the next time correctly when "L" (last day of month)' do
243
276
 
244
- now = local(1970, 1, 12)
277
+ nt('* * L * *', lo(1970, 1, 1)).should == lo(1970, 1, 31)
278
+ nt('* * L * *', lo(1970, 2, 1)).should == lo(1970, 2, 28)
279
+ nt('* * L * *', lo(1972, 2, 1)).should == lo(1972, 2, 29)
280
+ nt('* * L * *', lo(1970, 4, 1)).should == lo(1970, 4, 30)
281
+ end
282
+ end
245
283
 
246
- nt('* * * * sun#2,sun#3', now).should == local(1970, 1, 18)
284
+ describe '#previous_time' do
285
+
286
+ def pt(cronline, now)
287
+ Rufus::CronLine.new(cronline).previous_time(now)
247
288
  end
248
289
 
249
- it 'computes the next time correctly when there is a L (last day of month)' do
290
+ it 'returns the previous time the cron should have triggered' do
291
+
292
+ pt('* * * * sun', lo(1970, 1, 1)).should == lo(1969, 12, 28, 23, 59, 00)
293
+ pt('* * 13 * *', lo(1970, 1, 1)).should == lo(1969, 12, 13, 23, 59, 00)
294
+ pt('0 12 13 * *', lo(1970, 1, 1)).should == lo(1969, 12, 13, 12, 00)
250
295
 
251
- nt('* * L * *', local(1970,1,1)).should == local(1970, 1, 31)
252
- nt('* * L * *', local(1970,2,1)).should == local(1970, 2, 28)
253
- nt('* * L * *', local(1972,2,1)).should == local(1972, 2, 29)
254
- nt('* * L * *', local(1970,4,1)).should == local(1970, 4, 30)
255
- end
296
+ pt('* * * * * sun', lo(1970, 1, 1)).should == lo(1969, 12, 28, 23, 59, 59)
297
+ end
256
298
  end
257
299
 
258
300
  describe '#matches?' do
@@ -325,16 +367,21 @@ describe Rufus::CronLine do
325
367
  end
326
368
  end
327
369
 
328
- describe '.monthday' do
370
+ describe '#monthdays' do
329
371
 
330
372
  it 'returns the appropriate "sun#2"-like string' do
331
373
 
332
- d = local(1970, 1, 1)
333
- Rufus::CronLine.monthday(d).should == 'thu#1'
334
- Rufus::CronLine.monthday(d + 6 * 24 * 3600).should == 'wed#1'
335
- Rufus::CronLine.monthday(d + 13 * 24 * 3600).should == 'wed#2'
374
+ class Rufus::CronLine
375
+ public :monthdays
376
+ end
377
+
378
+ cl = Rufus::CronLine.new('* * * * *')
379
+
380
+ cl.monthdays(local(1970, 1, 1)).should == %w[ thu#1 thu#-5 ]
381
+ cl.monthdays(local(1970, 1, 7)).should == %w[ wed#1 wed#-4 ]
382
+ cl.monthdays(local(1970, 1, 14)).should == %w[ wed#2 wed#-3 ]
336
383
 
337
- Rufus::CronLine.monthday(local(2011, 3, 11)).should == 'fri#2'
384
+ cl.monthdays(local(2011, 3, 11)).should == %w[ fri#2 fri#-3 ]
338
385
  end
339
386
  end
340
387
  end
@@ -1,4 +1,3 @@
1
-
2
1
  #
3
2
  # Specifying rufus-scheduler
4
3
  #
@@ -93,5 +92,22 @@ describe SCHEDULER_CLASS do
93
92
  $j.class.should == Rufus::Scheduler::InJob
94
93
  $e.to_s.should == 'Houston we have a problem'
95
94
  end
95
+
96
+ it 'allow custom exception rescue' do
97
+ @s.options[:exception]= StandardError
98
+
99
+ job = @s.in 0 do
100
+ exit
101
+ end
102
+
103
+ @e= nil
104
+ begin
105
+ wait_next_tick
106
+ rescue SystemExit => e
107
+ @e= e
108
+ end
109
+
110
+ @e.should_not == nil
111
+ end
96
112
  end
97
113
 
data/spec/rtime_spec.rb CHANGED
@@ -105,7 +105,7 @@ describe 'rufus/rtime' do
105
105
  end
106
106
  end
107
107
 
108
- describe 'rufus/rtime#at_to_f' do
108
+ describe 'Rufus.at_to_f' do
109
109
 
110
110
  def atf(o)
111
111
  Rufus.at_to_f(o)
@@ -200,6 +200,38 @@ describe SCHEDULER_CLASS do
200
200
  end
201
201
  end
202
202
  end
203
+
204
+ context 'termination' do
205
+
206
+ describe '#stop(true)' do
207
+
208
+ it 'terminates the scheduler, blocking until all the jobs are unscheduled' do
209
+
210
+ $every = nil
211
+ $cron = nil
212
+
213
+ s = start_scheduler
214
+ s.every '1s' do
215
+ $every = :in
216
+ sleep 0.5
217
+ $every = :out
218
+ end
219
+ s.cron '* * * * * *' do
220
+ $cron = :in
221
+ sleep 0.5
222
+ $cron = :out
223
+ end
224
+
225
+ sleep 2
226
+
227
+ s.stop(:terminate => true)
228
+
229
+ s.jobs.size.should == 0
230
+ $every.should == :out
231
+ $cron.should == :out
232
+ end
233
+ end
234
+ end
203
235
  end
204
236
 
205
237
  describe 'Rufus::Scheduler#start_new' do
data/spec/spec_base.rb CHANGED
@@ -76,3 +76,12 @@ def wait_next_tick
76
76
  #end
77
77
  end
78
78
 
79
+ def local(*args)
80
+ Time.local(*args)
81
+ end
82
+ alias lo local
83
+
84
+ def utc(*args)
85
+ Time.utc(*args)
86
+ end
87
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rufus-scheduler
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.19
4
+ version: 2.0.20
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-05-06 00:00:00.000000000 Z
12
+ date: 2013-07-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: tzinfo
@@ -67,39 +67,38 @@ extensions: []
67
67
  extra_rdoc_files: []
68
68
  files:
69
69
  - Rakefile
70
- - lib/rufus/otime.rb
71
- - lib/rufus/sc/cronline.rb
72
- - lib/rufus/sc/jobqueues.rb
73
- - lib/rufus/sc/jobs.rb
74
- - lib/rufus/sc/rtime.rb
70
+ - lib/rufus-scheduler.rb
71
+ - lib/rufus/scheduler.rb
75
72
  - lib/rufus/sc/scheduler.rb
73
+ - lib/rufus/sc/rtime.rb
76
74
  - lib/rufus/sc/version.rb
77
- - lib/rufus/scheduler.rb
78
- - lib/rufus-scheduler.rb
79
- - spec/at_in_spec.rb
80
- - spec/at_spec.rb
81
- - spec/blocking_spec.rb
82
- - spec/cron_spec.rb
83
- - spec/cronline_spec.rb
84
- - spec/every_spec.rb
75
+ - lib/rufus/sc/cronline.rb
76
+ - lib/rufus/sc/jobs.rb
77
+ - lib/rufus/sc/jobqueues.rb
78
+ - lib/rufus/otime.rb
79
+ - spec/job_spec.rb
80
+ - spec/stress_schedule_unschedule_spec.rb
81
+ - spec/schedulable_spec.rb
85
82
  - spec/exception_spec.rb
86
83
  - spec/in_spec.rb
87
- - spec/job_spec.rb
88
- - spec/mutex_spec.rb
84
+ - spec/timeout_spec.rb
85
+ - spec/at_spec.rb
86
+ - spec/at_in_spec.rb
87
+ - spec/spec_base.rb
88
+ - spec/cronline_spec.rb
89
89
  - spec/rtime_spec.rb
90
- - spec/schedulable_spec.rb
90
+ - spec/mutex_spec.rb
91
91
  - spec/scheduler_spec.rb
92
- - spec/spec_base.rb
93
- - spec/stress_schedule_unschedule_spec.rb
94
- - spec/timeout_spec.rb
95
- - test/kjw.rb
92
+ - spec/blocking_spec.rb
93
+ - spec/every_spec.rb
94
+ - spec/cron_spec.rb
96
95
  - test/t.rb
96
+ - test/kjw.rb
97
97
  - rufus-scheduler.gemspec
98
98
  - CHANGELOG.txt
99
- - CREDITS.txt
100
- - LICENSE.txt
101
- - out.txt
102
99
  - TODO.txt
100
+ - LICENSE.txt
101
+ - CREDITS.txt
103
102
  - README.rdoc
104
103
  homepage: http://github.com/jmettraux/rufus-scheduler
105
104
  licenses: []
@@ -113,21 +112,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
113
112
  - - ! '>='
114
113
  - !ruby/object:Gem::Version
115
114
  version: '0'
116
- segments:
117
- - 0
118
- hash: -1513262691769122142
119
115
  required_rubygems_version: !ruby/object:Gem::Requirement
120
116
  none: false
121
117
  requirements:
122
118
  - - ! '>='
123
119
  - !ruby/object:Gem::Version
124
120
  version: '0'
125
- segments:
126
- - 0
127
- hash: -1513262691769122142
128
121
  requirements: []
129
122
  rubyforge_project: rufus
130
- rubygems_version: 1.8.24
123
+ rubygems_version: 1.8.23
131
124
  signing_key:
132
125
  specification_version: 3
133
126
  summary: job scheduler for Ruby (at, cron, in and every jobs)
data/out.txt DELETED
@@ -1,4 +0,0 @@
1
- ruby-1.8.7-p249 :001 > nada
2
- NameError: undefined local variable or method `nada' for #<Object:0x1001d1298>
3
- from (irb):1
4
- ruby-1.8.7-p249 :002 > ^D