tickwork 0.0.1
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.
- 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
|