clockwork 0.5.5 → 0.6.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/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