resque-retry 1.5.0 → 1.7.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,10 +3,9 @@ require 'resque/plugins/retry/logging'
3
3
 
4
4
  module Resque
5
5
  module Failure
6
-
7
6
  # A multiple failure backend, with retry suppression
8
7
  #
9
- # For example: if you had a job that could retry 5 times, your failure
8
+ # For example: if you had a job that could retry 5 times, your failure
10
9
  # backends are not notified unless the _final_ retry attempt also fails.
11
10
  #
12
11
  # Example:
@@ -28,17 +27,44 @@ module Resque
28
27
  #
29
28
  # @api private
30
29
  def save
31
- log_message 'failure backend save', args_from(payload), exception
30
+ args = args_from(payload)
31
+
32
+ log_message 'failure backend save', args, exception
32
33
 
33
34
  retryable = retryable?
34
35
  job_being_retried = retryable && retrying?
35
36
 
36
37
  if !job_being_retried
37
- log_message "#{retryable ? '' : 'non-'}retriable job is not being retried - sending failure to superclass", args_from(payload), exception
38
+ log_message(
39
+ "#{retryable ? '' : 'non-'}retryable job is not being retried - sending failure to superclass",
40
+ args,
41
+ exception
42
+ )
43
+
38
44
  cleanup_retry_failure_log!
39
- super
40
- elsif retry_delay > 0
41
- log_message "retry_delay: #{retry_delay} > 0 - saving details in Redis", args_from(payload), exception
45
+ return super
46
+ end
47
+
48
+ # some plugins define retry_delay and have it take no arguments, so rather than break those,
49
+ # we'll just check here to see whether it takes the additional exception class argument or not
50
+ # we also allow all job args to be passed to a custom `retry_delay` method
51
+ retry_delay_arity = klass.method(:retry_delay).arity
52
+
53
+ calculated_retry_delay = if [-2, 2].include?(retry_delay_arity)
54
+ klass.retry_delay(exception.class, *args)
55
+ elsif [-1, 1].include?(retry_delay_arity)
56
+ klass.retry_delay(exception.class)
57
+ else
58
+ klass.retry_delay
59
+ end
60
+
61
+ if calculated_retry_delay > 0
62
+ log_message(
63
+ "retry_delay: #{calculated_retry_delay} > 0 - saving details in Redis",
64
+ args,
65
+ exception
66
+ )
67
+
42
68
  data = {
43
69
  :failed_at => Time.now.strftime("%Y/%m/%d %H:%M:%S"),
44
70
  :payload => payload,
@@ -48,10 +74,19 @@ module Resque
48
74
  :worker => worker.to_s,
49
75
  :queue => queue
50
76
  }
77
+ data = Resque.encode(data)
51
78
 
52
- Resque.redis.setex(failure_key, 2*retry_delay, Resque.encode(data))
79
+ Resque.redis.setex(
80
+ failure_key,
81
+ 2 * calculated_retry_delay,
82
+ data
83
+ )
53
84
  else
54
- log_message "retry_delay: #{retry_delay} <= 0 - ignoring", args_from(payload), exception
85
+ log_message(
86
+ "retry_delay: #{calculated_retry_delay} <= 0 - ignoring",
87
+ args,
88
+ exception
89
+ )
55
90
  end
56
91
  end
57
92
 
@@ -62,25 +97,45 @@ module Resque
62
97
  'failure-' + retry_key
63
98
  end
64
99
 
65
- protected
66
-
67
- # Return the class/module of the failed job.
68
- def klass
69
- Resque::Job.new(nil, nil).constantize(payload['class'])
100
+ # Monkey-patch this in for now since it is a hard requirement for the
101
+ # "retry-all" functionality to work via the Web UI.
102
+ #
103
+ # This can be removed when the following PR has been merged into `Resque`
104
+ # itself:
105
+ #
106
+ # https://github.com/resque/resque/pull/1659
107
+ #
108
+ # @api public
109
+ class_eval do |klass|
110
+ if !klass.respond_to?(:requeue_queue)
111
+ def klass.requeue_queue(queue)
112
+ classes.first.requeue_queue(queue)
113
+ end
114
+ end
70
115
  end
71
116
 
72
- def retry_delay
73
- klass.retry_delay
117
+ protected
118
+
119
+ def args_from(payload)
120
+ (payload || {})['args'].dup
74
121
  end
75
122
 
76
- def retry_key
77
- klass.redis_retry_key(*payload['args'])
123
+ def cleanup_retry_failure_log!
124
+ Resque.redis.del(failure_key) if retryable?
78
125
  end
79
126
 
80
127
  def failure_key
81
128
  self.class.failure_key(retry_key)
82
129
  end
83
130
 
131
+ def klass
132
+ Resque::Job.new(nil, nil).constantize(payload['class'])
133
+ end
134
+
135
+ def retry_key
136
+ klass.redis_retry_key(*payload['args'])
137
+ end
138
+
84
139
  def retryable?
85
140
  klass.respond_to?(:redis_retry_key)
86
141
  rescue NameError
@@ -88,15 +143,17 @@ module Resque
88
143
  end
89
144
 
90
145
  def retrying?
91
- Resque.redis.exists(retry_key)
146
+ redis_key_exists?(retry_key)
92
147
  end
93
148
 
94
- def cleanup_retry_failure_log!
95
- Resque.redis.del(failure_key) if retryable?
96
- end
149
+ private
97
150
 
98
- def args_from(payload)
99
- (payload || {})['args']
151
+ def redis_key_exists?(key)
152
+ if Resque.redis.respond_to?(:exists?)
153
+ Resque.redis.exists?(key)
154
+ else
155
+ ![false, 0].include?(Resque.redis.exists(key) || false)
156
+ end
100
157
  end
101
158
  end
102
159
  end
@@ -52,12 +52,16 @@ module Resque
52
52
  #
53
53
  # @api private
54
54
  def self.extended(receiver)
55
+ retry_delay_multiplicand_min = DEFAULT_RETRY_DELAY_MULTIPLICAND_MIN
55
56
  retry_delay_multiplicand_min = \
56
- receiver.instance_variable_get("@retry_delay_multiplicand_min") || \
57
- DEFAULT_RETRY_DELAY_MULTIPLICAND_MIN
57
+ receiver.instance_variable_get(:@retry_delay_multiplicand_min) \
58
+ if receiver.instance_variable_defined?(:@retry_delay_multiplicand_min)
59
+
60
+ retry_delay_multiplicand_max = DEFAULT_RETRY_DELAY_MULTIPLICAND_MAX
58
61
  retry_delay_multiplicand_max = \
59
- receiver.instance_variable_get("@retry_delay_multiplicand_max") || \
60
- DEFAULT_RETRY_DELAY_MULTIPLICAND_MAX
62
+ receiver.instance_variable_get(:@retry_delay_multiplicand_max) \
63
+ if receiver.instance_variable_defined?(:@retry_delay_multiplicand_max)
64
+
61
65
  if retry_delay_multiplicand_min > retry_delay_multiplicand_max
62
66
  raise InvalidRetryDelayMultiplicandConfigurationException.new(
63
67
  %{"@retry_delay_multiplicand_min" must be less than or equal to "@retry_delay_multiplicand_max"}
@@ -76,10 +80,11 @@ module Resque
76
80
 
77
81
  # Selects the delay from the backoff strategy
78
82
  #
83
+ # @param _ [Exception] unused exception argument for signature parity
79
84
  # @return [Number] seconds to delay until the next retry.
80
85
  #
81
86
  # @api private
82
- def retry_delay
87
+ def retry_delay(_ = nil)
83
88
  delay = backoff_strategy[retry_attempt] || backoff_strategy.last
84
89
  # if the values are the same don't bother generating a random number, if
85
90
  # the delta is zero, some platforms will raise an error
@@ -43,11 +43,24 @@ module Resque
43
43
  # @api public
44
44
  class AmbiguousRetryStrategyException < StandardError; end
45
45
 
46
+ # Raised if there is a problem with the configuration of resque-retry.
47
+ #
48
+ # @api public
49
+ class RetryConfigurationException < StandardError; end
50
+
46
51
  # Fail fast, when extended, if the "receiver" is misconfigured
47
52
  #
48
53
  # @api private
49
54
  def self.extended(receiver)
50
- if receiver.instance_variable_get('@fatal_exceptions') && receiver.instance_variable_get('@retry_exceptions')
55
+ retry_exceptions = nil
56
+ retry_exceptions = receiver.instance_variable_get(:@retry_exceptions) \
57
+ if receiver.instance_variable_defined?(:@retry_exceptions)
58
+
59
+ fatal_exceptions = nil
60
+ fatal_exceptions = receiver.instance_variable_get(:@fatal_exceptions) \
61
+ if receiver.instance_variable_defined?(:@fatal_exceptions)
62
+
63
+ if fatal_exceptions && retry_exceptions
51
64
  raise AmbiguousRetryStrategyException.new(%{You can't define both "@fatal_exceptions" and "@retry_exceptions"})
52
65
  end
53
66
  end
@@ -94,11 +107,27 @@ module Resque
94
107
  # A retry limit of 0 will *never* retry.
95
108
  # A retry limit of -1 or below will retry forever.
96
109
  #
110
+ # The default value is: `1` or in the case of where `@retry_exceptions` is
111
+ # specified, and it contains one or more `Array` values, the maximum
112
+ # length will be used (e.g. `@retry_exceptions = { NetworkError => 30, SystemCallError => [120, 240] }`
113
+ # would return `2` because `SystemCallError` _should_ be attempted at
114
+ # least twice to respect the specified configuration).
115
+ #
97
116
  # @return [Fixnum]
98
117
  #
99
118
  # @api public
100
119
  def retry_limit
101
- @retry_limit ||= 1
120
+ @retry_limit ||= begin
121
+ default_retry_limit = 1
122
+ if instance_variable_defined?(:@retry_exceptions) && @retry_exceptions.is_a?(Hash)
123
+ @retry_exceptions.values.each do |value|
124
+ if value.is_a?(Array) && value.length > default_retry_limit
125
+ default_retry_limit = value.length
126
+ end
127
+ end
128
+ end
129
+ default_retry_limit
130
+ end
102
131
  end
103
132
 
104
133
  # Number of retry attempts used to try and perform the job
@@ -115,13 +144,22 @@ module Resque
115
144
 
116
145
  # @abstract
117
146
  # Number of seconds to delay until the job is retried
147
+ # If @retry_exceptions is a Hash and there is no delay defined for exception_class,
148
+ # looks for closest superclass and assigns it's delay to @retry_exceptions[exception_class]
118
149
  #
119
150
  # @return [Number] number of seconds to delay
120
151
  #
121
152
  # @api public
122
153
  def retry_delay(exception_class = nil)
123
- if @retry_exceptions.is_a?(Hash)
124
- delay = @retry_exceptions[exception_class] || 0
154
+ if \
155
+ !exception_class.nil? && \
156
+ instance_variable_defined?(:@retry_exceptions) && \
157
+ @retry_exceptions.is_a?(Hash)
158
+ delay = @retry_exceptions[exception_class] ||= begin
159
+ relevant_definitions = \
160
+ @retry_exceptions.select { |ex| exception_class <= ex }
161
+ relevant_definitions.any? ? relevant_definitions.sort.first[1] : 0
162
+ end
125
163
  # allow an array of delays.
126
164
  delay.is_a?(Array) ? delay[retry_attempt] || delay.last : delay
127
165
  else
@@ -131,7 +169,7 @@ module Resque
131
169
 
132
170
  # @abstract
133
171
  # Number of seconds to sleep after job is requeued
134
- #
172
+ #
135
173
  # @return [Number] number of seconds to sleep
136
174
  #
137
175
  # @api public
@@ -150,6 +188,16 @@ module Resque
150
188
  @retry_job_delegate ||= nil
151
189
  end
152
190
 
191
+ # @abstract
192
+ # Specify the queue that the job should be placed in upon failure
193
+ #
194
+ # @return [Symbol] Symbol representing queue that job should be placed in
195
+ #
196
+ # @api public
197
+ def retry_queue(exception, *args)
198
+ nil
199
+ end
200
+
153
201
  # @abstract
154
202
  # Modify the arguments used to retry the job. Use this to do something
155
203
  # other than try the exact same job again
@@ -158,15 +206,7 @@ module Resque
158
206
  #
159
207
  # @api public
160
208
  def retry_args(*args)
161
- # Here for backwards compatibility. If an "args_for_retry" method exists
162
- # invoke it, but warn that it is deprecated (and will be removed in a
163
- # future revision)
164
- if respond_to?(:args_for_retry)
165
- warn "`Resque::Plugins::Retry#args_for_retry` is deprecated, please use `Resque::Plugins::Retry#retry_args` instead."
166
- args_for_retry(*args)
167
- else
168
- args
169
- end
209
+ args.dup
170
210
  end
171
211
 
172
212
  # @abstract
@@ -238,7 +278,7 @@ module Resque
238
278
  #
239
279
  # @api public
240
280
  def retry_exceptions
241
- if @retry_exceptions.is_a?(Hash)
281
+ if instance_variable_defined?(:@retry_exceptions) && @retry_exceptions.is_a?(Hash)
242
282
  @retry_exceptions.keys
243
283
  else
244
284
  @retry_exceptions ||= nil
@@ -264,7 +304,7 @@ module Resque
264
304
  # if the retry limit was reached, dont bother checking anything else.
265
305
  if retry_limit_reached?
266
306
  log_message 'retry limit reached', args, exception
267
- return false
307
+ return false
268
308
  end
269
309
 
270
310
  # We always want to retry if the exception matches.
@@ -362,14 +402,27 @@ module Resque
362
402
 
363
403
  # some plugins define retry_delay and have it take no arguments, so rather than break those,
364
404
  # we'll just check here to see whether it takes the additional exception class argument or not
365
- temp_retry_delay = ([-1, 1].include?(method(:retry_delay).arity) ? retry_delay(exception.class) : retry_delay)
405
+ # we also allow all job args to be passed to a custom `retry_delay` method
406
+ retry_delay_arity = method(:retry_delay).arity
407
+
408
+ temp_retry_delay = if [-2, 2].include?(retry_delay_arity)
409
+ retry_delay(exception.class, *args)
410
+ elsif [-1, 1].include?(retry_delay_arity)
411
+ retry_delay(exception.class)
412
+ else
413
+ retry_delay
414
+ end
415
+
416
+ retry_job_class = retry_job_delegate ? retry_job_delegate : self
366
417
 
367
- retry_in_queue = retry_job_delegate ? retry_job_delegate : self
368
- log_message "retry delay: #{temp_retry_delay} for class: #{retry_in_queue}", args, exception
418
+ retry_in_queue = retry_queue(exception, *args)
419
+ retry_in_queue ||= Resque.queue_from_class(retry_job_class)
420
+
421
+ log_message "retry delay: #{temp_retry_delay} for queue: #{retry_in_queue}", args, exception
369
422
 
370
423
  # remember that this job is now being retried. before_perform_retry will increment
371
424
  # this so it represents the retry count, and MultipleWithRetrySuppression uses
372
- # the existence of this to determine if the job should be sent to the
425
+ # the existence of this to determine if the job should be sent to the
373
426
  # parent failure backend (e.g. failed queue) or not. Removing this means
374
427
  # jobs that fail before ::perform will be both retried and sent to the failed queue.
375
428
  Resque.redis.setnx(redis_retry_key(*args), -1)
@@ -378,9 +431,9 @@ module Resque
378
431
 
379
432
  if temp_retry_delay <= 0
380
433
  # If the delay is 0, no point passing it through the scheduler
381
- Resque.enqueue(retry_in_queue, *retry_args)
434
+ Resque.enqueue_to(retry_in_queue, retry_job_class, *retry_args)
382
435
  else
383
- Resque.enqueue_in(temp_retry_delay, retry_in_queue, *retry_args)
436
+ Resque.enqueue_in_with_queue(retry_in_queue, temp_retry_delay, retry_job_class, *retry_args)
384
437
  end
385
438
 
386
439
  # remove retry key from redis if we handed retry off to another queue.
@@ -406,6 +459,7 @@ module Resque
406
459
  #
407
460
  # @api private
408
461
  def before_perform_retry(*args)
462
+ return if Resque.inline?
409
463
  log_message 'before_perform_retry', args
410
464
  @on_failure_retry_hook_already_called = false
411
465
 
@@ -418,7 +472,8 @@ module Resque
418
472
  # set/update the "retry_key" expiration
419
473
  if expire_retry_key_after
420
474
  log_message "updating expiration for retry key: #{retry_key}", args
421
- Resque.redis.expire(retry_key, retry_delay + expire_retry_key_after)
475
+ exception_class = Object.const_get(args[0]) rescue nil
476
+ Resque.redis.expire(retry_key, retry_delay(exception_class) + expire_retry_key_after)
422
477
  end
423
478
  end
424
479
 
@@ -428,6 +483,7 @@ module Resque
428
483
  #
429
484
  # @api private
430
485
  def after_perform_retry(*args)
486
+ return if Resque.inline?
431
487
  log_message 'after_perform_retry, clearing retry key', args
432
488
  clean_retry_key(*args)
433
489
  end
@@ -443,16 +499,25 @@ module Resque
443
499
  #
444
500
  # @api private
445
501
  def on_failure_retry(exception, *args)
502
+ return if Resque.inline?
446
503
  log_message 'on_failure_retry', args, exception
447
504
  if exception.is_a?(Resque::DirtyExit)
448
505
  # This hook is called from a worker processes, not the job process
449
506
  # that failed with a DirtyExit, so @retry_attempt wasn't set yet
450
507
  @retry_attempt = Resque.redis.get(redis_retry_key(*args)).to_i
451
- elsif @on_failure_retry_hook_already_called
508
+ elsif instance_variable_defined?(:@on_failure_retry_hook_already_called) && \
509
+ @on_failure_retry_hook_already_called
452
510
  log_message 'on_failure_retry_hook_already_called', args, exception
453
511
  return
454
512
  end
455
513
 
514
+ # If we are "ignoring" the exception, then we decrement the retry
515
+ # counter, so that the current attempt didn't count toward the retry
516
+ # counter.
517
+ if ignore_exceptions.include?(exception.class)
518
+ @retry_attempt = Resque.redis.decr(redis_retry_key(*args))
519
+ end
520
+
456
521
  if retry_criteria_valid?(exception, *args)
457
522
  try_again(exception, *args)
458
523
  else
@@ -584,6 +649,10 @@ module Resque
584
649
  end
585
650
  end
586
651
 
652
+ def ignore_exceptions
653
+ @ignore_exceptions ||= []
654
+ end
655
+
587
656
  # Helper to call functions that may be passed as Symbols or Procs. If
588
657
  # a symbol, it is assumed to refer to a method that is already defined
589
658
  # on this class.
@@ -66,15 +66,23 @@ module ResqueRetry
66
66
  # cancels job retry
67
67
  def cancel_retry(job)
68
68
  klass = get_class(job)
69
- retry_key = retry_key_for_job(job)
70
- Resque.remove_delayed(klass, *job['args'])
71
- Resque.redis.del("failure-#{retry_key}")
72
- Resque.redis.del(retry_key)
69
+ if klass
70
+ retry_key = retry_key_for_job(job)
71
+ Resque.remove_delayed(klass, *job['args'])
72
+ Resque.redis.del("failure-#{retry_key}")
73
+ Resque.redis.del(retry_key)
74
+ else
75
+ raise 'cannot cancel, job not found'
76
+ end
73
77
  end
74
78
 
75
79
  private
76
80
  def get_class(job)
77
- Resque::Job.new(nil, nil).constantize(job['class'])
81
+ begin
82
+ Resque::Job.new(nil, nil).constantize(job['class'])
83
+ rescue
84
+ nil
85
+ end
78
86
  end
79
87
  end
80
88
 
@@ -1,3 +1,3 @@
1
1
  module ResqueRetry
2
- VERSION = '1.5.0'
2
+ VERSION = '1.7.6'
3
3
  end
data/resque-retry.gemspec CHANGED
@@ -25,11 +25,10 @@ Gem::Specification.new do |s|
25
25
  s.homepage = 'http://github.com/lantins/resque-retry'
26
26
  s.license = 'MIT'
27
27
 
28
- s.has_rdoc = false
29
28
  s.files = `git ls-files`.split($/)
30
29
  s.require_paths = %w[lib]
31
30
 
32
- s.add_dependency('resque', '~> 1.25')
31
+ s.add_dependency('resque', '>= 1.25', '< 3.0')
33
32
  s.add_dependency('resque-scheduler', '~> 4.0')
34
33
 
35
34
  s.add_development_dependency('rake', '~> 10.3')
@@ -128,4 +128,11 @@ class ExponentialBackoffTest < Minitest::Test
128
128
  assert_equal 4, Resque.info[:failed], 'failed jobs'
129
129
  assert_equal 0, Resque.info[:pending], 'pending jobs'
130
130
  end
131
+
132
+ def test_backoff_with_expiration
133
+ Resque.redis.expects(:expire)
134
+
135
+ Resque.enqueue(ExponentialBackoffWithExpiryJob)
136
+ perform_next_job(@worker)
137
+ end
131
138
  end
@@ -0,0 +1,26 @@
1
+ require 'test_helper'
2
+
3
+ class IgnoreExceptionsTest < Minitest::Test
4
+ def setup
5
+ Resque.redis.flushall
6
+ @worker = Resque::Worker.new(:testing)
7
+ @worker.register_worker
8
+ end
9
+
10
+ def test_ignore_exceptions
11
+ Resque.enqueue(IgnoreExceptionsJob)
12
+ retry_key = IgnoreExceptionsJob.redis_retry_key
13
+
14
+ IgnoreExceptionsJob.stubs(:perform).raises(AnotherCustomException)
15
+ perform_next_job(@worker)
16
+ assert_equal '0', Resque.redis.get(retry_key), 'retry counter'
17
+
18
+ IgnoreExceptionsJob.stubs(:perform).raises(AnotherCustomException)
19
+ perform_next_job(@worker)
20
+ assert_equal '1', Resque.redis.get(retry_key), 'retry counter'
21
+
22
+ IgnoreExceptionsJob.stubs(:perform).raises(CustomException)
23
+ perform_next_job(@worker)
24
+ assert_equal '1', Resque.redis.get(retry_key), 'retry counter'
25
+ end
26
+ end
@@ -1,4 +1,4 @@
1
- require 'test_helper'
1
+ require_relative './test_helper'
2
2
 
3
3
  # Mock failure backend for testing MultipleWithRetrySuppression
4
4
  class MockFailureBackend < Resque::Failure::Base
@@ -14,7 +14,6 @@ class MockFailureBackend < Resque::Failure::Base
14
14
  end
15
15
 
16
16
  class MultipleFailureTest < Minitest::Test
17
-
18
17
  def setup
19
18
  Resque.redis.flushall
20
19
  @worker = Resque::Worker.new(:testing)
@@ -26,9 +25,8 @@ class MultipleFailureTest < Minitest::Test
26
25
  Resque::Failure.backend = Resque::Failure::MultipleWithRetrySuppression
27
26
  end
28
27
 
29
- def failure_key_for(klass)
30
- args = []
31
- key = 'failure-' + klass.redis_retry_key(args)
28
+ def failure_key_for(klass, *args)
29
+ Resque::Failure::MultipleWithRetrySuppression.failure_key(klass.redis_retry_key(args))
32
30
  end
33
31
 
34
32
  def test_failure_is_passed_on_when_job_class_not_found
@@ -41,7 +39,9 @@ class MultipleFailureTest < Minitest::Test
41
39
  perform_next_job(@worker)
42
40
 
43
41
  assert_equal 1, MockFailureBackend.errors.count, 'should have one error'
44
- assert_match /uninitialized constant.* LimitThreeJobTemp/, MockFailureBackend.errors.first
42
+
43
+ uninitialized_constant_pattern = /uninitialized constant.* LimitThreeJobTemp/
44
+ assert_match uninitialized_constant_pattern, MockFailureBackend.errors.first
45
45
  end
46
46
 
47
47
  def test_last_failure_is_saved_in_redis_if_delay
@@ -53,6 +53,22 @@ class MultipleFailureTest < Minitest::Test
53
53
  assert Resque.redis.exists(key)
54
54
  end
55
55
 
56
+ def test_retry_delay_is_calculated_with_custom_calculation
57
+ delay = 5
58
+ Resque.enqueue(DynamicDelayedJobOnExceptionAndArgs, delay.to_s)
59
+ perform_next_job(@worker)
60
+
61
+ key = failure_key_for(DynamicDelayedJobOnExceptionAndArgs, delay.to_s)
62
+ ttl = Resque.redis.ttl(key)
63
+ assert Resque.redis.exists(key)
64
+ assert MockFailureBackend.errors.size == 0
65
+
66
+ # expiration on failure_key is set to 2x the delay
67
+ # to ensure the customized delay is properly calculated using
68
+ # dynamic retry_delay method on the job
69
+ assert ttl > delay && delay <= (delay * 2)
70
+ end
71
+
56
72
  def test_retry_key_splatting_args
57
73
  # were expecting this to be called three times:
58
74
  # - once when we queue the job to try again
@@ -141,6 +157,20 @@ class MultipleFailureTest < Minitest::Test
141
157
  end
142
158
  end
143
159
 
160
+ def test_redis_exists_returns_integer
161
+ Resque.enqueue(RetryDefaultsJob)
162
+ original = Redis.exists_returns_integer
163
+ Redis.exists_returns_integer = true
164
+
165
+ 3.times do
166
+ perform_next_job(@worker)
167
+ end
168
+
169
+ Redis.exists_returns_integer = original
170
+
171
+ assert_equal 1, MockFailureBackend.errors.size
172
+ end
173
+
144
174
  def teardown
145
175
  Resque::Failure.backend = @old_failure_backend
146
176
  end
@@ -0,0 +1,23 @@
1
+ require 'test_helper'
2
+
3
+ class ResqueInlineTest < Minitest::Test
4
+ def setup
5
+ Resque.inline = true
6
+ Resque.expects(:redis).never
7
+ end
8
+
9
+ def teardown
10
+ Resque.inline = false
11
+ end
12
+
13
+ def test_runs_inline
14
+ GoodJob.expects :perform
15
+ Resque.enqueue(GoodJob)
16
+ end
17
+
18
+ def test_fails_inline
19
+ assert_raises CustomException do
20
+ Resque.enqueue(RetryCustomExceptionsJob, 'CustomException')
21
+ end
22
+ end
23
+ end
data/test/resque_test.rb CHANGED
@@ -1,11 +1,15 @@
1
1
  require 'test_helper'
2
2
 
3
- # make sure the worlds not fallen from beneith us.
4
3
  class ResqueTest < Minitest::Test
5
4
  def test_resque_version
6
- major, minor, patch = Resque::Version.split('.')
7
- assert_equal 1, major.to_i, 'major version does not match'
8
- assert_operator minor.to_i, :>=, 8, 'minor version is too low'
5
+ major, minor, _ = Resque::Version.split('.')
6
+ assert [1, 2].include?(major.to_i), 'major version does not match'
7
+
8
+ if major.to_i == 1
9
+ assert_operator minor.to_i, :>=, 25, 'minor version is too low'
10
+ else
11
+ assert_operator minor.to_i, :>=, 0, 'minor version is too low'
12
+ end
9
13
  end
10
14
 
11
15
  def test_good_job
@@ -0,0 +1,23 @@
1
+ require 'test_helper'
2
+
3
+ class RetryCustomDelayTest < Minitest::Test
4
+ def setup
5
+ Resque.redis.flushall
6
+ @worker = Resque::Worker.new(:testing)
7
+ @worker.register_worker
8
+ end
9
+
10
+ def test_delay_with_exception
11
+ Resque.enqueue(DynamicDelayedJobOnException, 'arg1')
12
+ Resque.expects(:enqueue_in_with_queue).with(:testing, 4, DynamicDelayedJobOnException, 'arg1')
13
+
14
+ perform_next_job(@worker)
15
+ end
16
+
17
+ def test_delay_with_exception_and_args
18
+ Resque.enqueue(DynamicDelayedJobOnExceptionAndArgs, '3')
19
+ Resque.expects(:enqueue_in_with_queue).with(:testing, 3, DynamicDelayedJobOnExceptionAndArgs, '3')
20
+
21
+ perform_next_job(@worker)
22
+ end
23
+ end