clockwork 0.7.7 → 1.0.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.
@@ -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