clockwork 0.7.7 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,283 @@
1
+ require 'contest'
2
+ require 'mocha/setup'
3
+ require 'time'
4
+ require 'active_support/time'
5
+
6
+ require_relative '../../lib/clockwork'
7
+ require_relative '../../lib/clockwork/database_events'
8
+ require_relative 'test_helpers'
9
+
10
+ module DatabaseEvents
11
+
12
+ class SyncPerformerTest < Test::Unit::TestCase
13
+
14
+ setup do
15
+ @now = Time.now
16
+ DatabaseEventModelClass.delete_all
17
+ DatabaseEventModelClass2.delete_all
18
+
19
+ Clockwork.manager = @manager = Clockwork::DatabaseEvents::Manager.new
20
+ class << @manager
21
+ def log(msg); end # silence log output
22
+ end
23
+ end
24
+
25
+ describe "setup" do
26
+ setup do
27
+ @subject = Clockwork::DatabaseEvents::SyncPerformer
28
+ end
29
+
30
+ describe "arguments" do
31
+ def test_does_not_raise_error_with_valid_arguments
32
+ @subject.setup(model: DatabaseEventModelClass, every: 1.minute) {}
33
+ end
34
+
35
+ def test_raises_argument_error_if_model_is_not_set
36
+ error = assert_raises KeyError do
37
+ @subject.setup(every: 1.minute) {}
38
+ end
39
+ assert_equal error.message, ":model must be set to the model class"
40
+ end
41
+
42
+ def test_raises_argument_error_if_every_is_not_set
43
+ error = assert_raises KeyError do
44
+ @subject.setup(model: DatabaseEventModelClass) {}
45
+ end
46
+ assert_equal error.message, ":every must be set to the database sync frequency"
47
+ end
48
+ end
49
+
50
+ context "when database reload frequency is greater than model frequency period" do
51
+ setup do
52
+ @events_run = []
53
+ @sync_frequency = 1.minute
54
+ end
55
+
56
+ def test_fetches_and_registers_event_from_database
57
+ DatabaseEventModelClass.create(:frequency => 10)
58
+ setup_sync(model: DatabaseEventModelClass, :every => @sync_frequency, :events_run => @events_run)
59
+
60
+ tick_at(@now, :and_every_second_for => 1.second)
61
+
62
+ assert_equal ["DatabaseEventModelClass:1"], @events_run
63
+ end
64
+
65
+ def test_multiple_events_from_database_can_be_registered
66
+ DatabaseEventModelClass.create(:frequency => 10)
67
+ DatabaseEventModelClass.create(:frequency => 10)
68
+ setup_sync(model: DatabaseEventModelClass, :every => @sync_frequency, :events_run => @events_run)
69
+
70
+ tick_at(@now, :and_every_second_for => 1.second)
71
+
72
+ assert_equal ["DatabaseEventModelClass:1", "DatabaseEventModelClass:2"], @events_run
73
+ end
74
+
75
+ def test_event_from_database_does_not_run_again_before_frequency_specified_in_database
76
+ model = DatabaseEventModelClass.create(:frequency => 10)
77
+ setup_sync(model: DatabaseEventModelClass, :every => @sync_frequency, :events_run => @events_run)
78
+
79
+ tick_at(@now, :and_every_second_for => model.frequency - 1.second)
80
+ assert_equal 1, @events_run.length
81
+ end
82
+
83
+ def test_event_from_database_runs_repeatedly_with_frequency_specified_in_database
84
+ model = DatabaseEventModelClass.create(:frequency => 10)
85
+ setup_sync(model: DatabaseEventModelClass, :every => @sync_frequency, :events_run => @events_run)
86
+
87
+ tick_at(@now, :and_every_second_for => (2 * model.frequency) + 1.second)
88
+
89
+ assert_equal 3, @events_run.length
90
+ end
91
+
92
+ def test_reloaded_events_from_database_run_repeatedly
93
+ model = DatabaseEventModelClass.create(:frequency => 10)
94
+ setup_sync(model: DatabaseEventModelClass, :every => @sync_frequency, :events_run => @events_run)
95
+
96
+ tick_at(@now, :and_every_second_for => @sync_frequency - 1)
97
+
98
+ model.update(:name => "DatabaseEventModelClass:1:Reloaded")
99
+
100
+ tick_at(@now + @sync_frequency, :and_every_second_for => model.frequency * 2)
101
+
102
+ assert_equal ["DatabaseEventModelClass:1:Reloaded", "DatabaseEventModelClass:1:Reloaded"], @events_run[-2..-1]
103
+ end
104
+
105
+ def test_reloading_events_from_database_with_modified_frequency_will_run_with_new_frequency
106
+ model = DatabaseEventModelClass.create(:frequency => 10)
107
+ setup_sync(model: DatabaseEventModelClass, :every => @sync_frequency, :events_run => @events_run)
108
+
109
+ tick_at(@now, :and_every_second_for => @sync_frequency - 1.second)
110
+ model.update(:frequency => 5)
111
+ tick_at(@now + @sync_frequency, :and_every_second_for => 6.seconds)
112
+
113
+ # model runs at: 1, 11, 21, 31, 41, 51 (6 runs)
114
+ # database sync happens at: 60
115
+ # modified model runs at: 61 (next tick after reload) and then 66 (2 runs)
116
+ assert_equal 8, @events_run.length
117
+ end
118
+
119
+ def test_stops_running_deleted_events_from_database
120
+ model = DatabaseEventModelClass.create(:frequency => 10)
121
+ setup_sync(model: DatabaseEventModelClass, :every => @sync_frequency, :events_run => @events_run)
122
+
123
+ tick_at(@now, :and_every_second_for => (@sync_frequency - 1.second))
124
+ before = @events_run.dup
125
+ model.delete!
126
+ tick_at(@now + @sync_frequency, :and_every_second_for => @sync_frequency)
127
+ after = @events_run
128
+
129
+ assert_equal before, after
130
+ end
131
+
132
+ def test_event_from_database_with_edited_name_switches_to_new_name
133
+ model = DatabaseEventModelClass.create(:frequency => 10.seconds)
134
+ setup_sync(model: DatabaseEventModelClass, :every => @sync_frequency, :events_run => @events_run)
135
+
136
+ tick_at @now, :and_every_second_for => @sync_frequency - 1.second
137
+ @events_run.clear
138
+ model.update(:name => "DatabaseEventModelClass:1_modified")
139
+ tick_at @now + @sync_frequency, :and_every_second_for => (model.frequency * 2)
140
+
141
+ assert_equal ["DatabaseEventModelClass:1_modified", "DatabaseEventModelClass:1_modified"], @events_run
142
+ end
143
+
144
+ def test_event_from_database_with_edited_frequency_switches_to_new_frequency
145
+ model = DatabaseEventModelClass.create(:frequency => 10)
146
+ setup_sync(model: DatabaseEventModelClass, :every => @sync_frequency, :events_run => @events_run)
147
+
148
+ tick_at @now, :and_every_second_for => @sync_frequency - 1.second
149
+ @events_run.clear
150
+ model.update(:frequency => 30)
151
+ tick_at @now + @sync_frequency, :and_every_second_for => @sync_frequency - 1.seconds
152
+
153
+ assert_equal 2, @events_run.length
154
+ end
155
+
156
+ def test_event_from_database_with_edited_at_runs_at_new_at
157
+ model = DatabaseEventModelClass.create(:frequency => 1.day, :at => '10:30')
158
+ setup_sync(model: DatabaseEventModelClass, :every => @sync_frequency, :events_run => @events_run)
159
+
160
+ assert_will_run 'jan 1 2010 10:30:00'
161
+ assert_wont_run 'jan 1 2010 09:30:00'
162
+
163
+ model.update(:at => '09:30')
164
+ tick_at @now, :and_every_second_for => @sync_frequency + 1.second
165
+
166
+ assert_will_run 'jan 1 2010 09:30:00'
167
+ assert_wont_run 'jan 1 2010 10:30:00'
168
+ end
169
+
170
+ def test_daily_event_from_database_with_at_should_only_run_once
171
+ DatabaseEventModelClass.create(:frequency => 1.day, :at => next_minute(@now).strftime('%H:%M'))
172
+ setup_sync(model: DatabaseEventModelClass, :every => @sync_frequency, :events_run => @events_run)
173
+
174
+ # tick from now, though specified :at time
175
+ tick_at(@now, :and_every_second_for => (2 * @sync_frequency) + 1.second)
176
+
177
+ assert_equal 1, @events_run.length
178
+ end
179
+
180
+ def test_event_from_databse_with_comma_separated_at_leads_to_multiple_event_ats
181
+ DatabaseEventModelClass.create(:frequency => 1.day, :at => '16:20, 18:10')
182
+ setup_sync(model: DatabaseEventModelClass, :every => @sync_frequency, :events_run => @events_run)
183
+
184
+ tick_at @now, :and_every_second_for => 1.second
185
+
186
+ assert_wont_run 'jan 1 2010 16:19:59'
187
+ assert_will_run 'jan 1 2010 16:20:00'
188
+ assert_wont_run 'jan 1 2010 16:20:01'
189
+
190
+ assert_wont_run 'jan 1 2010 18:09:59'
191
+ assert_will_run 'jan 1 2010 18:10:00'
192
+ assert_wont_run 'jan 1 2010 18:10:01'
193
+ end
194
+
195
+ def test_syncing_multiple_database_models_works
196
+ DatabaseEventModelClass.create(:frequency => 10)
197
+ setup_sync(model: DatabaseEventModelClass, :every => @sync_frequency, :events_run => @events_run)
198
+
199
+ DatabaseEventModelClass2.create(:frequency => 10)
200
+ setup_sync(model: DatabaseEventModelClass2, :every => @sync_frequency, :events_run => @events_run)
201
+
202
+ tick_at(@now, :and_every_second_for => 1.second)
203
+
204
+ assert_equal ["DatabaseEventModelClass:1", "DatabaseEventModelClass2:1"], @events_run
205
+ end
206
+ end
207
+
208
+ context "when database reload frequency is less than model frequency period" do
209
+ setup do
210
+ @events_run = []
211
+ end
212
+
213
+ def test_the_event_only_runs_once_within_the_model_frequency_period
214
+ DatabaseEventModelClass.create(:frequency => 5.minutes)
215
+ setup_sync(model: DatabaseEventModelClass, :every => 1.minute, :events_run => @events_run)
216
+
217
+ tick_at(@now, :and_every_second_for => 5.minutes)
218
+
219
+ assert_equal 1, @events_run.length
220
+ end
221
+ end
222
+
223
+ context "with database event with :at as empty string" do
224
+ setup do
225
+ @events_run = []
226
+
227
+ DatabaseEventModelClass.create(:frequency => 10)
228
+ setup_sync(model: DatabaseEventModelClass, :every => 1.minute, :events_run => @events_run)
229
+ end
230
+
231
+ def test_it_does_not_raise_an_error
232
+ begin
233
+ tick_at(Time.now, :and_every_second_for => 10.seconds)
234
+ rescue => e
235
+ assert false, "Raised an error: #{e.message}"
236
+ end
237
+ end
238
+
239
+ def test_the_event_runs
240
+ begin
241
+ tick_at(Time.now, :and_every_second_for => 10.seconds)
242
+ rescue => e
243
+ end
244
+ assert_equal 1, @events_run.length
245
+ end
246
+ end
247
+
248
+ context "with task that respond to `tz`" do
249
+ setup do
250
+ @events_run = []
251
+ @utc_time_now = Time.now.utc
252
+
253
+ DatabaseEventModelClass.create(:frequency => 1.days, :at => @utc_time_now.strftime('%H:%M'), :tz => 'America/Montreal')
254
+ setup_sync(model: DatabaseEventModelClass, :every => 1.minute, :events_run => @events_run)
255
+ end
256
+
257
+ def test_it_does_not_raise_an_error
258
+ begin
259
+ tick_at(@utc_time_now, :and_every_second_for => 10.seconds)
260
+ rescue => e
261
+ assert false, "Raised an error: #{e.message}"
262
+ end
263
+ end
264
+
265
+ def test_it_do_not_runs_the_task_as_utc
266
+ begin
267
+ tick_at(@utc_time_now, :and_every_second_for => 3.hours)
268
+ rescue => e
269
+ end
270
+ assert_equal 0, @events_run.length
271
+ end
272
+
273
+ def test_it_does_runs_the_task_as_est
274
+ begin
275
+ tick_at(@utc_time_now, :and_every_second_for => 5.hours)
276
+ rescue => e
277
+ end
278
+ assert_equal 1, @events_run.length
279
+ end
280
+ end
281
+ end
282
+ end
283
+ end
@@ -0,0 +1,101 @@
1
+ def setup_sync(options={})
2
+ model_class = options.fetch(:model) { raise KeyError, ":model must be set to the model class" }
3
+ frequency = options.fetch(:every) { raise KeyError, ":every must be set to the database sync frequency" }
4
+ events_run = options.fetch(:events_run) { raise KeyError, ":events_run must be provided"}
5
+
6
+ Clockwork::DatabaseEvents::SyncPerformer.setup model: model_class, every: frequency do |model|
7
+ events_run << model.name
8
+ end
9
+ end
10
+
11
+ def assert_will_run(t)
12
+ assert_equal 1, @manager.tick(normalize_time(t)).size
13
+ end
14
+
15
+ def assert_wont_run(t)
16
+ assert_equal 0, @manager.tick(normalize_time(t)).size
17
+ end
18
+
19
+ def tick_at(now = Time.now, options = {})
20
+ seconds_to_tick_for = options[:and_every_second_for] || 0
21
+ number_of_ticks = 1 + seconds_to_tick_for
22
+ number_of_ticks.times{|i| @manager.tick(now + i) }
23
+ end
24
+
25
+ def next_minute(now = Time.now)
26
+ Time.at((now.to_i / 60 + 1) * 60)
27
+ end
28
+
29
+ def normalize_time t
30
+ t.is_a?(String) ? Time.parse(t) : t
31
+ end
32
+
33
+
34
+ class ActiveRecordFake
35
+ attr_accessor :id, :name, :at, :frequency, :tz
36
+
37
+ class << self
38
+ def create *args
39
+ new *args
40
+ end
41
+
42
+ def add instance
43
+ @events << instance
44
+ end
45
+
46
+ def remove instance
47
+ @events.delete(instance)
48
+ end
49
+
50
+ def next_id
51
+ id = @next_id
52
+ @next_id += 1
53
+ id
54
+ end
55
+
56
+ def reset_id
57
+ @next_id = 1
58
+ end
59
+
60
+ def delete_all
61
+ @events.clear
62
+ reset_id
63
+ end
64
+
65
+ def all
66
+ @events.dup
67
+ end
68
+ end
69
+
70
+ def initialize options={}
71
+ @id = options.fetch(:id) { self.class.next_id }
72
+ @name = options.fetch(:name) { nil }
73
+ @at = options.fetch(:at) { nil }
74
+ @frequency = options.fetch(:frequency) { raise KeyError, ":every must be supplied" }
75
+ @tz = options.fetch(:tz) { nil }
76
+
77
+ self.class.add self
78
+ end
79
+
80
+ def name
81
+ @name || "#{self.class}:#{id}"
82
+ end
83
+
84
+ def delete!
85
+ self.class.remove(self)
86
+ end
87
+
88
+ def update options={}
89
+ options.each{|attr, value| self.send("#{attr}=".to_sym, value) }
90
+ end
91
+ end
92
+
93
+ class DatabaseEventModelClass < ActiveRecordFake
94
+ @events = []
95
+ @next_id = 1
96
+ end
97
+
98
+ class DatabaseEventModelClass2 < ActiveRecordFake
99
+ @events = []
100
+ @next_id = 1
101
+ end
metadata CHANGED
@@ -1,8 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: clockwork
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.7
5
- prerelease:
4
+ version: 1.0.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - Adam Wiggins
@@ -10,134 +9,118 @@ authors:
10
9
  autorequire:
11
10
  bindir: bin
12
11
  cert_chain: []
13
- date: 2014-07-05 00:00:00.000000000 Z
12
+ date: 2014-09-03 00:00:00.000000000 Z
14
13
  dependencies:
15
14
  - !ruby/object:Gem::Dependency
16
15
  name: tzinfo
17
16
  requirement: !ruby/object:Gem::Requirement
18
- none: false
19
17
  requirements:
20
- - - ! '>='
18
+ - - ">="
21
19
  - !ruby/object:Gem::Version
22
20
  version: '0'
23
21
  type: :runtime
24
22
  prerelease: false
25
23
  version_requirements: !ruby/object:Gem::Requirement
26
- none: false
27
24
  requirements:
28
- - - ! '>='
25
+ - - ">="
29
26
  - !ruby/object:Gem::Version
30
27
  version: '0'
31
28
  - !ruby/object:Gem::Dependency
32
29
  name: activesupport
33
30
  requirement: !ruby/object:Gem::Requirement
34
- none: false
35
31
  requirements:
36
- - - ! '>='
32
+ - - ">="
37
33
  - !ruby/object:Gem::Version
38
34
  version: '0'
39
35
  type: :runtime
40
36
  prerelease: false
41
37
  version_requirements: !ruby/object:Gem::Requirement
42
- none: false
43
38
  requirements:
44
- - - ! '>='
39
+ - - ">="
45
40
  - !ruby/object:Gem::Version
46
41
  version: '0'
47
42
  - !ruby/object:Gem::Dependency
48
43
  name: bundler
49
44
  requirement: !ruby/object:Gem::Requirement
50
- none: false
51
45
  requirements:
52
- - - ~>
46
+ - - "~>"
53
47
  - !ruby/object:Gem::Version
54
48
  version: '1.3'
55
49
  type: :development
56
50
  prerelease: false
57
51
  version_requirements: !ruby/object:Gem::Requirement
58
- none: false
59
52
  requirements:
60
- - - ~>
53
+ - - "~>"
61
54
  - !ruby/object:Gem::Version
62
55
  version: '1.3'
63
56
  - !ruby/object:Gem::Dependency
64
57
  name: rake
65
58
  requirement: !ruby/object:Gem::Requirement
66
- none: false
67
59
  requirements:
68
- - - ! '>='
60
+ - - ">="
69
61
  - !ruby/object:Gem::Version
70
62
  version: '0'
71
63
  type: :development
72
64
  prerelease: false
73
65
  version_requirements: !ruby/object:Gem::Requirement
74
- none: false
75
66
  requirements:
76
- - - ! '>='
67
+ - - ">="
77
68
  - !ruby/object:Gem::Version
78
69
  version: '0'
79
70
  - !ruby/object:Gem::Dependency
80
71
  name: daemons
81
72
  requirement: !ruby/object:Gem::Requirement
82
- none: false
83
73
  requirements:
84
- - - ! '>='
74
+ - - ">="
85
75
  - !ruby/object:Gem::Version
86
76
  version: '0'
87
77
  type: :development
88
78
  prerelease: false
89
79
  version_requirements: !ruby/object:Gem::Requirement
90
- none: false
91
80
  requirements:
92
- - - ! '>='
81
+ - - ">="
93
82
  - !ruby/object:Gem::Version
94
83
  version: '0'
95
84
  - !ruby/object:Gem::Dependency
96
85
  name: contest
97
86
  requirement: !ruby/object:Gem::Requirement
98
- none: false
99
87
  requirements:
100
- - - ! '>='
88
+ - - ">="
101
89
  - !ruby/object:Gem::Version
102
90
  version: '0'
103
91
  type: :development
104
92
  prerelease: false
105
93
  version_requirements: !ruby/object:Gem::Requirement
106
- none: false
107
94
  requirements:
108
- - - ! '>='
95
+ - - ">="
109
96
  - !ruby/object:Gem::Version
110
97
  version: '0'
111
98
  - !ruby/object:Gem::Dependency
112
99
  name: minitest
113
100
  requirement: !ruby/object:Gem::Requirement
114
- none: false
115
101
  requirements:
116
- - - ~>
102
+ - - "~>"
117
103
  - !ruby/object:Gem::Version
118
104
  version: '4.0'
119
105
  type: :development
120
106
  prerelease: false
121
107
  version_requirements: !ruby/object:Gem::Requirement
122
- none: false
123
108
  requirements:
124
- - - ~>
109
+ - - "~>"
125
110
  - !ruby/object:Gem::Version
126
111
  version: '4.0'
127
112
  - !ruby/object:Gem::Dependency
128
113
  name: mocha
129
114
  requirement: !ruby/object:Gem::Requirement
130
- none: false
131
115
  requirements:
132
- - - ! '>='
116
+ - - ">="
133
117
  - !ruby/object:Gem::Version
134
118
  version: '0'
135
119
  type: :development
136
120
  prerelease: false
137
121
  version_requirements: !ruby/object:Gem::Requirement
138
- none: false
139
122
  requirements:
140
- - - ! '>='
123
+ - - ">="
141
124
  - !ruby/object:Gem::Version
142
125
  version: '0'
143
126
  description: A scheduler process to replace cron, using a more flexible Ruby syntax
@@ -152,61 +135,62 @@ extensions: []
152
135
  extra_rdoc_files:
153
136
  - README.md
154
137
  files:
155
- - .gitignore
156
- - .travis.yml
138
+ - ".gitignore"
139
+ - ".travis.yml"
157
140
  - Gemfile
141
+ - LICENSE
158
142
  - README.md
159
143
  - Rakefile
160
144
  - bin/clockwork
161
145
  - bin/clockworkd
162
146
  - clockwork.gemspec
147
+ - clockworkd.1
163
148
  - example.rb
164
149
  - gemfiles/activesupport3.gemfile
165
150
  - gemfiles/activesupport4.gemfile
166
151
  - lib/clockwork.rb
167
152
  - lib/clockwork/at.rb
153
+ - lib/clockwork/database_events.rb
154
+ - lib/clockwork/database_events/event.rb
155
+ - lib/clockwork/database_events/manager.rb
156
+ - lib/clockwork/database_events/registry.rb
157
+ - lib/clockwork/database_events/sync_performer.rb
168
158
  - lib/clockwork/event.rb
169
159
  - lib/clockwork/manager.rb
170
- - lib/clockwork/manager_with_database_tasks.rb
171
160
  - test/at_test.rb
172
161
  - test/clockwork_test.rb
162
+ - test/database_events/sync_performer_test.rb
163
+ - test/database_events/test_helpers.rb
173
164
  - test/event_test.rb
174
165
  - test/manager_test.rb
175
- - test/manager_with_database_tasks_test.rb
176
166
  homepage: http://github.com/tomykaira/clockwork
177
167
  licenses:
178
168
  - MIT
169
+ metadata: {}
179
170
  post_install_message:
180
171
  rdoc_options: []
181
172
  require_paths:
182
173
  - lib
183
174
  required_ruby_version: !ruby/object:Gem::Requirement
184
- none: false
185
175
  requirements:
186
- - - ! '>='
176
+ - - ">="
187
177
  - !ruby/object:Gem::Version
188
178
  version: '0'
189
- segments:
190
- - 0
191
- hash: -1006563675732551945
192
179
  required_rubygems_version: !ruby/object:Gem::Requirement
193
- none: false
194
180
  requirements:
195
- - - ! '>='
181
+ - - ">="
196
182
  - !ruby/object:Gem::Version
197
183
  version: '0'
198
- segments:
199
- - 0
200
- hash: -1006563675732551945
201
184
  requirements: []
202
185
  rubyforge_project:
203
- rubygems_version: 1.8.23
186
+ rubygems_version: 2.2.0
204
187
  signing_key:
205
- specification_version: 3
188
+ specification_version: 4
206
189
  summary: A scheduler process to replace cron.
207
190
  test_files:
208
191
  - test/at_test.rb
209
192
  - test/clockwork_test.rb
193
+ - test/database_events/sync_performer_test.rb
194
+ - test/database_events/test_helpers.rb
210
195
  - test/event_test.rb
211
196
  - test/manager_test.rb
212
- - test/manager_with_database_tasks_test.rb