tickwork 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/.ruby-gemset +2 -0
- data/.ruby-version +1 -0
- data/.travis.yml +13 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +373 -0
- data/Rakefile +9 -0
- data/example.rb +28 -0
- data/gemfiles/activesupport3.gemfile +10 -0
- data/gemfiles/activesupport4.gemfile +11 -0
- data/lib/tickwork/at.rb +62 -0
- data/lib/tickwork/data_store.rb +28 -0
- data/lib/tickwork/event.rb +83 -0
- data/lib/tickwork/manager.rb +174 -0
- data/lib/tickwork.rb +56 -0
- data/test/at_test.rb +116 -0
- data/test/data_stores/fake_store.rb +21 -0
- data/test/event_test.rb +67 -0
- data/test/manager_test.rb +576 -0
- data/test/null_logger.rb +19 -0
- data/test/tickwork_test.rb +91 -0
- data/tickworkwork.gemspec +28 -0
- metadata +172 -0
@@ -0,0 +1,576 @@
|
|
1
|
+
require File.expand_path('../../lib/tickwork', __FILE__)
|
2
|
+
require File.expand_path('../data_stores/fake_store.rb', __FILE__)
|
3
|
+
require File.expand_path('../null_logger.rb', __FILE__)
|
4
|
+
require "minitest/autorun"
|
5
|
+
require 'mocha/mini_test'
|
6
|
+
require 'time'
|
7
|
+
require 'active_support/time'
|
8
|
+
|
9
|
+
describe Tickwork::Manager do
|
10
|
+
def self.test_order
|
11
|
+
:alpha
|
12
|
+
end
|
13
|
+
before do
|
14
|
+
@manager = Tickwork::Manager.new
|
15
|
+
@manager.configure do |config|
|
16
|
+
config[:data_store] = Tickwork::FakeStore.new
|
17
|
+
config[:logger] = NullLogger.new
|
18
|
+
end
|
19
|
+
class << @manager
|
20
|
+
def log(msg); end
|
21
|
+
end
|
22
|
+
@manager.handler { }
|
23
|
+
end
|
24
|
+
|
25
|
+
def assert_will_run(t)
|
26
|
+
if t.is_a? String
|
27
|
+
t = Time.parse(t)
|
28
|
+
end
|
29
|
+
assert_equal 1, @manager.tick(t).size
|
30
|
+
end
|
31
|
+
|
32
|
+
def assert_wont_run(t)
|
33
|
+
if t.is_a? String
|
34
|
+
t = Time.parse(t)
|
35
|
+
end
|
36
|
+
assert_equal 0, @manager.tick(t).size
|
37
|
+
end
|
38
|
+
|
39
|
+
it "once a minute" do
|
40
|
+
@manager.every(1.minute, 'myjob')
|
41
|
+
|
42
|
+
assert_will_run(t=Time.now)
|
43
|
+
assert_wont_run(t+30)
|
44
|
+
assert_will_run(t+60)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "every three minutes" do
|
48
|
+
@manager.every(3.minutes, 'myjob')
|
49
|
+
|
50
|
+
assert_will_run(t=Time.now)
|
51
|
+
assert_wont_run(t+2*60)
|
52
|
+
assert_will_run(t+3*60)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "once an hour" do
|
56
|
+
@manager.every(1.hour, 'myjob')
|
57
|
+
|
58
|
+
assert_will_run(t=Time.now)
|
59
|
+
assert_wont_run(t+30*60)
|
60
|
+
assert_will_run(t+60*60)
|
61
|
+
end
|
62
|
+
|
63
|
+
it "once a week" do
|
64
|
+
@manager.every(1.week, 'myjob')
|
65
|
+
|
66
|
+
assert_will_run(t=Time.now)
|
67
|
+
assert_wont_run(t+60*60*24*6)
|
68
|
+
assert_will_run(t+60*60*24*7)
|
69
|
+
end
|
70
|
+
|
71
|
+
it "won't drift later and later" do
|
72
|
+
@manager.every(1.hour, 'myjob')
|
73
|
+
|
74
|
+
assert_will_run(Time.parse("10:00:00.5"))
|
75
|
+
assert_wont_run(Time.parse("10:59:59.999"))
|
76
|
+
assert_will_run(Time.parse("11:00:00.0"))
|
77
|
+
end
|
78
|
+
|
79
|
+
it "aborts when no handler defined" do
|
80
|
+
manager = Tickwork::Manager.new
|
81
|
+
assert_raises(Tickwork::Manager::NoHandlerDefined) do
|
82
|
+
manager.every(1.minute, 'myjob')
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
it "aborts when fails to parse" do
|
87
|
+
assert_raises(Tickwork::At::FailedToParse) do
|
88
|
+
@manager.every(1.day, "myjob", :at => "a:bc")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
it "general handler" do
|
93
|
+
$set_me = 0
|
94
|
+
@manager.handler { $set_me = 1 }
|
95
|
+
@manager.every(1.minute, 'myjob')
|
96
|
+
@manager.tick(Time.now)
|
97
|
+
assert_equal 1, $set_me
|
98
|
+
end
|
99
|
+
|
100
|
+
it "event-specific handler" do
|
101
|
+
$set_me = 0
|
102
|
+
@manager.every(1.minute, 'myjob') { $set_me = 2 }
|
103
|
+
@manager.tick(Time.now)
|
104
|
+
|
105
|
+
assert_equal 2, $set_me
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should pass time to the general handler" do
|
109
|
+
received = nil
|
110
|
+
now = Time.now
|
111
|
+
@manager.handler { |job, time| received = time }
|
112
|
+
@manager.every(1.minute, 'myjob')
|
113
|
+
@manager.tick(now)
|
114
|
+
assert_equal now, received
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should pass time to the event-specific handler" do
|
118
|
+
received = nil
|
119
|
+
now = Time.now
|
120
|
+
@manager.every(1.minute, 'myjob') { |job, time| received = time }
|
121
|
+
@manager.tick(now)
|
122
|
+
assert_equal now, received
|
123
|
+
end
|
124
|
+
|
125
|
+
it "exceptions are trapped and logged" do
|
126
|
+
@manager.handler { raise 'boom' }
|
127
|
+
@manager.every(1.minute, 'myjob')
|
128
|
+
|
129
|
+
mocked_logger = MiniTest::Mock.new
|
130
|
+
mocked_logger.expect :error, true, [RuntimeError]
|
131
|
+
@manager.configure { |c| c[:logger] = mocked_logger }
|
132
|
+
@manager.tick(Time.now)
|
133
|
+
mocked_logger.verify
|
134
|
+
end
|
135
|
+
|
136
|
+
it "exceptions still set the last timestamp to avoid spastic error loops" do
|
137
|
+
@manager.handler { raise 'boom' }
|
138
|
+
event = @manager.every(1.minute, 'myjob')
|
139
|
+
@manager.stubs(:log_error)
|
140
|
+
@manager.tick(t = Time.now)
|
141
|
+
assert_equal t, event.last
|
142
|
+
end
|
143
|
+
|
144
|
+
it "should be configurable" do
|
145
|
+
logger = NullLogger.new
|
146
|
+
@manager.configure do |config|
|
147
|
+
config[:logger] = logger
|
148
|
+
config[:max_threads] = 20
|
149
|
+
config[:max_ticks] = 21
|
150
|
+
config[:tick_size] = 59
|
151
|
+
config[:max_catchup] = 3000
|
152
|
+
config[:thread] = true
|
153
|
+
config[:namespace] = 'superhero'
|
154
|
+
end
|
155
|
+
|
156
|
+
assert_equal logger, @manager.config[:logger]
|
157
|
+
assert_equal 20, @manager.config[:max_threads]
|
158
|
+
assert_equal 21, @manager.config[:max_ticks]
|
159
|
+
assert_equal 59, @manager.config[:tick_size]
|
160
|
+
assert_equal 3000, @manager.config[:max_catchup]
|
161
|
+
assert_equal true, @manager.config[:thread]
|
162
|
+
assert_equal 'superhero', @manager.config[:namespace]
|
163
|
+
end
|
164
|
+
|
165
|
+
it "configuration should have reasonable defaults" do
|
166
|
+
@manager = Tickwork::Manager.new
|
167
|
+
assert @manager.config[:logger].is_a?(Logger)
|
168
|
+
assert_equal 10, @manager.config[:max_threads]
|
169
|
+
assert_equal 10, @manager.config[:max_ticks]
|
170
|
+
assert_equal 60, @manager.config[:tick_size]
|
171
|
+
assert_equal 3600, @manager.config[:max_catchup]
|
172
|
+
assert_equal false, @manager.config[:thread]
|
173
|
+
assert_equal '_tickwork_', @manager.config[:namespace]
|
174
|
+
end
|
175
|
+
|
176
|
+
it "config raises exception without a datastore" do
|
177
|
+
@my_manager = Tickwork::Manager.new
|
178
|
+
assert_raises Tickwork::Manager::NoDataStoreDefined do
|
179
|
+
@my_manager.configure do |config|
|
180
|
+
config[:tick_size] = 10
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
it "run raises exception without a datastore" do
|
186
|
+
@my_manager = Tickwork::Manager.new
|
187
|
+
assert_raises Tickwork::Manager::NoDataStoreDefined do
|
188
|
+
@my_manager.run
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
describe ':at option' do
|
193
|
+
it "once a day at 16:20" do
|
194
|
+
@manager.every(1.day, 'myjob', :at => '16:20')
|
195
|
+
|
196
|
+
assert_wont_run 'jan 1 2010 16:19:59'
|
197
|
+
assert_will_run 'jan 1 2010 16:20:00'
|
198
|
+
assert_wont_run 'jan 1 2010 16:20:01'
|
199
|
+
assert_wont_run 'jan 2 2010 16:19:59'
|
200
|
+
assert_will_run 'jan 2 2010 16:20:00'
|
201
|
+
end
|
202
|
+
|
203
|
+
it "twice a day at 16:20 and 18:10" do
|
204
|
+
@manager.every(1.day, 'myjob', :at => ['16:20', '18:10'])
|
205
|
+
|
206
|
+
assert_wont_run 'jan 1 2010 16:19:59'
|
207
|
+
assert_will_run 'jan 1 2010 16:20:00'
|
208
|
+
assert_wont_run 'jan 1 2010 16:20:01'
|
209
|
+
|
210
|
+
assert_wont_run 'jan 1 2010 18:09:59'
|
211
|
+
assert_will_run 'jan 1 2010 18:10:00'
|
212
|
+
assert_wont_run 'jan 1 2010 18:10:01'
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
describe ':tz option' do
|
217
|
+
it "time zone is not set by default" do
|
218
|
+
assert @manager.config[:tz].nil?
|
219
|
+
end
|
220
|
+
|
221
|
+
it "should be able to specify a different timezone than local" do
|
222
|
+
@manager.every(1.day, 'myjob', :at => '10:00', :tz => 'UTC')
|
223
|
+
|
224
|
+
assert_wont_run 'jan 1 2010 10:00:00 EST'
|
225
|
+
assert_will_run 'jan 1 2010 10:00:00 UTC'
|
226
|
+
end
|
227
|
+
|
228
|
+
it "should be able to specify a different timezone than local for multiple times" do
|
229
|
+
@manager.every(1.day, 'myjob', :at => ['10:00', '8:00'], :tz => 'UTC')
|
230
|
+
|
231
|
+
assert_wont_run 'jan 1 2010 08:00:00 EST'
|
232
|
+
assert_will_run 'jan 1 2010 08:00:00 UTC'
|
233
|
+
assert_wont_run 'jan 1 2010 10:00:00 EST'
|
234
|
+
assert_will_run 'jan 1 2010 10:00:00 UTC'
|
235
|
+
end
|
236
|
+
|
237
|
+
it "should be able to configure a default timezone to use for all events" do
|
238
|
+
@manager.configure { |config| config[:tz] = 'UTC' }
|
239
|
+
@manager.every(1.day, 'myjob', :at => '10:00')
|
240
|
+
|
241
|
+
assert_wont_run 'jan 1 2010 10:00:00 EST'
|
242
|
+
assert_will_run 'jan 1 2010 10:00:00 UTC'
|
243
|
+
end
|
244
|
+
|
245
|
+
it "should be able to override a default timezone in an event" do
|
246
|
+
@manager.configure { |config| config[:tz] = 'UTC' }
|
247
|
+
@manager.every(1.day, 'myjob', :at => '10:00', :tz => 'EST')
|
248
|
+
|
249
|
+
assert_will_run 'jan 1 2010 10:00:00 EST'
|
250
|
+
assert_wont_run 'jan 1 2010 10:00:00 UTC'
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
describe ':if option' do
|
255
|
+
it ":if true then always run" do
|
256
|
+
@manager.every(1.second, 'myjob', :if => lambda { |_| true })
|
257
|
+
|
258
|
+
assert_will_run 'jan 1 2010 16:20:00'
|
259
|
+
end
|
260
|
+
|
261
|
+
it ":if false then never run" do
|
262
|
+
@manager.every(1.second, 'myjob', :if => lambda { |_| false })
|
263
|
+
|
264
|
+
assert_wont_run 'jan 1 2010 16:20:00'
|
265
|
+
end
|
266
|
+
|
267
|
+
it ":if the first day of month" do
|
268
|
+
@manager.every(1.second, 'myjob', :if => lambda { |t| t.day == 1 })
|
269
|
+
|
270
|
+
assert_will_run 'jan 1 2010 16:20:00'
|
271
|
+
assert_wont_run 'jan 2 2010 16:20:00'
|
272
|
+
assert_will_run 'feb 1 2010 16:20:00'
|
273
|
+
end
|
274
|
+
|
275
|
+
it ":if it is compared to a time with zone" do
|
276
|
+
tz = 'America/Chicago'
|
277
|
+
time = Time.utc(2012,5,25,10,00)
|
278
|
+
@manager.every(1.second, 'myjob', tz: tz, :if => lambda { |t|
|
279
|
+
((time - 1.hour)..(time + 1.hour)).cover? t
|
280
|
+
})
|
281
|
+
assert_will_run time
|
282
|
+
end
|
283
|
+
|
284
|
+
it ":if is not callable then raise ArgumentError" do
|
285
|
+
assert_raises(ArgumentError) do
|
286
|
+
@manager.every(1.second, 'myjob', :if => true)
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
describe "max_threads" do
|
292
|
+
it "should warn when an event tries to generate threads more than max_threads" do
|
293
|
+
logger = NullLogger.new
|
294
|
+
@manager.configure do |config|
|
295
|
+
config[:max_threads] = 1
|
296
|
+
config[:logger] = logger
|
297
|
+
end
|
298
|
+
|
299
|
+
@manager.every(1.minute, 'myjob1', :thread => true) { sleep 2 }
|
300
|
+
@manager.every(1.minute, 'myjob2', :thread => true) { sleep 2 }
|
301
|
+
logger.expects(:error).with("Threads exhausted; skipping myjob2")
|
302
|
+
|
303
|
+
@manager.tick(Time.now)
|
304
|
+
end
|
305
|
+
|
306
|
+
it "should not warn when thread is managed by others" do
|
307
|
+
begin
|
308
|
+
t = Thread.new { sleep 5 }
|
309
|
+
logger = Logger.new(StringIO.new)
|
310
|
+
@manager.configure do |config|
|
311
|
+
config[:max_threads] = 1
|
312
|
+
config[:logger] = logger
|
313
|
+
end
|
314
|
+
|
315
|
+
@manager.every(1.minute, 'myjob', :thread => true)
|
316
|
+
logger.expects(:error).never
|
317
|
+
|
318
|
+
@manager.tick(Time.now)
|
319
|
+
ensure
|
320
|
+
t.kill
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
describe "callbacks" do
|
326
|
+
it "should not accept unknown callback name" do
|
327
|
+
assert_raises(RuntimeError, "Unsupported callback unknown_callback") do
|
328
|
+
@manager.on(:unknown_callback) do
|
329
|
+
true
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
it "should run before_tick callback once on tick" do
|
335
|
+
counter = 0
|
336
|
+
@manager.on(:before_tick) do
|
337
|
+
counter += 1
|
338
|
+
end
|
339
|
+
@manager.tick
|
340
|
+
assert_equal 1, counter
|
341
|
+
end
|
342
|
+
|
343
|
+
it "should not run events if before_tick returns false" do
|
344
|
+
@manager.on(:before_tick) do
|
345
|
+
false
|
346
|
+
end
|
347
|
+
@manager.every(1.second, 'myjob') { raise "should not run" }
|
348
|
+
@manager.tick
|
349
|
+
end
|
350
|
+
|
351
|
+
it "should run before_run twice if two events are registered" do
|
352
|
+
counter = 0
|
353
|
+
@manager.on(:before_run) do
|
354
|
+
counter += 1
|
355
|
+
end
|
356
|
+
@manager.every(1.second, 'myjob')
|
357
|
+
@manager.every(1.second, 'myjob2')
|
358
|
+
@manager.tick
|
359
|
+
assert_equal 2, counter
|
360
|
+
end
|
361
|
+
|
362
|
+
it "should run even jobs only" do
|
363
|
+
counter = 0
|
364
|
+
ran = false
|
365
|
+
@manager.on(:before_run) do
|
366
|
+
counter += 1
|
367
|
+
counter % 2 == 0
|
368
|
+
end
|
369
|
+
@manager.every(1.second, 'myjob') { raise "should not ran" }
|
370
|
+
@manager.every(1.second, 'myjob2') { ran = true }
|
371
|
+
@manager.tick
|
372
|
+
assert ran
|
373
|
+
end
|
374
|
+
|
375
|
+
it "should run after_run callback for each event" do
|
376
|
+
counter = 0
|
377
|
+
@manager.on(:after_run) do
|
378
|
+
counter += 1
|
379
|
+
end
|
380
|
+
@manager.every(1.second, 'myjob')
|
381
|
+
@manager.every(1.second, 'myjob2')
|
382
|
+
@manager.tick
|
383
|
+
assert_equal 2, counter
|
384
|
+
end
|
385
|
+
|
386
|
+
it "should run after_tick callback once" do
|
387
|
+
counter = 0
|
388
|
+
@manager.on(:after_tick) do
|
389
|
+
counter += 1
|
390
|
+
end
|
391
|
+
@manager.tick
|
392
|
+
assert_equal 1, counter
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
it "should start from last tick" do
|
397
|
+
@manager.configure do |config|
|
398
|
+
config[:tick_size] = 1
|
399
|
+
config[:max_ticks] = 1
|
400
|
+
end
|
401
|
+
last = Time.now.to_i - 1000
|
402
|
+
@manager.data_store.set(@manager.data_store_key, last)
|
403
|
+
@manager.expects(:tick).with(last + 1).then.returns
|
404
|
+
@manager.run
|
405
|
+
end
|
406
|
+
|
407
|
+
it "should tick to max_ticks" do
|
408
|
+
@manager.configure do |config|
|
409
|
+
config[:tick_size] = 1
|
410
|
+
config[:max_ticks] = 3
|
411
|
+
end
|
412
|
+
last = Time.now.to_i - 1000
|
413
|
+
@manager.data_store.set(@manager.data_store_key, last)
|
414
|
+
@manager.expects(:tick).with(last + 1).then.returns
|
415
|
+
@manager.expects(:tick).with(last + 2).then.returns
|
416
|
+
@manager.expects(:tick).with(last + 3).then.returns
|
417
|
+
@manager.run
|
418
|
+
end
|
419
|
+
|
420
|
+
it "should tick by tick size" do
|
421
|
+
@manager.configure do |config|
|
422
|
+
config[:tick_size] = 2
|
423
|
+
config[:max_ticks] = 3
|
424
|
+
end
|
425
|
+
last = Time.now.to_i - 1000
|
426
|
+
@manager.data_store.set(@manager.data_store_key, last)
|
427
|
+
@manager.expects(:tick).with(last + 2).then.returns
|
428
|
+
@manager.expects(:tick).with(last + 4).then.returns
|
429
|
+
@manager.expects(:tick).with(last + 6).then.returns
|
430
|
+
@manager.run
|
431
|
+
end
|
432
|
+
|
433
|
+
it "should not tick into the future" do
|
434
|
+
@manager.configure do |config|
|
435
|
+
config[:tick_size] = 10
|
436
|
+
config[:max_ticks] = 3
|
437
|
+
end
|
438
|
+
last = Time.now.to_i - 1
|
439
|
+
@manager.data_store.set(@manager.data_store_key, last)
|
440
|
+
module Failure
|
441
|
+
def tick
|
442
|
+
raise "don't call me"
|
443
|
+
end
|
444
|
+
end
|
445
|
+
@manager.extend Failure
|
446
|
+
@manager.run
|
447
|
+
end
|
448
|
+
|
449
|
+
it "should save the last tick time" do
|
450
|
+
@manager.configure do |config|
|
451
|
+
config[:tick_size] = 10
|
452
|
+
config[:max_ticks] = 1
|
453
|
+
end
|
454
|
+
last = Time.now.to_i - 1000
|
455
|
+
@manager.data_store.set(@manager.data_store_key, last)
|
456
|
+
@manager.expects(:tick).with(last + 10).then.returns
|
457
|
+
@manager.run
|
458
|
+
assert_equal (last + 10), @manager.data_store.get(@manager.data_store_key)
|
459
|
+
end
|
460
|
+
|
461
|
+
it "should tick from now if no last time" do
|
462
|
+
@manager.configure do |config|
|
463
|
+
config[:tick_size] = 10
|
464
|
+
config[:max_ticks] = 1
|
465
|
+
end
|
466
|
+
@manager.expects(:tick).with(Time.now.to_i).then.returns
|
467
|
+
@manager.run
|
468
|
+
end
|
469
|
+
|
470
|
+
it "should be saving event last run times" do
|
471
|
+
@manager.configure do |config|
|
472
|
+
config[:tick_size] = 10
|
473
|
+
config[:max_ticks] = 1
|
474
|
+
end
|
475
|
+
@manager.every(1.minute, 'myjob')
|
476
|
+
assert_equal 0, @manager.config[:data_store].size
|
477
|
+
@manager.run
|
478
|
+
assert_equal 2, @manager.config[:data_store].size
|
479
|
+
assert_equal false, @manager.config[:data_store].get('_tickwork_myjob').nil?
|
480
|
+
end
|
481
|
+
|
482
|
+
it "should start from max catchup when last is further in the past" do
|
483
|
+
@manager.configure do |config|
|
484
|
+
config[:tick_size] = 1
|
485
|
+
config[:max_ticks] = 1
|
486
|
+
config[:max_catchup] = 1800
|
487
|
+
end
|
488
|
+
|
489
|
+
@manager.every(1.minute, 'myjob')
|
490
|
+
last = Time.now.to_i - 3600
|
491
|
+
@manager.data_store.set(@manager.data_store_key, last)
|
492
|
+
@manager.expects(:tick).with(Time.now.to_i - 1800).then.returns
|
493
|
+
@manager.run
|
494
|
+
end
|
495
|
+
|
496
|
+
it "0 should disable max catchup" do
|
497
|
+
@manager.configure do |config|
|
498
|
+
config[:tick_size] = 1
|
499
|
+
config[:max_ticks] = 1
|
500
|
+
config[:max_catchup] = 0
|
501
|
+
end
|
502
|
+
|
503
|
+
@manager.every(1.minute, 'myjob')
|
504
|
+
last = Time.now.to_i - 36000
|
505
|
+
@manager.data_store.set(@manager.data_store_key, last)
|
506
|
+
@manager.expects(:tick).with(Time.now.to_i - 36000 + 1).then.returns
|
507
|
+
@manager.run
|
508
|
+
end
|
509
|
+
|
510
|
+
it "0 should disable max catchup" do
|
511
|
+
@manager.configure do |config|
|
512
|
+
config[:tick_size] = 1
|
513
|
+
config[:max_ticks] = 1
|
514
|
+
config[:max_catchup] = nil
|
515
|
+
end
|
516
|
+
|
517
|
+
@manager.every(1.minute, 'myjob')
|
518
|
+
last = Time.now.to_i - 36000
|
519
|
+
@manager.data_store.set(@manager.data_store_key, last)
|
520
|
+
@manager.expects(:tick).with(Time.now.to_i - 36000 + 1).then.returns
|
521
|
+
@manager.run
|
522
|
+
end
|
523
|
+
|
524
|
+
it "should return the last time it ran" do
|
525
|
+
@manager.configure do |config|
|
526
|
+
config[:tick_size] = 10
|
527
|
+
config[:max_ticks] = 2
|
528
|
+
end
|
529
|
+
last = Time.now.to_i - 1000
|
530
|
+
@manager.data_store.set(@manager.data_store_key, last)
|
531
|
+
@manager.expects(:tick).with(last + 10).then.returns
|
532
|
+
@manager.expects(:tick).with(last + 20).then.returns
|
533
|
+
assert_equal last + 20, @manager.run
|
534
|
+
end
|
535
|
+
|
536
|
+
it "should clear it's datastore on #clear!" do
|
537
|
+
@manager.data_store.set(@manager.data_store_key, "10")
|
538
|
+
@manager.clear!
|
539
|
+
assert_equal nil, @manager.data_store.get(@manager.data_store_key)
|
540
|
+
end
|
541
|
+
|
542
|
+
describe 'error_handler' do
|
543
|
+
before do
|
544
|
+
@errors = []
|
545
|
+
@manager.error_handler do |e|
|
546
|
+
@errors << e
|
547
|
+
end
|
548
|
+
|
549
|
+
# block error log
|
550
|
+
@string_io = StringIO.new
|
551
|
+
@manager.configure do |config|
|
552
|
+
config[:logger] = Logger.new(@string_io)
|
553
|
+
end
|
554
|
+
@manager.every(1.second, 'myjob') { raise 'it error' }
|
555
|
+
end
|
556
|
+
|
557
|
+
it 'registered error_handler handles error from event' do
|
558
|
+
@manager.tick
|
559
|
+
assert_equal ['it error'], @errors.map(&:message)
|
560
|
+
end
|
561
|
+
|
562
|
+
it 'error is notified to logger and handler' do
|
563
|
+
@manager.tick
|
564
|
+
assert @string_io.string.include?('it error')
|
565
|
+
end
|
566
|
+
|
567
|
+
it 'error in handler will NOT be suppressed' do
|
568
|
+
@manager.error_handler do |e|
|
569
|
+
raise e.message + ' re-raised'
|
570
|
+
end
|
571
|
+
assert_raises(RuntimeError, 'it error re-raised') do
|
572
|
+
@manager.tick
|
573
|
+
end
|
574
|
+
end
|
575
|
+
end
|
576
|
+
end
|
data/test/null_logger.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# https://github.com/karafka/null-logger/blob/master/lib/null_logger.rb
|
2
|
+
# under MIT license as of 5/9/2016
|
3
|
+
|
4
|
+
# Null logger class
|
5
|
+
# Is used when logger is not defined
|
6
|
+
class NullLogger
|
7
|
+
# Possible log levels from ruby Logger::Severity class
|
8
|
+
LOG_LEVELS = %w( unknown fatal error warn info debug ).freeze
|
9
|
+
|
10
|
+
# Returns nil for any method call from LOG_LEVELS array
|
11
|
+
# Instead raise NoMethodError
|
12
|
+
# @example:
|
13
|
+
# NullLogger.new.fatal -> return nil
|
14
|
+
# NullLogger.new.wrong_method -> raise NoMethodError
|
15
|
+
def method_missing(method_name, *args, &block)
|
16
|
+
return nil if LOG_LEVELS.include?(method_name.to_s)
|
17
|
+
super(method_name, *args, &block)
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require File.expand_path('../../lib/tickwork', __FILE__)
|
2
|
+
require File.expand_path('../data_stores/fake_store.rb', __FILE__)
|
3
|
+
require 'minitest/autorun'
|
4
|
+
require 'mocha/mini_test'
|
5
|
+
|
6
|
+
describe Tickwork do
|
7
|
+
def self.test_order
|
8
|
+
:alpha
|
9
|
+
end
|
10
|
+
before do
|
11
|
+
@log_output = StringIO.new
|
12
|
+
Tickwork.configure do |config|
|
13
|
+
config[:logger] = Logger.new(@log_output)
|
14
|
+
#config[:logger] = Logger.new(STDOUT)
|
15
|
+
config[:data_store] = Tickwork::FakeStore.new
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
after do
|
20
|
+
Tickwork.clear!
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should run events with configured logger' do
|
24
|
+
run = false
|
25
|
+
Tickwork.handler do |job|
|
26
|
+
run = job == 'myjob'
|
27
|
+
end
|
28
|
+
Tickwork.every(1.minute, 'myjob')
|
29
|
+
# don't know why this doesn't work with while but did with loop
|
30
|
+
#Tickwork.manager.expects(:while).yields.then.returns
|
31
|
+
Tickwork.run
|
32
|
+
|
33
|
+
assert run
|
34
|
+
assert @log_output.string.include?('Triggering')
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should log event correctly' do
|
38
|
+
run = false
|
39
|
+
Tickwork.handler do |job|
|
40
|
+
run = job == 'an event'
|
41
|
+
end
|
42
|
+
Tickwork.every(1.minute, 'an event')
|
43
|
+
# Tickwork.manager.expects(:while).yields.then.returns
|
44
|
+
Tickwork.run
|
45
|
+
assert run
|
46
|
+
assert @log_output.string.include?("Triggering 'an event'")
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should pass event without modification to handler' do
|
50
|
+
event_object = 'myEvent'
|
51
|
+
run = false
|
52
|
+
Tickwork.handler do |job|
|
53
|
+
run = job == event_object
|
54
|
+
end
|
55
|
+
Tickwork.every(1.minute, event_object)
|
56
|
+
# Tickwork.manager.expects(:while).yields.then.returns
|
57
|
+
Tickwork.run
|
58
|
+
assert run
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'should not run anything after reset' do
|
62
|
+
Tickwork.every(1.minute, 'myjob') { }
|
63
|
+
Tickwork.clear!
|
64
|
+
Tickwork.configure do |config|
|
65
|
+
config[:sleep_timeout] = 0
|
66
|
+
config[:logger] = Logger.new(@log_output)
|
67
|
+
config[:data_store] = Tickwork::FakeStore.new
|
68
|
+
end
|
69
|
+
# Tickwork.manager.expects(:while).yields.then.returns
|
70
|
+
Tickwork.run
|
71
|
+
assert @log_output.string.include?('0 events')
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'should pass all arguments to every' do
|
75
|
+
Tickwork.every(1.second, 'myjob', if: lambda { |_| false }) { }
|
76
|
+
# Tickwork.manager.expects(:while).yields.then.returns
|
77
|
+
Tickwork.run
|
78
|
+
assert @log_output.string.include?('1 events')
|
79
|
+
assert !@log_output.string.include?('Triggering')
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'support module re-open style' do
|
83
|
+
$called = false
|
84
|
+
module ::Tickwork
|
85
|
+
every(1.second, 'myjob') { $called = true }
|
86
|
+
end
|
87
|
+
# Tickwork.manager.expects(:while).yields.then.returns
|
88
|
+
Tickwork.run
|
89
|
+
assert $called
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "tickwork"
|
3
|
+
s.version = "0.0.1"
|
4
|
+
|
5
|
+
s.authors = ["John Hinnegan"]
|
6
|
+
s.license = 'MIT'
|
7
|
+
s.description = "A fork of clockwork. Under development."
|
8
|
+
s.email = ["tickwork@johnhinnegan.com"]
|
9
|
+
s.extra_rdoc_files = [
|
10
|
+
"README.md"
|
11
|
+
]
|
12
|
+
s.homepage = "http://github.com/softwaregravy/clockwork"
|
13
|
+
s.summary = "A scheduling library"
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split($/)
|
16
|
+
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
s.add_dependency(%q<tzinfo>)
|
21
|
+
s.add_dependency(%q<activesupport>)
|
22
|
+
|
23
|
+
s.add_development_dependency "bundler", "~> 1.3"
|
24
|
+
s.add_development_dependency "rake"
|
25
|
+
s.add_development_dependency "daemons"
|
26
|
+
s.add_development_dependency "minitest", "~> 5.8"
|
27
|
+
s.add_development_dependency "mocha"
|
28
|
+
end
|