resque-scheduler 2.0.0 → 2.3.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.

Potentially problematic release.


This version of resque-scheduler might be problematic. Click here for more details.

@@ -1,11 +1,11 @@
1
1
  require 'rubygems'
2
2
  require 'resque'
3
3
  require 'resque_scheduler/version'
4
+ require 'resque_scheduler/util'
4
5
  require 'resque/scheduler'
5
6
  require 'resque_scheduler/plugin'
6
7
 
7
8
  module ResqueScheduler
8
-
9
9
  #
10
10
  # Accepts a new schedule configuration of the form:
11
11
  #
@@ -25,6 +25,9 @@ module ResqueScheduler
25
25
  # is used implicitly as "class" argument - in the "MakeTea" example,
26
26
  # "MakeTea" is used both as job name and resque worker class.
27
27
  #
28
+ # Any jobs that were in the old schedule, but are not
29
+ # present in the new schedule, will be removed.
30
+ #
28
31
  # :cron can be any cron scheduling string
29
32
  #
30
33
  # :every can be used in lieu of :cron. see rufus-scheduler's 'every' usage
@@ -43,19 +46,27 @@ module ResqueScheduler
43
46
  # params is an array, each element in the array is passed as a separate
44
47
  # param, otherwise params is passed in as the only parameter to perform.
45
48
  def schedule=(schedule_hash)
49
+ # clean the schedules as it exists in redis
50
+ clean_schedules
51
+
46
52
  schedule_hash = prepare_schedule(schedule_hash)
47
53
 
48
- if Resque::Scheduler.dynamic
49
- schedule_hash.each do |name, job_spec|
50
- set_schedule(name, job_spec)
51
- end
54
+ # store all schedules in redis, so we can retrieve them back everywhere.
55
+ schedule_hash.each do |name, job_spec|
56
+ set_schedule(name, job_spec)
52
57
  end
53
- @schedule = schedule_hash
58
+
59
+ # ensure only return the successfully saved data!
60
+ reload_schedule!
54
61
  end
55
62
 
56
63
  # Returns the schedule hash
57
64
  def schedule
58
- @schedule ||= {}
65
+ @schedule ||= get_schedules
66
+ if @schedule.nil?
67
+ return {}
68
+ end
69
+ @schedule
59
70
  end
60
71
 
61
72
  # reloads the schedule from redis
@@ -63,17 +74,28 @@ module ResqueScheduler
63
74
  @schedule = get_schedules
64
75
  end
65
76
 
66
- # gets the schedule as it exists in redis
77
+ # gets the schedules as it exists in redis
67
78
  def get_schedules
79
+ unless redis.exists(:schedules)
80
+ return nil
81
+ end
82
+
83
+ redis.hgetall(:schedules).tap do |h|
84
+ h.each do |name, config|
85
+ h[name] = decode(config)
86
+ end
87
+ end
88
+ end
89
+
90
+ # clean the schedules as it exists in redis, useful for first setup?
91
+ def clean_schedules
68
92
  if redis.exists(:schedules)
69
- redis.hgetall(:schedules).tap do |h|
70
- h.each do |name, config|
71
- h[name] = decode(config)
72
- end
93
+ redis.hkeys(:schedules).each do |key|
94
+ remove_schedule(key)
73
95
  end
74
- else
75
- nil
76
96
  end
97
+ @schedule = nil
98
+ true
77
99
  end
78
100
 
79
101
  # Create or update a schedule with the provided name and configuration.
@@ -110,7 +132,7 @@ module ResqueScheduler
110
132
  # for queueing. Until timestamp is in the past, the job will
111
133
  # sit in the schedule list.
112
134
  def enqueue_at(timestamp, klass, *args)
113
- validate_job!(klass)
135
+ validate(klass)
114
136
  enqueue_at_with_queue(queue_from_class(klass), timestamp, klass, *args)
115
137
  end
116
138
 
@@ -121,9 +143,15 @@ module ResqueScheduler
121
143
  def enqueue_at_with_queue(queue, timestamp, klass, *args)
122
144
  return false unless Plugin.run_before_schedule_hooks(klass, *args)
123
145
 
124
- if Resque.inline?
146
+ if Resque.inline? || timestamp.to_i < Time.now.to_i
125
147
  # Just create the job and let resque perform it right away with inline.
126
- Resque::Job.create(queue, klass, *args)
148
+ # If the class is a custom job class, call self#scheduled on it. This allows you to do things like
149
+ # Resque.enqueue_at(timestamp, CustomJobClass, :opt1 => val1). Otherwise, pass off to Resque.
150
+ if klass.respond_to?(:scheduled)
151
+ klass.scheduled(queue, klass.to_s(), *args)
152
+ else
153
+ Resque::Job.create(queue, klass, *args)
154
+ end
127
155
  else
128
156
  delayed_push(timestamp, job_to_hash_with_queue(queue, klass, args))
129
157
  end
@@ -152,6 +180,9 @@ module ResqueScheduler
152
180
  # First add this item to the list for this timestamp
153
181
  redis.rpush("delayed:#{timestamp.to_i}", encode(item))
154
182
 
183
+ # Store the timestamps at with this item occurs
184
+ redis.sadd("timestamps:#{encode(item)}", "delayed:#{timestamp.to_i}")
185
+
155
186
  # Now, add this timestamp to the zsets. The score and the value are
156
187
  # the same since we'll be querying by timestamp, and we don't have
157
188
  # anything else to store.
@@ -160,7 +191,7 @@ module ResqueScheduler
160
191
 
161
192
  # Returns an array of timestamps based on start and count
162
193
  def delayed_queue_peek(start, count)
163
- Array(redis.zrange(:delayed_queue_schedule, start, start+count-1)).collect{|x| x.to_i}
194
+ Array(redis.zrange(:delayed_queue_schedule, start, start+count-1)).collect { |x| x.to_i }
164
195
  end
165
196
 
166
197
  # Returns the size of the delayed queue schedule
@@ -197,7 +228,9 @@ module ResqueScheduler
197
228
  def next_item_for_timestamp(timestamp)
198
229
  key = "delayed:#{timestamp.to_i}"
199
230
 
200
- item = decode redis.lpop(key)
231
+ encoded_item = redis.lpop(key)
232
+ redis.srem("timestamps:#{encoded_item}", key)
233
+ item = decode(encoded_item)
201
234
 
202
235
  # If the list is empty, remove it.
203
236
  clean_up_timestamp(key, timestamp)
@@ -207,23 +240,36 @@ module ResqueScheduler
207
240
  # Clears all jobs created with enqueue_at or enqueue_in
208
241
  def reset_delayed_queue
209
242
  Array(redis.zrange(:delayed_queue_schedule, 0, -1)).each do |item|
210
- redis.del "delayed:#{item}"
243
+ key = "delayed:#{item}"
244
+ items = redis.lrange(key, 0, -1)
245
+ redis.pipelined do
246
+ items.each { |ts_item| redis.del("timestamps:#{ts_item}") }
247
+ end
248
+ redis.del key
211
249
  end
212
250
 
213
251
  redis.del :delayed_queue_schedule
214
252
  end
215
253
 
216
254
  # Given an encoded item, remove it from the delayed_queue
217
- #
218
- # This method is potentially very expensive since it needs to scan
219
- # through the delayed queue for every timestamp.
220
255
  def remove_delayed(klass, *args)
221
- destroyed = 0
222
256
  search = encode(job_to_hash(klass, args))
223
- Array(redis.keys("delayed:*")).each do |key|
224
- destroyed += redis.lrem key, 0, search
257
+ timestamps = redis.smembers("timestamps:#{search}")
258
+
259
+ replies = redis.pipelined do
260
+ timestamps.each do |key|
261
+ redis.lrem(key, 0, search)
262
+ redis.srem("timestamps:#{search}", key)
263
+ end
225
264
  end
226
- destroyed
265
+
266
+ (replies.nil? || replies.empty?) ? 0 : replies.each_slice(2).collect { |slice| slice.first }.inject(:+)
267
+ end
268
+
269
+ # Given an encoded item, enqueue it now
270
+ def enqueue_delayed(klass, *args)
271
+ hash = job_to_hash(klass, args)
272
+ remove_delayed(klass, *args).times { Resque::Scheduler.enqueue_from_config(hash) }
227
273
  end
228
274
 
229
275
  # Given a timestamp and job (klass + args) it removes all instances and
@@ -233,8 +279,12 @@ module ResqueScheduler
233
279
  # timestamp
234
280
  def remove_delayed_job_from_timestamp(timestamp, klass, *args)
235
281
  key = "delayed:#{timestamp.to_i}"
236
- count = redis.lrem key, 0, encode(job_to_hash(klass, args))
282
+ encoded_job = encode(job_to_hash(klass, args))
283
+
284
+ redis.srem("timestamps:#{encoded_job}", key)
285
+ count = redis.lrem(key, 0, encoded_job)
237
286
  clean_up_timestamp(key, timestamp)
287
+
238
288
  count
239
289
  end
240
290
 
@@ -246,6 +296,14 @@ module ResqueScheduler
246
296
  total_jobs
247
297
  end
248
298
 
299
+ # Returns delayed jobs schedule timestamp for +klass+, +args+.
300
+ def scheduled_at(klass, *args)
301
+ search = encode(job_to_hash(klass, args))
302
+ redis.smembers("timestamps:#{search}").collect do |key|
303
+ key.tr('delayed:', '').to_i
304
+ end
305
+ end
306
+
249
307
  private
250
308
 
251
309
  def job_to_hash(klass, args)
@@ -258,6 +316,9 @@ module ResqueScheduler
258
316
 
259
317
  def clean_up_timestamp(key, timestamp)
260
318
  # If the list is empty, remove it.
319
+
320
+ # Use a watch here to ensure nobody adds jobs to this delayed
321
+ # queue while we're removing it.
261
322
  redis.watch key
262
323
  if 0 == redis.llen(key).to_i
263
324
  redis.multi do
@@ -268,15 +329,6 @@ module ResqueScheduler
268
329
  redis.unwatch
269
330
  end
270
331
  end
271
- def validate_job!(klass)
272
- if klass.to_s.empty?
273
- raise Resque::NoClassError.new("Jobs must be given a class.")
274
- end
275
-
276
- unless queue_from_class(klass)
277
- raise Resque::NoQueueError.new("Jobs must be placed onto a queue.")
278
- end
279
- end
280
332
 
281
333
  def prepare_schedule(schedule_hash)
282
334
  prepared_hash = {}
@@ -287,7 +339,6 @@ module ResqueScheduler
287
339
  end
288
340
  prepared_hash
289
341
  end
290
-
291
342
  end
292
343
 
293
344
  Resque.extend ResqueScheduler
@@ -1,27 +1,33 @@
1
- # -*- encoding: utf-8 -*-
2
- $:.unshift File.expand_path("../lib", __FILE__)
3
- require "resque_scheduler/version"
1
+ # vim:fileencoding=utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'resque_scheduler/version'
4
5
 
5
- Gem::Specification.new do |s|
6
- s.name = "resque-scheduler"
7
- s.version = ResqueScheduler::VERSION
8
- s.platform = Gem::Platform::RUBY
9
- s.authors = ['Ben VandenBos']
10
- s.email = ['bvandenbos@gmail.com']
11
- s.homepage = "http://github.com/bvandenbos/resque-scheduler"
12
- s.summary = "Light weight job scheduling on top of Resque"
13
- s.description = %q{Light weight job scheduling on top of Resque.
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'resque-scheduler'
8
+ spec.version = ResqueScheduler::VERSION
9
+ spec.authors = ['Ben VandenBos']
10
+ spec.email = ['bvandenbos@gmail.com']
11
+ spec.homepage = 'http://github.com/resque/resque-scheduler'
12
+ spec.summary = 'Light weight job scheduling on top of Resque'
13
+ spec.description = %q{Light weight job scheduling on top of Resque.
14
14
  Adds methods enqueue_at/enqueue_in to schedule jobs in the future.
15
15
  Also supports queueing jobs on a fixed, cron-like schedule.}
16
+ spec.license = 'MIT'
16
17
 
17
- s.required_rubygems_version = ">= 1.3.6"
18
- s.add_development_dependency "bundler", ">= 1.0.0"
18
+ spec.files = `git ls-files`.split("\n")
19
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
+ spec.test_files = spec.files.grep(%r{^test/})
21
+ spec.require_paths = ['lib']
19
22
 
20
- s.files = `git ls-files`.split("\n")
21
- s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
22
- s.require_path = 'lib'
23
+ spec.add_development_dependency 'bundler', '~> 1.3'
24
+ spec.add_development_dependency 'mocha'
25
+ spec.add_development_dependency 'rack-test'
26
+ spec.add_development_dependency 'rake'
27
+ spec.add_development_dependency 'json' if RUBY_VERSION < '1.9'
28
+ spec.add_development_dependency 'rubocop' unless RUBY_VERSION < '1.9'
23
29
 
24
- s.add_runtime_dependency(%q<redis>, [">= 2.0.1"])
25
- s.add_runtime_dependency(%q<resque>, [">= 1.20.0"])
26
- s.add_runtime_dependency(%q<rufus-scheduler>, [">= 0"])
30
+ spec.add_runtime_dependency 'redis', '>= 3.0.0'
31
+ spec.add_runtime_dependency 'resque', '~> 1.25'
32
+ spec.add_runtime_dependency 'rufus-scheduler', '~> 2.0'
27
33
  end
@@ -0,0 +1,14 @@
1
+ require 'redis'
2
+ require 'resque'
3
+
4
+ if ARGV.size != 1
5
+ puts "migrate_to_timestamps_set.rb <redis-host:redis-port>"
6
+ exit
7
+ end
8
+
9
+ Resque.redis = ARGV[0]
10
+ redis = Resque.redis
11
+ Array(redis.keys("delayed:*")).each do |key|
12
+ jobs = redis.lrange(key, 0, -1)
13
+ jobs.each {|job| redis.sadd("timestamps:#{job}", key)}
14
+ end
@@ -8,20 +8,21 @@ context "DelayedQueue" do
8
8
  end
9
9
 
10
10
  test "enqueue_at adds correct list and zset" do
11
- timestamp = Time.now - 1 # 1 second ago (in the past, should come out right away)
11
+ timestamp = Time.now + 1
12
+ encoded_job = Resque.encode(:class => SomeIvarJob.to_s, :args => ["path"], :queue => Resque.queue_from_class(SomeIvarJob))
12
13
 
13
14
  assert_equal(0, Resque.redis.llen("delayed:#{timestamp.to_i}").to_i, "delayed queue should be empty to start")
15
+ assert_equal(0, Resque.redis.scard("timestamps:#{encoded_job}"), "job timestamps set should be empty to start")
14
16
 
15
17
  Resque.enqueue_at(timestamp, SomeIvarJob, "path")
16
18
 
17
19
  # Confirm the correct keys were added
18
20
  assert_equal(1, Resque.redis.llen("delayed:#{timestamp.to_i}").to_i, "delayed queue should have one entry now")
21
+ assert_equal(1, Resque.redis.scard("timestamps:#{encoded_job}"), "job timestamps should have one entry now")
19
22
  assert_equal(1, Resque.redis.zcard(:delayed_queue_schedule), "The delayed_queue_schedule should have 1 entry now")
20
23
 
21
- read_timestamp = Resque.next_delayed_timestamp
24
+ read_timestamp = timestamp.to_i
22
25
 
23
- # Confirm the timestamp came out correctly
24
- assert_equal(timestamp.to_i, read_timestamp, "The timestamp we pull out of redis should match the one we put in")
25
26
  item = Resque.next_item_for_timestamp(read_timestamp)
26
27
 
27
28
  # Confirm the item came out correctly
@@ -31,23 +32,25 @@ context "DelayedQueue" do
31
32
  # And now confirm the keys are gone
32
33
  assert(!Resque.redis.exists("delayed:#{timestamp.to_i}"))
33
34
  assert_equal(0, Resque.redis.zcard(:delayed_queue_schedule), "delayed queue should be empty")
35
+ assert_equal(0, Resque.redis.scard("timestamps:#{encoded_job}"), "job timestamps set should be empty")
34
36
  end
35
37
 
36
38
  test "enqueue_at with queue adds correct list and zset and queue" do
37
- timestamp = Time.now - 1 # 1 second ago (in the past, should come out right away)
39
+ timestamp = Time.now + 1
40
+ encoded_job = Resque.encode(:class => SomeIvarJob.to_s, :args => ["path"], :queue => 'critical')
38
41
 
39
42
  assert_equal(0, Resque.redis.llen("delayed:#{timestamp.to_i}").to_i, "delayed queue should be empty to start")
43
+ assert_equal(0, Resque.redis.scard("timestamps:#{encoded_job}"), "job timestamps set should be empty to start")
40
44
 
41
45
  Resque.enqueue_at_with_queue('critical', timestamp, SomeIvarJob, "path")
42
46
 
43
47
  # Confirm the correct keys were added
44
48
  assert_equal(1, Resque.redis.llen("delayed:#{timestamp.to_i}").to_i, "delayed queue should have one entry now")
49
+ assert_equal(1, Resque.redis.scard("timestamps:#{encoded_job}"), "job timestamps should have one entry now")
45
50
  assert_equal(1, Resque.redis.zcard(:delayed_queue_schedule), "The delayed_queue_schedule should have 1 entry now")
46
51
 
47
- read_timestamp = Resque.next_delayed_timestamp
52
+ read_timestamp = timestamp.to_i
48
53
 
49
- # Confirm the timestamp came out correctly
50
- assert_equal(timestamp.to_i, read_timestamp, "The timestamp we pull out of redis should match the one we put in")
51
54
  item = Resque.next_item_for_timestamp(read_timestamp)
52
55
 
53
56
  # Confirm the item came out correctly
@@ -58,17 +61,21 @@ context "DelayedQueue" do
58
61
  # And now confirm the keys are gone
59
62
  assert(!Resque.redis.exists("delayed:#{timestamp.to_i}"))
60
63
  assert_equal(0, Resque.redis.zcard(:delayed_queue_schedule), "delayed queue should be empty")
64
+ assert_equal(0, Resque.redis.scard("timestamps:#{encoded_job}"), "job timestamps set should be empty")
61
65
  end
62
66
 
63
67
  test "a job in the future doesn't come out" do
64
68
  timestamp = Time.now + 600 # 10 minutes from now (in the future, shouldn't come out)
69
+ encoded_job = Resque.encode(:class => SomeIvarJob.to_s, :args => ["path"], :queue => Resque.queue_from_class(SomeIvarJob))
65
70
 
66
71
  assert_equal(0, Resque.redis.llen("delayed:#{timestamp.to_i}").to_i, "delayed queue should be empty to start")
72
+ assert_equal(0, Resque.redis.scard("timestamps:#{encoded_job}"), "job timestamps set should be empty to start")
67
73
 
68
74
  Resque.enqueue_at(timestamp, SomeIvarJob, "path")
69
75
 
70
76
  # Confirm the correct keys were added
71
77
  assert_equal(1, Resque.redis.llen("delayed:#{timestamp.to_i}").to_i, "delayed queue should have one entry now")
78
+ assert_equal(1, Resque.redis.scard("timestamps:#{encoded_job}"), "job timestamps should have one entry now")
72
79
  assert_equal(1, Resque.redis.zcard(:delayed_queue_schedule), "The delayed_queue_schedule should have 1 entry now")
73
80
 
74
81
  read_timestamp = Resque.next_delayed_timestamp
@@ -88,19 +95,21 @@ context "DelayedQueue" do
88
95
 
89
96
  test "enqueue_at and enqueue_in are equivelent" do
90
97
  timestamp = Time.now + 60
98
+ encoded_job = Resque.encode(:class => SomeIvarJob.to_s, :args => ["path"], :queue => Resque.queue_from_class(SomeIvarJob))
91
99
 
92
100
  Resque.enqueue_at(timestamp, SomeIvarJob, "path")
93
101
  Resque.enqueue_in(timestamp - Time.now, SomeIvarJob, "path")
94
102
 
95
103
  assert_equal(1, Resque.redis.zcard(:delayed_queue_schedule), "should have one timestamp in the delayed queue")
96
104
  assert_equal(2, Resque.redis.llen("delayed:#{timestamp.to_i}"), "should have 2 items in the timestamp queue")
105
+ assert_equal(1, Resque.redis.scard("timestamps:#{encoded_job}"), "job timestamps should have one entry now")
97
106
  end
98
107
 
99
108
  test "empty delayed_queue_peek returns empty array" do
100
109
  assert_equal([], Resque.delayed_queue_peek(0,20))
101
110
  end
102
111
 
103
- test "delqyed_queue_peek returns stuff" do
112
+ test "delayed_queue_peek returns stuff" do
104
113
  t = Time.now
105
114
  expected_timestamps = (1..5).to_a.map do |i|
106
115
  (t + 60 + i).to_i
@@ -166,12 +175,11 @@ context "DelayedQueue" do
166
175
 
167
176
  test "handle_delayed_item with items" do
168
177
  t = Time.now - 60 # in the past
169
- Resque.enqueue_at(t, SomeIvarJob)
170
- Resque.enqueue_at(t, SomeIvarJob)
171
178
 
172
179
  # 2 SomeIvarJob jobs should be created in the "ivar" queue
173
- Resque::Job.expects(:create).twice.with('ivar', SomeIvarJob, nil)
174
- Resque::Scheduler.handle_delayed_items
180
+ Resque::Job.expects(:create).twice.with(:ivar, SomeIvarJob)
181
+ Resque.enqueue_at(t, SomeIvarJob)
182
+ Resque.enqueue_at(t, SomeIvarJob)
175
183
  end
176
184
 
177
185
  test "handle_delayed_items with items in the future" do
@@ -184,6 +192,24 @@ context "DelayedQueue" do
184
192
  Resque::Scheduler.handle_delayed_items(t)
185
193
  end
186
194
 
195
+ test "calls klass#scheduled when enqueuing jobs if it exists" do
196
+ t = Time.now - 60
197
+ FakeCustomJobClassEnqueueAt.expects(:scheduled).once.with(:test, FakeCustomJobClassEnqueueAt.to_s, {:foo => "bar"})
198
+ Resque.enqueue_at(t, FakeCustomJobClassEnqueueAt, :foo => "bar")
199
+ end
200
+
201
+ test "when Resque.inline = true, calls klass#scheduled when enqueuing jobs if it exists" do
202
+ old_val = Resque.inline
203
+ begin
204
+ Resque.inline = true
205
+ t = Time.now - 60
206
+ FakeCustomJobClassEnqueueAt.expects(:scheduled).once.with(:test, FakeCustomJobClassEnqueueAt.to_s, {:foo => "bar"})
207
+ Resque.enqueue_at(t, FakeCustomJobClassEnqueueAt, :foo => "bar")
208
+ ensure
209
+ Resque.inline = old_val
210
+ end
211
+ end
212
+
187
213
  test "enqueue_delayed_items_for_timestamp creates jobs and empties the delayed queue" do
188
214
  t = Time.now + 60
189
215
 
@@ -199,6 +225,24 @@ context "DelayedQueue" do
199
225
  assert_equal(0, Resque.delayed_timestamp_peek(t, 0, 3).length)
200
226
  end
201
227
 
228
+ test "enqueue_delayed creates jobs and empties the delayed queue" do
229
+ t = Time.now + 60
230
+
231
+ Resque.enqueue_at(t, SomeIvarJob, "foo")
232
+ Resque.enqueue_at(t, SomeIvarJob, "bar")
233
+ Resque.enqueue_at(t, SomeIvarJob, "bar")
234
+
235
+ # 3 SomeIvarJob jobs should be created in the "ivar" queue
236
+ Resque::Job.expects(:create).never.with(:ivar, SomeIvarJob, "foo")
237
+ Resque::Job.expects(:create).twice.with(:ivar, SomeIvarJob, "bar")
238
+
239
+ # 2 SomeIvarJob jobs should be enqueued
240
+ assert_equal(2, Resque.enqueue_delayed(SomeIvarJob, "bar"))
241
+
242
+ # delayed queue for timestamp should have one remaining
243
+ assert_equal(1, Resque.delayed_timestamp_peek(t, 0, 3).length)
244
+ end
245
+
202
246
  test "handle_delayed_items works with out specifying queue (upgrade case)" do
203
247
  t = Time.now - 60
204
248
  Resque.delayed_push(t, :class => 'SomeIvarJob')
@@ -218,13 +262,23 @@ context "DelayedQueue" do
218
262
 
219
263
  Resque.reset_delayed_queue
220
264
  assert_equal(0, Resque.delayed_queue_schedule_size)
265
+ assert_equal(0, Resque.redis.keys("timestamps:*").size)
221
266
  end
222
267
 
223
268
  test "remove_delayed removes job and returns the count" do
224
269
  t = Time.now + 120
270
+ encoded_job = Resque.encode(:class => SomeIvarJob.to_s, :args => [], :queue => Resque.queue_from_class(SomeIvarJob))
225
271
  Resque.enqueue_at(t, SomeIvarJob)
226
272
 
227
273
  assert_equal(1, Resque.remove_delayed(SomeIvarJob))
274
+ assert_equal(0, Resque.redis.scard("timestamps:#{encoded_job}"))
275
+ end
276
+
277
+ test "scheduled_at returns an array containing job schedule time" do
278
+ t = Time.now + 120
279
+ Resque.enqueue_at(t, SomeIvarJob)
280
+
281
+ assert_equal([t.to_i], Resque.scheduled_at(SomeIvarJob))
228
282
  end
229
283
 
230
284
  test "remove_delayed doesn't remove things it shouldn't" do
@@ -304,9 +358,6 @@ context "DelayedQueue" do
304
358
  end
305
359
 
306
360
  test "invalid job class" do
307
- assert_raise Resque::NoClassError do
308
- Resque.enqueue_in(10, nil)
309
- end
310
361
  assert_raise Resque::NoQueueError do
311
362
  Resque.enqueue_in(10, String) # string serves as invalid Job class
312
363
  end
data/test/redis-test.conf CHANGED
@@ -106,10 +106,3 @@ databases 16
106
106
  # errors for write operations, and this may even lead to DB inconsistency.
107
107
 
108
108
  # maxmemory <bytes>
109
-
110
- ############################### ADVANCED CONFIG ###############################
111
-
112
- # Glue small output buffers together in order to send small replies in a
113
- # single TCP packet. Uses a bit more CPU but most of the times it is a win
114
- # in terms of number of queries per second. Use 'yes' if unsure.
115
- glueoutputbuf yes
@@ -10,10 +10,24 @@ context "on GET to /schedule" do
10
10
  end
11
11
 
12
12
  context "on GET to /schedule with scheduled jobs" do
13
- setup do
13
+ setup do
14
14
  ENV['rails_env'] = 'production'
15
- Resque.schedule = {:some_ivar_job => {'cron' => "* * * * *", 'class' => 'SomeIvarJob', 'args' => "/tmp", 'rails_env' => 'production'},
16
- :some_other_job => {'queue' => 'high', 'class' => 'SomeOtherJob', 'args' => {:b => 'blah'}}}
15
+ Resque.schedule = {
16
+ 'some_ivar_job' => {
17
+ 'cron' => "* * * * *",
18
+ 'class' => 'SomeIvarJob',
19
+ 'args' => "/tmp",
20
+ 'rails_env' => 'production'
21
+ },
22
+ 'some_other_job' => {
23
+ 'every' => ['5m'],
24
+ 'queue' => 'high',
25
+ 'class' => 'SomeOtherJob',
26
+ 'args' => {
27
+ 'b' => 'blah'
28
+ }
29
+ }
30
+ }
17
31
  Resque::Scheduler.load_schedule!
18
32
  get "/schedule"
19
33
  end
@@ -30,3 +44,91 @@ context "on GET to /delayed" do
30
44
 
31
45
  should_respond_with_success
32
46
  end
47
+
48
+ def resque_schedule
49
+ {
50
+ 'job_without_params' => {
51
+ 'cron' => '* * * * *',
52
+ 'class' => 'JobWithoutParams',
53
+ 'args' => {
54
+ 'host' => 'localhost'
55
+ },
56
+ 'rails_env' => 'production'},
57
+ 'job_with_params' => {
58
+ 'cron' => '* * * * *',
59
+ 'class' => 'JobWithParams',
60
+ 'args' => {
61
+ 'host' => 'localhost'
62
+ },
63
+ 'parameters' => {
64
+ 'log_level' => {
65
+ 'description' => 'The level of logging',
66
+ 'default' => 'warn'
67
+ }
68
+ }
69
+ }
70
+ }
71
+ end
72
+
73
+ context "POST /schedule/requeue" do
74
+ setup do
75
+ Resque.schedule = resque_schedule
76
+ Resque::Scheduler.load_schedule!
77
+ end
78
+
79
+ test 'job without params' do
80
+ # Regular jobs without params should redirect to /overview
81
+ job_name = 'job_without_params'
82
+ Resque::Scheduler.stubs(:enqueue_from_config).once.with(Resque.schedule[job_name])
83
+
84
+ post '/schedule/requeue', {'job_name' => job_name}
85
+ follow_redirect!
86
+ assert_equal "http://example.org/overview", last_request.url
87
+ assert last_response.ok?
88
+ end
89
+
90
+ test 'job with params' do
91
+ # If a job has params defined,
92
+ # it should render the template with a form for the job params
93
+ job_name = 'job_with_params'
94
+ post '/schedule/requeue', {'job_name' => job_name}
95
+
96
+ assert last_response.ok?, last_response.errors
97
+ assert last_response.body.include?("This job requires parameters")
98
+ assert last_response.body.include?("<input type=\"hidden\" name=\"job_name\" value=\"#{job_name}\">")
99
+
100
+ Resque.schedule[job_name]['parameters'].each do |param_name, param_config|
101
+ assert last_response.body.include?(
102
+ "<span style=\"border-bottom:1px dotted;\" title=\"#{param_config['description']}\">(?)</span>")
103
+ assert last_response.body.include?(
104
+ "<input type=\"text\" name=\"log_level\" value=\"#{param_config['default']}\">")
105
+ end
106
+ end
107
+ end
108
+
109
+ context "POST /schedule/requeue_with_params" do
110
+ setup do
111
+ Resque.schedule = resque_schedule
112
+ Resque::Scheduler.load_schedule!
113
+ end
114
+
115
+ test 'job with params' do
116
+ job_name = 'job_with_params'
117
+ log_level = 'error'
118
+
119
+ job_config = Resque.schedule[job_name]
120
+ args = job_config['args'].merge('log_level' => log_level)
121
+ job_config = job_config.merge('args' => args)
122
+
123
+ Resque::Scheduler.stubs(:enqueue_from_config).once.with(job_config)
124
+
125
+ post '/schedule/requeue_with_params', {
126
+ 'job_name' => job_name,
127
+ 'log_level' => log_level
128
+ }
129
+ follow_redirect!
130
+ assert_equal "http://example.org/overview", last_request.url
131
+
132
+ assert last_response.ok?, last_response.errors
133
+ end
134
+ end
@@ -77,7 +77,7 @@ context "scheduling jobs with arguments" do
77
77
  test "calls the worker without arguments when 'args' is blank in the config" do
78
78
  Resque::Scheduler.enqueue_from_config(YAML.load(<<-YAML))
79
79
  class: SomeIvarJob
80
- args:
80
+ args:
81
81
  YAML
82
82
  SomeIvarJob.expects(:perform).once.with()
83
83
  Resque.reserve('ivar').perform