clockwork 0.5.5 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -42,10 +42,6 @@ Triggering frequent.job
42
42
  If you would not like to taint the namespace with `include Clockwork`, you can use
43
43
  it as the module (thanks to [hoverlover](https://github.com/hoverlover/clockwork/)).
44
44
 
45
- As reported in [issue #41](https://github.com/tomykaira/clockwork/issues/41#issuecomment-22073609),
46
- this technique is necessary when you use Clockwork with Sinatra,
47
- because both Sinatra and Clockwork add `register()` method into global namespace.
48
-
49
45
  ```ruby
50
46
  require 'clockwork'
51
47
 
data/Rakefile CHANGED
@@ -1,7 +1,9 @@
1
1
  require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
2
3
 
3
- task 'test' do
4
- sh 'ruby test/clockwork_test.rb'
4
+ Rake::TestTask.new do |t|
5
+ t.test_files = FileList['test/*_test.rb']
6
+ t.verbose = false
5
7
  end
6
8
 
7
9
  task :default => :test
@@ -1,8 +1,9 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "clockwork"
3
- s.version = "0.5.5"
3
+ s.version = "0.6.0"
4
4
 
5
5
  s.authors = ["Adam Wiggins", "tomykaira"]
6
+ s.license = 'MIT'
6
7
  s.description = "A scheduler process to replace cron, using a more flexible Ruby syntax running as a single long-running process. Inspired by rufus-scheduler and resque-scheduler."
7
8
  s.email = ["adam@heroku.com", "tomykaira@gmail.com"]
8
9
  s.extra_rdoc_files = [
@@ -1,215 +1,34 @@
1
1
  require 'logger'
2
2
  require 'active_support/time'
3
3
 
4
- module Clockwork
5
-
6
- @@events = []
7
-
8
- class At
9
- class FailedToParse < StandardError; end;
10
- NOT_SPECIFIED = nil
11
- WDAYS = %w[sunday monday tuesday wednesday thursday friday saturday].map do |w|
12
- [w, w.capitalize, w[0...3], w[0...3].capitalize]
13
- end
14
-
15
- def self.parse(at)
16
- return unless at
17
- case at
18
- when /^([[:alpha:]]+)\s(.*)$/
19
- ret = parse($2)
20
- wday = WDAYS.find_index { |x| x.include?($1) }
21
- raise FailedToParse, at if wday.nil?
22
- ret.wday = wday
23
- ret
24
- when /^(\d{1,2}):(\d\d)$/
25
- new($2.to_i, $1.to_i)
26
- when /^\*{1,2}:(\d\d)$/
27
- new($1.to_i)
28
- else
29
- raise FailedToParse, at
30
- end
31
- rescue ArgumentError
32
- raise FailedToParse, at
33
- end
34
-
35
- attr_writer :min, :hour, :wday
36
-
37
- def initialize(min, hour=NOT_SPECIFIED, wday=NOT_SPECIFIED)
38
- if min.nil? || min < 0 || min > 59 ||
39
- (hour != NOT_SPECIFIED && (hour < 0 || hour > 23)) ||
40
- (wday != NOT_SPECIFIED && (wday < 0 || wday > 6))
41
- raise ArgumentError
42
- end
43
- @min = min
44
- @hour = hour
45
- @wday = wday
46
- end
47
-
48
- def ready?(t)
49
- t.min == @min and
50
- (@hour == NOT_SPECIFIED or t.hour == @hour) and
51
- (@wday == NOT_SPECIFIED or t.wday == @wday)
52
- end
53
- end
54
-
55
- class Event
56
- attr_accessor :job, :last
57
-
58
- def initialize(period, job, block, options={})
59
- @period = period
60
- @job = job
61
- @at = At.parse(options[:at])
62
- @last = nil
63
- @block = block
64
- if options[:if]
65
- if options[:if].respond_to?(:call)
66
- @if = options[:if]
67
- else
68
- raise ArgumentError.new(':if expects a callable object, but #{options[:if]} does not respond to call')
69
- end
70
- end
71
-
72
- @thread = !!(options.has_key?(:thread) ? options[:thread] : Clockwork.config[:thread])
73
-
74
- @timezone = options[:tz] || Clockwork.config[:tz]
75
- end
76
-
77
- def to_s
78
- @job
79
- end
80
-
81
- def convert_timezone(t)
82
- @timezone ? t.in_time_zone(@timezone) : t
83
- end
84
-
85
- def time?(t)
86
- t = convert_timezone(t)
87
- elapsed_ready = (@last.nil? or (t - @last).to_i >= @period)
88
- elapsed_ready and (@at.nil? or @at.ready?(t)) and (@if.nil? or @if.call(t))
89
- end
90
-
91
- def thread?
92
- @thread
93
- end
94
-
95
- def run(t)
96
- t = convert_timezone(t)
97
- @last = t
98
-
99
- if thread?
100
- if Clockwork.thread_available?
101
- Thread.new { execute }
102
- else
103
- log_error "Threads exhausted; skipping #{self}"
104
- end
105
- else
106
- execute
107
- end
108
- end
109
-
110
- def execute
111
- @block.call(@job)
112
- rescue => e
113
- log_error e
114
- end
115
-
116
- def log_error(e)
117
- Clockwork.config[:logger].error(e)
118
- end
119
-
120
- def exception_message(e)
121
- msg = [ "Exception #{e.class} -> #{e.message}" ]
122
-
123
- base = File.expand_path(Dir.pwd) + '/'
124
- e.backtrace.each do |t|
125
- msg << " #{File.expand_path(t).gsub(/#{base}/, '')}"
126
- end
127
-
128
- msg.join("\n")
129
- end
130
- end
131
-
132
- def thread_available?
133
- Thread.list.count < config[:max_threads]
134
- end
135
-
136
- def configure
137
- yield(config)
138
- end
139
-
140
- def config
141
- @@configuration
142
- end
4
+ require 'clockwork/at'
5
+ require 'clockwork/event'
6
+ require 'clockwork/manager'
143
7
 
8
+ module Clockwork
144
9
  extend self
145
10
 
146
- def default_configuration
147
- { :sleep_timeout => 1, :logger => Logger.new(STDOUT), :thread => false, :max_threads => 10 }
148
- end
11
+ @@manager = Manager.new
149
12
 
150
- @@configuration = default_configuration
151
-
152
- def handler(&block)
153
- @@handler = block
13
+ def configure(&block)
14
+ @@manager.configure(&block)
154
15
  end
155
16
 
156
- class NoHandlerDefined < RuntimeError; end
157
-
158
- def get_handler
159
- raise NoHandlerDefined unless (defined?(@@handler) and @@handler)
160
- @@handler
17
+ def handler(&block)
18
+ @@manager.handler(&block)
161
19
  end
162
20
 
163
21
  def every(period, job, options={}, &block)
164
- if options[:at].respond_to?(:each)
165
- each_options = options.clone
166
- options[:at].each do |at|
167
- each_options[:at] = at
168
- register(period, job, block, each_options)
169
- end
170
- else
171
- register(period, job, block, options)
172
- end
22
+ @@manager.every(period, job, options, &block)
173
23
  end
174
24
 
175
25
  def run
176
- log "Starting clock for #{@@events.size} events: [ " + @@events.map { |e| e.to_s }.join(' ') + " ]"
177
- loop do
178
- tick
179
- sleep(config[:sleep_timeout])
180
- end
181
- end
182
-
183
- def log(msg)
184
- config[:logger].info(msg)
185
- end
186
-
187
- def tick(t=Time.now)
188
- to_run = @@events.select do |event|
189
- event.time?(t)
190
- end
191
-
192
- to_run.each do |event|
193
- log "Triggering '#{event}'"
194
- event.run(t)
195
- end
196
-
197
- to_run
26
+ @@manager.run
198
27
  end
199
28
 
200
29
  def clear!
201
- @@events = []
202
- @@handler = nil
203
- @@configuration = Clockwork.default_configuration
30
+ @@manager = Manager.new
204
31
  end
205
-
206
- private
207
- def register(period, job, block, options)
208
- event = Event.new(period, job, block || get_handler, options)
209
- @@events << event
210
- event
211
- end
212
-
213
32
  end
214
33
 
215
34
  unless 1.respond_to?(:seconds)
@@ -0,0 +1,49 @@
1
+ module Clockwork
2
+ class At
3
+ class FailedToParse < StandardError; end
4
+
5
+ NOT_SPECIFIED = nil
6
+ WDAYS = %w[sunday monday tuesday wednesday thursday friday saturday].map do |w|
7
+ [w, w.capitalize, w[0...3], w[0...3].capitalize]
8
+ end
9
+
10
+ def self.parse(at)
11
+ return unless at
12
+ case at
13
+ when /^([[:alpha:]]+)\s(.*)$/
14
+ ret = parse($2)
15
+ wday = WDAYS.find_index { |x| x.include?($1) }
16
+ raise FailedToParse, at if wday.nil?
17
+ ret.wday = wday
18
+ ret
19
+ when /^(\d{1,2}):(\d\d)$/
20
+ new($2.to_i, $1.to_i)
21
+ when /^\*{1,2}:(\d\d)$/
22
+ new($1.to_i)
23
+ else
24
+ raise FailedToParse, at
25
+ end
26
+ rescue ArgumentError
27
+ raise FailedToParse, at
28
+ end
29
+
30
+ attr_writer :min, :hour, :wday
31
+
32
+ def initialize(min, hour=NOT_SPECIFIED, wday=NOT_SPECIFIED)
33
+ if min.nil? || min < 0 || min > 59 ||
34
+ (hour != NOT_SPECIFIED && (hour < 0 || hour > 23)) ||
35
+ (wday != NOT_SPECIFIED && (wday < 0 || wday > 6))
36
+ raise ArgumentError
37
+ end
38
+ @min = min
39
+ @hour = hour
40
+ @wday = wday
41
+ end
42
+
43
+ def ready?(t)
44
+ t.min == @min and
45
+ (@hour == NOT_SPECIFIED or t.hour == @hour) and
46
+ (@wday == NOT_SPECIFIED or t.wday == @wday)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,71 @@
1
+ module Clockwork
2
+ class Event
3
+ attr_accessor :job, :last
4
+
5
+ def initialize(manager, period, job, block, options={})
6
+ @manager = manager
7
+ @period = period
8
+ @job = job
9
+ @at = At.parse(options[:at])
10
+ @last = nil
11
+ @block = block
12
+ @if = options[:if]
13
+ @thread = options[:thread]
14
+ @timezone = options[:tz]
15
+ end
16
+
17
+ def to_s
18
+ @job
19
+ end
20
+
21
+ def convert_timezone(t)
22
+ @timezone ? t.in_time_zone(@timezone) : t
23
+ end
24
+
25
+ def time?(t)
26
+ t = convert_timezone(t)
27
+ elapsed_ready = (@last.nil? or (t - @last).to_i >= @period)
28
+ elapsed_ready and (@at.nil? or @at.ready?(t)) and (@if.nil? or @if.call(t))
29
+ end
30
+
31
+ def thread?
32
+ @thread
33
+ end
34
+
35
+ def run(t)
36
+ t = convert_timezone(t)
37
+ @last = t
38
+
39
+ if thread?
40
+ if @manager.thread_available?
41
+ Thread.new { execute }
42
+ else
43
+ log_error "Threads exhausted; skipping #{self}"
44
+ end
45
+ else
46
+ execute
47
+ end
48
+ end
49
+
50
+ def execute
51
+ @block.call(@job)
52
+ rescue => e
53
+ log_error e
54
+ end
55
+
56
+ def log_error(e)
57
+ @manager.config[:logger].error(e)
58
+ end
59
+
60
+ def exception_message(e)
61
+ msg = [ "Exception #{e.class} -> #{e.message}" ]
62
+
63
+ base = File.expand_path(Dir.pwd) + '/'
64
+ e.backtrace.each do |t|
65
+ msg << " #{File.expand_path(t).gsub(/#{base}/, '')}"
66
+ end
67
+
68
+ msg.join("\n")
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,91 @@
1
+ module Clockwork
2
+ class Manager
3
+ class NoHandlerDefined < RuntimeError; end
4
+
5
+ attr_reader :config
6
+
7
+ def initialize
8
+ @events = []
9
+ @config = default_configuration
10
+ @handler = nil
11
+ end
12
+
13
+ def thread_available?
14
+ Thread.list.count < config[:max_threads]
15
+ end
16
+
17
+ def configure
18
+ yield(config)
19
+ end
20
+
21
+ def default_configuration
22
+ { :sleep_timeout => 1, :logger => Logger.new(STDOUT), :thread => false, :max_threads => 10 }
23
+ end
24
+
25
+ def handler(&block)
26
+ @handler = block
27
+ end
28
+
29
+ def get_handler
30
+ raise NoHandlerDefined unless (defined?(@handler) and @handler)
31
+ @handler
32
+ end
33
+
34
+ def every(period, job, options={}, &block)
35
+ if options[:at].respond_to?(:each)
36
+ each_options = options.clone
37
+ options[:at].each do |at|
38
+ each_options[:at] = at
39
+ register(period, job, block, each_options)
40
+ end
41
+ else
42
+ register(period, job, block, options)
43
+ end
44
+ end
45
+
46
+ def run
47
+ log "Starting clock for #{@events.size} events: [ " + @events.map { |e| e.to_s }.join(' ') + " ]"
48
+ loop do
49
+ tick
50
+ sleep(config[:sleep_timeout])
51
+ end
52
+ end
53
+
54
+ def tick(t=Time.now)
55
+ to_run = @events.select do |event|
56
+ event.time?(t)
57
+ end
58
+
59
+ to_run.each do |event|
60
+ log "Triggering '#{event}'"
61
+ event.run(t)
62
+ end
63
+
64
+ to_run
65
+ end
66
+
67
+ private
68
+ def log(msg)
69
+ config[:logger].info(msg)
70
+ end
71
+
72
+ def register(period, job, block, options)
73
+ event = Event.new(self, period, job, block || get_handler, parse_event_option(options))
74
+ @events << event
75
+ event
76
+ end
77
+
78
+ def parse_event_option(options)
79
+ if options[:if]
80
+ if !options[:if].respond_to?(:call)
81
+ raise ArgumentError.new(':if expects a callable object, but #{options[:if]} does not respond to call')
82
+ end
83
+ end
84
+
85
+ options[:thread] = !!(options.has_key?(:thread) ? options[:thread] : config[:thread])
86
+ options[:tz] ||= config[:tz]
87
+
88
+ options
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,67 @@
1
+ require File.expand_path('../../lib/clockwork', __FILE__)
2
+ require 'rubygems'
3
+ require 'contest'
4
+ require 'mocha/setup'
5
+ require 'time'
6
+ require 'active_support/time'
7
+
8
+ class AtTest < Test::Unit::TestCase
9
+ def time_in_day(hour, minute)
10
+ Time.new(2013, 1, 1, hour, minute, 0)
11
+ end
12
+
13
+ test '16:20' do
14
+ at = Clockwork::At.parse('16:20')
15
+ assert !at.ready?(time_in_day(16, 19))
16
+ assert at.ready?(time_in_day(16, 20))
17
+ assert !at.ready?(time_in_day(16, 21))
18
+ end
19
+
20
+ test '8:20' do
21
+ at = Clockwork::At.parse('8:20')
22
+ assert !at.ready?(time_in_day(8, 19))
23
+ assert at.ready?(time_in_day(8, 20))
24
+ assert !at.ready?(time_in_day(8, 21))
25
+ end
26
+
27
+ test '**:20' do
28
+ at = Clockwork::At.parse('**:20')
29
+
30
+ assert !at.ready?(time_in_day(15, 19))
31
+ assert at.ready?(time_in_day(15, 20))
32
+ assert !at.ready?(time_in_day(15, 21))
33
+
34
+ assert !at.ready?(time_in_day(16, 19))
35
+ assert at.ready?(time_in_day(16, 20))
36
+ assert !at.ready?(time_in_day(16, 21))
37
+ end
38
+
39
+ test '**:20' do
40
+ at = Clockwork::At.parse('*:20')
41
+
42
+ assert !at.ready?(time_in_day(15, 19))
43
+ assert at.ready?(time_in_day(15, 20))
44
+ assert !at.ready?(time_in_day(15, 21))
45
+
46
+ assert !at.ready?(time_in_day(16, 19))
47
+ assert at.ready?(time_in_day(16, 20))
48
+ assert !at.ready?(time_in_day(16, 21))
49
+ end
50
+
51
+ test 'Saturday 12:00' do
52
+ at = Clockwork::At.parse('Saturday 12:00')
53
+
54
+ assert !at.ready?(Time.new(2010, 1, 1, 12, 00))
55
+ assert at.ready?(Time.new(2010, 1, 2, 12, 00)) # Saturday
56
+ assert !at.ready?(Time.new(2010, 1, 3, 12, 00))
57
+ assert at.ready?(Time.new(2010, 1, 9, 12, 00))
58
+ end
59
+
60
+ test 'Saturday 12:00' do
61
+ at = Clockwork::At.parse('sat 12:00')
62
+
63
+ assert !at.ready?(Time.new(2010, 1, 1, 12, 00))
64
+ assert at.ready?(Time.new(2010, 1, 2, 12, 00))
65
+ assert !at.ready?(Time.new(2010, 1, 3, 12, 00))
66
+ end
67
+ end
@@ -1,307 +1,62 @@
1
1
  require File.expand_path('../../lib/clockwork', __FILE__)
2
- require 'rubygems'
3
2
  require 'contest'
4
- require 'mocha/setup'
5
- require 'time'
6
- require 'active_support/time'
7
-
8
- module Clockwork
9
- def log(msg)
10
- end
11
- end
3
+ require 'timeout'
12
4
 
13
5
  class ClockworkTest < Test::Unit::TestCase
14
- setup do
6
+ teardown do
15
7
  Clockwork.clear!
16
- Clockwork.handler { }
17
8
  end
18
9
 
19
- def assert_will_run(t)
20
- if t.is_a? String
21
- t = Time.parse(t)
10
+ def set_string_io_logger
11
+ string_io = StringIO.new
12
+ Clockwork.configure do |config|
13
+ config[:logger] = Logger.new(string_io)
22
14
  end
23
- assert_equal 1, Clockwork.tick(t).size
15
+ string_io
24
16
  end
25
17
 
26
- def assert_wont_run(t)
27
- if t.is_a? String
28
- t = Time.parse(t)
18
+ def run_in_thread
19
+ Thread.new do
20
+ Clockwork.run
29
21
  end
30
- assert_equal 0, Clockwork.tick(t).size
31
22
  end
32
23
 
33
- test "once a minute" do
34
- Clockwork.every(1.minute, 'myjob')
35
-
36
- assert_will_run(t=Time.now)
37
- assert_wont_run(t+30)
38
- assert_will_run(t+60)
39
- end
40
-
41
- test "every three minutes" do
42
- Clockwork.every(3.minutes, 'myjob')
43
-
44
- assert_will_run(t=Time.now)
45
- assert_wont_run(t+2*60)
46
- assert_will_run(t+3*60)
47
- end
48
-
49
- test "once an hour" do
50
- Clockwork.every(1.hour, 'myjob')
51
-
52
- assert_will_run(t=Time.now)
53
- assert_wont_run(t+30*60)
54
- assert_will_run(t+60*60)
55
- end
56
-
57
- test "once a week" do
58
- Clockwork.every(1.week, 'myjob')
59
-
60
- assert_will_run(t=Time.now)
61
- assert_wont_run(t+60*60*24*6)
62
- assert_will_run(t+60*60*24*7)
63
- end
64
-
65
- test "once a day at 16:20" do
66
- Clockwork.every(1.day, 'myjob', :at => '16:20')
67
-
68
- assert_wont_run 'jan 1 2010 16:19:59'
69
- assert_will_run 'jan 1 2010 16:20:00'
70
- assert_wont_run 'jan 1 2010 16:20:01'
71
- assert_wont_run 'jan 2 2010 16:19:59'
72
- assert_will_run 'jan 2 2010 16:20:00'
73
- end
74
-
75
- test ":at also accepts 8:20" do
76
- Clockwork.every(1.hour, 'myjob', :at => '8:20')
77
-
78
- assert_wont_run 'jan 1 2010 08:19:59'
79
- assert_will_run 'jan 1 2010 08:20:00'
80
- assert_wont_run 'jan 1 2010 08:20:01'
81
- end
82
-
83
- test "twice a day at 16:20 and 18:10" do
84
- Clockwork.every(1.day, 'myjob', :at => ['16:20', '18:10'])
85
-
86
- assert_wont_run 'jan 1 2010 16:19:59'
87
- assert_will_run 'jan 1 2010 16:20:00'
88
- assert_wont_run 'jan 1 2010 16:20:01'
89
-
90
- assert_wont_run 'jan 1 2010 18:09:59'
91
- assert_will_run 'jan 1 2010 18:10:00'
92
- assert_wont_run 'jan 1 2010 18:10:01'
93
- end
94
-
95
- test "once an hour at **:20" do
96
- Clockwork.every(1.hour, 'myjob', :at => '**:20')
97
-
98
- assert_wont_run 'jan 1 2010 15:19:59'
99
- assert_will_run 'jan 1 2010 15:20:00'
100
- assert_wont_run 'jan 1 2010 15:20:01'
101
- assert_wont_run 'jan 2 2010 16:19:59'
102
- assert_will_run 'jan 2 2010 16:20:00'
103
- end
104
-
105
- test ":at also accepts *:20" do
106
- Clockwork.every(1.hour, 'myjob', :at => '*:20')
107
-
108
- assert_wont_run 'jan 1 2010 15:19:59'
109
- assert_will_run 'jan 1 2010 15:20:00'
110
- assert_wont_run 'jan 1 2010 15:20:01'
111
- end
112
-
113
- test "on every Saturday" do
114
- Clockwork.every(1.week, 'myjob', :at => 'Saturday 12:00')
115
-
116
- assert_wont_run 'jan 1 2010 12:00:00'
117
- assert_will_run 'jan 2 2010 12:00:00' # Saturday
118
- assert_wont_run 'jan 3 2010 12:00:00'
119
- assert_wont_run 'jan 8 2010 12:00:00'
120
- assert_will_run 'jan 9 2010 12:00:00'
121
- end
122
-
123
- test ":at accepts abbreviated weekday" do
124
- Clockwork.every(1.week, 'myjob', :at => 'sat 12:00')
125
-
126
- assert_wont_run 'jan 1 2010 12:00:00'
127
- assert_will_run 'jan 2 2010 12:00:00' # Saturday
128
- assert_wont_run 'jan 3 2010 12:00:00'
129
- end
130
-
131
- test "aborts when no handler defined" do
132
- Clockwork.clear!
133
- assert_raise(Clockwork::NoHandlerDefined) do
134
- Clockwork.every(1.minute, 'myjob')
135
- end
136
- end
137
-
138
- test "aborts when fails to parse" do
139
- assert_raise(Clockwork::At::FailedToParse) do
140
- Clockwork.every(1.day, "myjob", :at => "a:bc")
24
+ test 'should run events with configured logger' do
25
+ run = false
26
+ string_io = set_string_io_logger
27
+ Clockwork.handler do |job|
28
+ run = job == 'myjob'
141
29
  end
142
- end
143
-
144
- test "general handler" do
145
- $set_me = 0
146
- Clockwork.handler { $set_me = 1 }
147
30
  Clockwork.every(1.minute, 'myjob')
148
- Clockwork.tick(Time.now)
149
- assert_equal 1, $set_me
150
- end
151
-
152
- test "event-specific handler" do
153
- $set_me = 0
154
- Clockwork.every(1.minute, 'myjob') { $set_me = 2 }
155
- Clockwork.tick(Time.now)
156
-
157
- assert_equal 2, $set_me
158
- end
159
31
 
160
- test "exceptions are trapped and logged" do
161
- Clockwork.handler { raise 'boom' }
162
- event = Clockwork.every(1.minute, 'myjob')
163
- event.expects(:log_error)
32
+ runner = run_in_thread
164
33
 
165
- assert_nothing_raised do
166
- Clockwork.tick(Time.now)
34
+ timeout(5) do
35
+ sleep 1 until run
167
36
  end
37
+ runner.kill
38
+ assert run
39
+ assert string_io.string.include?('Triggering')
168
40
  end
169
41
 
170
- test "exceptions still set the last timestamp to avoid spastic error loops" do
171
- Clockwork.handler { raise 'boom' }
172
- event = Clockwork.every(1.minute, 'myjob')
173
- event.stubs(:log_error)
174
- Clockwork.tick(t = Time.now)
175
- assert_equal t, event.last
176
- end
177
-
178
- test "should be configurable" do
179
- Clockwork.configure do |config|
180
- config[:sleep_timeout] = 200
181
- config[:logger] = "A Logger"
182
- config[:max_threads] = 10
183
- config[:thread] = true
184
- end
185
-
186
- assert_equal 200, Clockwork.config[:sleep_timeout]
187
- assert_equal "A Logger", Clockwork.config[:logger]
188
- assert_equal 10, Clockwork.config[:max_threads]
189
- assert_equal true, Clockwork.config[:thread]
190
- end
191
-
192
- test "configuration should have reasonable defaults" do
193
- assert_equal 1, Clockwork.config[:sleep_timeout]
194
- assert Clockwork.config[:logger].is_a?(Logger)
195
- assert_equal 10, Clockwork.config[:max_threads]
196
- assert_equal false, Clockwork.config[:thread]
197
- end
198
-
199
- test "should be able to specify a different timezone than local" do
200
- Clockwork.every(1.day, 'myjob', :at => '10:00', :tz => 'UTC')
201
-
202
- assert_wont_run 'jan 1 2010 10:00:00 EST'
203
- assert_will_run 'jan 1 2010 10:00:00 UTC'
204
- end
205
-
206
- test "should be able to specify a different timezone than local for multiple times" do
207
- Clockwork.every(1.day, 'myjob', :at => ['10:00', '8:00'], :tz => 'UTC')
208
-
209
- assert_wont_run 'jan 1 2010 08:00:00 EST'
210
- assert_will_run 'jan 1 2010 08:00:00 UTC'
211
- assert_wont_run 'jan 1 2010 10:00:00 EST'
212
- assert_will_run 'jan 1 2010 10:00:00 UTC'
213
- end
214
-
215
- test "should be able to configure a default timezone to use for all events" do
216
- Clockwork.configure { |config| config[:tz] = 'UTC' }
217
- Clockwork.every(1.day, 'myjob', :at => '10:00')
218
-
219
- assert_wont_run 'jan 1 2010 10:00:00 EST'
220
- assert_will_run 'jan 1 2010 10:00:00 UTC'
221
- end
222
-
223
- test "should be able to override a default timezone in an event" do
224
- Clockwork.configure { |config| config[:tz] = 'UTC' }
225
- Clockwork.every(1.day, 'myjob', :at => '10:00', :tz => 'EST')
226
-
227
- assert_will_run 'jan 1 2010 10:00:00 EST'
228
- assert_wont_run 'jan 1 2010 10:00:00 UTC'
229
- end
230
-
231
- test ":if true then always run" do
232
- Clockwork.every(1.second, 'myjob', :if => lambda { |_| true })
233
-
234
- assert_will_run 'jan 1 2010 16:20:00'
235
- end
236
-
237
- test ":if false then never run" do
238
- Clockwork.every(1.second, 'myjob', :if => lambda { |_| false })
239
-
240
- assert_wont_run 'jan 1 2010 16:20:00'
241
- end
242
-
243
- test ":if the first day of month" do
244
- Clockwork.every(1.second, 'myjob', :if => lambda { |t| t.day == 1 })
245
-
246
- assert_will_run 'jan 1 2010 16:20:00'
247
- assert_wont_run 'jan 2 2010 16:20:00'
248
- assert_will_run 'feb 1 2010 16:20:00'
249
- end
250
-
251
- test ":if it is compared to a time with zone" do
252
- tz = 'America/Chicago'
253
- time = Time.utc(2012,5,25,10,00)
254
- Clockwork.every(1.second, 'myjob', tz: tz, :if => lambda { |t|(
255
- ((time - 1.hour)..(time + 1.hour)).cover? t
256
- )})
257
- assert_will_run time
258
- end
259
-
260
- test ":if is not callable then raise ArgumentError" do
261
- assert_raise(ArgumentError) do
262
- Clockwork.every(1.second, 'myjob', :if => true)
263
- end
264
- end
265
-
266
- test "should warn about missing jobs upon exhausting threads" do
267
- Clockwork.configure do |config|
268
- config[:max_threads] = 0
269
- end
270
-
271
- event = Clockwork.every(1.minute, 'myjob', :thread => true)
272
- event.expects(:log_error).with("Threads exhausted; skipping #{event}")
42
+ test 'should not run anything after reset' do
43
+ Clockwork.every(1.minute, 'myjob') { }
44
+ Clockwork.clear!
273
45
 
274
- Clockwork.tick(Time.now)
46
+ string_io = set_string_io_logger
47
+ runner = run_in_thread
48
+ sleep 1
49
+ runner.kill
50
+ assert string_io.string.include?('0 events')
275
51
  end
276
52
 
277
- describe "thread option" do
278
- test "should not use thread by default" do
279
- event = Clockwork.every(1.minute, 'myjob')
280
- assert !event.thread?
281
- end
282
-
283
- test "should use thread if thread option is specified with truly value" do
284
- event = Clockwork.every(1.minute, 'myjob', :thread => true)
285
- assert event.thread?
286
- end
287
-
288
- test "should use thread if global thread option is set" do
289
- Clockwork.configure do |config|
290
- config[:thread] = true
291
- end
292
-
293
- event = Clockwork.every(1.minute, 'myjob')
294
- assert event.thread?
295
- end
296
-
297
- test "should not use thread if job option overrides global option" do
298
- Clockwork.configure do |config|
299
- config[:thread] = true
300
- end
301
-
302
- event = Clockwork.every(1.minute, 'myjob', :thread => false)
303
- assert !event.thread?
304
- end
53
+ test 'should pass all arguments to every' do
54
+ Clockwork.every(1.second, 'myjob', if: lambda { false }) { }
55
+ string_io = set_string_io_logger
56
+ runner = run_in_thread
57
+ sleep 1
58
+ runner.kill
59
+ assert string_io.string.include?('1 events')
60
+ assert !string_io.string.include?('Triggering')
305
61
  end
306
-
307
62
  end
@@ -0,0 +1,272 @@
1
+ require File.expand_path('../../lib/clockwork', __FILE__)
2
+ require 'rubygems'
3
+ require 'contest'
4
+ require 'mocha/setup'
5
+ require 'time'
6
+ require 'active_support/time'
7
+
8
+ class ManagerTest < Test::Unit::TestCase
9
+ setup do
10
+ @manager = Clockwork::Manager.new
11
+ class << @manager
12
+ def log(msg); end
13
+ end
14
+ @manager.handler { }
15
+ end
16
+
17
+ def assert_will_run(t)
18
+ if t.is_a? String
19
+ t = Time.parse(t)
20
+ end
21
+ assert_equal 1, @manager.tick(t).size
22
+ end
23
+
24
+ def assert_wont_run(t)
25
+ if t.is_a? String
26
+ t = Time.parse(t)
27
+ end
28
+ assert_equal 0, @manager.tick(t).size
29
+ end
30
+
31
+ test "once a minute" do
32
+ @manager.every(1.minute, 'myjob')
33
+
34
+ assert_will_run(t=Time.now)
35
+ assert_wont_run(t+30)
36
+ assert_will_run(t+60)
37
+ end
38
+
39
+ test "every three minutes" do
40
+ @manager.every(3.minutes, 'myjob')
41
+
42
+ assert_will_run(t=Time.now)
43
+ assert_wont_run(t+2*60)
44
+ assert_will_run(t+3*60)
45
+ end
46
+
47
+ test "once an hour" do
48
+ @manager.every(1.hour, 'myjob')
49
+
50
+ assert_will_run(t=Time.now)
51
+ assert_wont_run(t+30*60)
52
+ assert_will_run(t+60*60)
53
+ end
54
+
55
+ test "once a week" do
56
+ @manager.every(1.week, 'myjob')
57
+
58
+ assert_will_run(t=Time.now)
59
+ assert_wont_run(t+60*60*24*6)
60
+ assert_will_run(t+60*60*24*7)
61
+ end
62
+
63
+ test "aborts when no handler defined" do
64
+ manager = Clockwork::Manager.new
65
+ assert_raise(Clockwork::Manager::NoHandlerDefined) do
66
+ manager.every(1.minute, 'myjob')
67
+ end
68
+ end
69
+
70
+ test "aborts when fails to parse" do
71
+ assert_raise(Clockwork::At::FailedToParse) do
72
+ @manager.every(1.day, "myjob", :at => "a:bc")
73
+ end
74
+ end
75
+
76
+ test "general handler" do
77
+ $set_me = 0
78
+ @manager.handler { $set_me = 1 }
79
+ @manager.every(1.minute, 'myjob')
80
+ @manager.tick(Time.now)
81
+ assert_equal 1, $set_me
82
+ end
83
+
84
+ test "event-specific handler" do
85
+ $set_me = 0
86
+ @manager.every(1.minute, 'myjob') { $set_me = 2 }
87
+ @manager.tick(Time.now)
88
+
89
+ assert_equal 2, $set_me
90
+ end
91
+
92
+ test "exceptions are trapped and logged" do
93
+ @manager.handler { raise 'boom' }
94
+ @manager.every(1.minute, 'myjob')
95
+
96
+ logger = Logger.new(StringIO.new)
97
+ @manager.configure { |c| c[:logger] = logger }
98
+ logger.expects(:error)
99
+
100
+ assert_nothing_raised do
101
+ @manager.tick(Time.now)
102
+ end
103
+ end
104
+
105
+ test "exceptions still set the last timestamp to avoid spastic error loops" do
106
+ @manager.handler { raise 'boom' }
107
+ event = @manager.every(1.minute, 'myjob')
108
+ event.stubs(:log_error)
109
+ @manager.tick(t = Time.now)
110
+ assert_equal t, event.last
111
+ end
112
+
113
+ test "should be configurable" do
114
+ @manager.configure do |config|
115
+ config[:sleep_timeout] = 200
116
+ config[:logger] = "A Logger"
117
+ config[:max_threads] = 10
118
+ config[:thread] = true
119
+ end
120
+
121
+ assert_equal 200, @manager.config[:sleep_timeout]
122
+ assert_equal "A Logger", @manager.config[:logger]
123
+ assert_equal 10, @manager.config[:max_threads]
124
+ assert_equal true, @manager.config[:thread]
125
+ end
126
+
127
+ test "configuration should have reasonable defaults" do
128
+ assert_equal 1, @manager.config[:sleep_timeout]
129
+ assert @manager.config[:logger].is_a?(Logger)
130
+ assert_equal 10, @manager.config[:max_threads]
131
+ assert_equal false, @manager.config[:thread]
132
+ end
133
+
134
+ describe ':at option' do
135
+ test "once a day at 16:20" do
136
+ @manager.every(1.day, 'myjob', :at => '16:20')
137
+
138
+ assert_wont_run 'jan 1 2010 16:19:59'
139
+ assert_will_run 'jan 1 2010 16:20:00'
140
+ assert_wont_run 'jan 1 2010 16:20:01'
141
+ assert_wont_run 'jan 2 2010 16:19:59'
142
+ assert_will_run 'jan 2 2010 16:20:00'
143
+ end
144
+
145
+ test "twice a day at 16:20 and 18:10" do
146
+ @manager.every(1.day, 'myjob', :at => ['16:20', '18:10'])
147
+
148
+ assert_wont_run 'jan 1 2010 16:19:59'
149
+ assert_will_run 'jan 1 2010 16:20:00'
150
+ assert_wont_run 'jan 1 2010 16:20:01'
151
+
152
+ assert_wont_run 'jan 1 2010 18:09:59'
153
+ assert_will_run 'jan 1 2010 18:10:00'
154
+ assert_wont_run 'jan 1 2010 18:10:01'
155
+ end
156
+ end
157
+
158
+ describe ':tz option' do
159
+ test "should be able to specify a different timezone than local" do
160
+ @manager.every(1.day, 'myjob', :at => '10:00', :tz => 'UTC')
161
+
162
+ assert_wont_run 'jan 1 2010 10:00:00 EST'
163
+ assert_will_run 'jan 1 2010 10:00:00 UTC'
164
+ end
165
+
166
+ test "should be able to specify a different timezone than local for multiple times" do
167
+ @manager.every(1.day, 'myjob', :at => ['10:00', '8:00'], :tz => 'UTC')
168
+
169
+ assert_wont_run 'jan 1 2010 08:00:00 EST'
170
+ assert_will_run 'jan 1 2010 08:00:00 UTC'
171
+ assert_wont_run 'jan 1 2010 10:00:00 EST'
172
+ assert_will_run 'jan 1 2010 10:00:00 UTC'
173
+ end
174
+
175
+ test "should be able to configure a default timezone to use for all events" do
176
+ @manager.configure { |config| config[:tz] = 'UTC' }
177
+ @manager.every(1.day, 'myjob', :at => '10:00')
178
+
179
+ assert_wont_run 'jan 1 2010 10:00:00 EST'
180
+ assert_will_run 'jan 1 2010 10:00:00 UTC'
181
+ end
182
+
183
+ test "should be able to override a default timezone in an event" do
184
+ @manager.configure { |config| config[:tz] = 'UTC' }
185
+ @manager.every(1.day, 'myjob', :at => '10:00', :tz => 'EST')
186
+
187
+ assert_will_run 'jan 1 2010 10:00:00 EST'
188
+ assert_wont_run 'jan 1 2010 10:00:00 UTC'
189
+ end
190
+ end
191
+
192
+ describe ':if option' do
193
+ test ":if true then always run" do
194
+ @manager.every(1.second, 'myjob', :if => lambda { |_| true })
195
+
196
+ assert_will_run 'jan 1 2010 16:20:00'
197
+ end
198
+
199
+ test ":if false then never run" do
200
+ @manager.every(1.second, 'myjob', :if => lambda { |_| false })
201
+
202
+ assert_wont_run 'jan 1 2010 16:20:00'
203
+ end
204
+
205
+ test ":if the first day of month" do
206
+ @manager.every(1.second, 'myjob', :if => lambda { |t| t.day == 1 })
207
+
208
+ assert_will_run 'jan 1 2010 16:20:00'
209
+ assert_wont_run 'jan 2 2010 16:20:00'
210
+ assert_will_run 'feb 1 2010 16:20:00'
211
+ end
212
+
213
+ test ":if it is compared to a time with zone" do
214
+ tz = 'America/Chicago'
215
+ time = Time.utc(2012,5,25,10,00)
216
+ @manager.every(1.second, 'myjob', tz: tz, :if => lambda { |t|
217
+ ((time - 1.hour)..(time + 1.hour)).cover? t
218
+ })
219
+ assert_will_run time
220
+ end
221
+
222
+ test ":if is not callable then raise ArgumentError" do
223
+ assert_raise(ArgumentError) do
224
+ @manager.every(1.second, 'myjob', :if => true)
225
+ end
226
+ end
227
+ end
228
+
229
+ test "should warn about missing jobs upon exhausting threads" do
230
+ logger = Logger.new(StringIO.new)
231
+ @manager.configure do |config|
232
+ config[:max_threads] = 0
233
+ config[:logger] = logger
234
+ end
235
+
236
+ @manager.every(1.minute, 'myjob', :thread => true)
237
+ logger.expects(:error).with("Threads exhausted; skipping myjob")
238
+
239
+ @manager.tick(Time.now)
240
+ end
241
+
242
+ describe "thread option" do
243
+ test "should not use thread by default" do
244
+ event = @manager.every(1.minute, 'myjob')
245
+ assert !event.thread?
246
+ end
247
+
248
+ test "should use thread if thread option is specified with truly value" do
249
+ event = @manager.every(1.minute, 'myjob', :thread => true)
250
+ assert event.thread?
251
+ end
252
+
253
+ test "should use thread if global thread option is set" do
254
+ @manager.configure do |config|
255
+ config[:thread] = true
256
+ end
257
+
258
+ event = @manager.every(1.minute, 'myjob')
259
+ assert event.thread?
260
+ end
261
+
262
+ test "should not use thread if job option overrides global option" do
263
+ @manager.configure do |config|
264
+ config[:thread] = true
265
+ end
266
+
267
+ event = @manager.every(1.minute, 'myjob', :thread => false)
268
+ assert !event.thread?
269
+ end
270
+ end
271
+
272
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: clockwork
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.5
4
+ version: 0.6.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2013-08-04 00:00:00.000000000 Z
13
+ date: 2013-08-06 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: tzinfo
@@ -148,9 +148,15 @@ files:
148
148
  - gemfiles/activesupport3.gemfile
149
149
  - gemfiles/activesupport4.gemfile
150
150
  - lib/clockwork.rb
151
+ - lib/clockwork/at.rb
152
+ - lib/clockwork/event.rb
153
+ - lib/clockwork/manager.rb
154
+ - test/at_test.rb
151
155
  - test/clockwork_test.rb
156
+ - test/manager_test.rb
152
157
  homepage: http://github.com/tomykaira/clockwork
153
- licenses: []
158
+ licenses:
159
+ - MIT
154
160
  post_install_message:
155
161
  rdoc_options: []
156
162
  require_paths:
@@ -163,7 +169,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
163
169
  version: '0'
164
170
  segments:
165
171
  - 0
166
- hash: -871026735
172
+ hash: 1040886093
167
173
  required_rubygems_version: !ruby/object:Gem::Requirement
168
174
  none: false
169
175
  requirements:
@@ -172,7 +178,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
172
178
  version: '0'
173
179
  segments:
174
180
  - 0
175
- hash: -871026735
181
+ hash: 1040886093
176
182
  requirements: []
177
183
  rubyforge_project:
178
184
  rubygems_version: 1.8.23
@@ -180,4 +186,6 @@ signing_key:
180
186
  specification_version: 3
181
187
  summary: A scheduler process to replace cron.
182
188
  test_files:
189
+ - test/at_test.rb
183
190
  - test/clockwork_test.rb
191
+ - test/manager_test.rb