resque_unit 0.4.8 → 1.0.0.beta.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1aad0f765a2b78da6d5f17cdbf2f5646066d8b8d
4
- data.tar.gz: c694a2f46b3ec5892f7a83ebfae9d734dd0371ec
3
+ metadata.gz: d6be35403595c85939faf33cbacfaa990dfadd1e
4
+ data.tar.gz: e4891ea3f28fee039c921bd4c8f6264d9c2468da
5
5
  SHA512:
6
- metadata.gz: 812552beeb3b8c3de00be72b6c13b8dfc4fd2808df1114f308c3eb5aaba2635108d58d9583c5a8c879248b91b716c0bf21c38fad6b2d1453a0405c272daa3f3b
7
- data.tar.gz: 97110758541b5c3335906743fdd0dbfd2f1514106ec90ea98dbb9149bef0f86b0932974cc2ed2455d9ea119fffae551aaf9764302ab64101195f4ae3977dec8a
6
+ metadata.gz: db13bb8dd32e9cae5abfed2c9cb5e3162fef21186ca46e829b4bc3959e3a5d362dc562a223766853d2000f220cd95a9abf843e6b7e0f9e0c3736b4680baa629d
7
+ data.tar.gz: da321605d44889632e4adb15a5c7178ce28d0b3f1801d852bce8263b396d82b5c05efc691d986b0b8403e4fe949e348608281dfaf2ffa75b461b8c78918a6389
data/README.md CHANGED
@@ -3,7 +3,7 @@ ResqueUnit
3
3
 
4
4
  ResqueUnit provides some extra assertions and a mock Resque for
5
5
  testing Rails code that depends on Resque. You can install it as
6
- either a gem or a plugin:
6
+ either a gem:
7
7
 
8
8
  gem install resque_unit
9
9
 
@@ -11,12 +11,6 @@ and in your test.rb:
11
11
 
12
12
  config.gem 'resque_unit'
13
13
 
14
- If you'd rather install it as a plugin, you should be able to run
15
-
16
- script/plugin install git://github.com/justinweiss/resque_unit.git
17
-
18
- inside your Rails projects.
19
-
20
14
  Examples
21
15
  ========
22
16
 
@@ -103,10 +97,6 @@ Caveats
103
97
 
104
98
  * You should make sure that you call `Resque.reset!` in your test's
105
99
  setup method to clear all of the test queues.
106
- * Hooks support is optional. Just because you probably don't want to call
107
- them during unit tests if they play with a DB. Call `Resque.enable_hooks!`
108
- in your tests's setup method to enable hooks. To disable hooks, call
109
- `Resque.disable_hooks!`.
110
100
 
111
101
  Resque-Scheduler Support
112
102
  ========================
data/lib/resque_unit.rb CHANGED
@@ -6,14 +6,12 @@ begin
6
6
  rescue LoadError
7
7
  require 'json'
8
8
  end
9
-
9
+ require 'resque'
10
10
  require 'resque_unit/helpers'
11
11
  require 'resque_unit/resque'
12
- require 'resque_unit/errors'
13
12
  require 'resque_unit/assertions'
14
- require 'resque_unit/plugin'
15
13
 
16
- if defined?(Test::Unit)
14
+ if defined?(Test::Unit::TestCase)
17
15
  Test::Unit::TestCase.send(:include, ResqueUnit::Assertions)
18
16
  end
19
17
 
@@ -1,7 +1,6 @@
1
1
  # These are a group of assertions you can use in your unit tests to
2
2
  # verify that your code is using Resque correctly.
3
3
  module ResqueUnit::Assertions
4
-
5
4
  # Asserts that +klass+ has been queued into its appropriate queue at
6
5
  # least once. If +args+ is nil, it only asserts that the klass has
7
6
  # been queued. Otherwise, it asserts that the klass has been queued
@@ -9,14 +8,14 @@ module ResqueUnit::Assertions
9
8
  # want to assert that klass has been queued without arguments. Pass a block
10
9
  # if you want to assert something was queued within its execution.
11
10
  def assert_queued(klass, args = nil, message = nil, &block)
12
- queue_name = Resque.queue_for(klass)
11
+ queue_name = Resque.queue_from_class(klass)
13
12
  assert_job_created(queue_name, klass, args, message, &block)
14
13
  end
15
14
  alias assert_queues assert_queued
16
15
 
17
16
  # The opposite of +assert_queued+.
18
17
  def assert_not_queued(klass = nil, args = nil, message = nil, &block)
19
- queue_name = Resque.queue_for(klass)
18
+ queue_name = Resque.queue_from_class(klass)
20
19
 
21
20
  queue = if block_given?
22
21
  snapshot = Resque.size(queue_name)
@@ -32,9 +31,9 @@ module ResqueUnit::Assertions
32
31
 
33
32
  # Asserts no jobs were queued within the block passed.
34
33
  def assert_nothing_queued(message = nil, &block)
35
- snapshot = Resque.size
34
+ snapshot = total_job_count
36
35
  yield
37
- present = Resque.size
36
+ present = total_job_count
38
37
  assert_equal snapshot, present, message || "No jobs should have been queued"
39
38
  end
40
39
 
@@ -54,6 +53,11 @@ module ResqueUnit::Assertions
54
53
 
55
54
  private
56
55
 
56
+ # The total count of all the jobs in Resque.
57
+ def total_job_count
58
+ Resque.queues.inject(0) { |acc, queue| acc + Resque.size(queue) }
59
+ end
60
+
57
61
  # In Test::Unit, +assert_block+ displays only the message on a test
58
62
  # failure and +assert+ always appends a message to the end of the
59
63
  # passed-in assertion message. In MiniTest, it's the other way
@@ -1,224 +1,15 @@
1
- # The fake Resque class. This needs to be loaded after the real Resque
2
- # for the assertions in +ResqueUnit::Assertions+ to work.
3
- module Resque
4
- include ResqueUnit::Helpers
5
- extend self
6
-
7
- # Resets all the queues to the empty state. This should be called in
8
- # your test's +setup+ method until I can figure out a way for it to
9
- # automatically be called.
10
- #
11
- # If <tt>queue_name</tt> is given, then resets only that queue.
12
- def reset!(queue_name = nil)
13
- if @queue && queue_name
14
- @queue[queue_name] = []
15
- else
16
- @queue = Hash.new { |h, k| h[k] = [] }
17
- end
18
- end
19
-
20
- # Returns a hash of all the queue names and jobs that have been queued. The
21
- # format is <tt>{queue_name => [job, ..]}</tt>.
22
- def self.queues
23
- @queue || reset!
24
- end
25
-
26
- # Returns an array of all the jobs that have been queued. Each
27
- # element is of the form +{"class" => klass, "args" => args}+ where
28
- # +klass+ is the job's class and +args+ is an array of the arguments
29
- # passed to the job.
30
- def queue(queue_name)
31
- queues[queue_name]
32
- end
33
-
34
- # Return an array of all jobs' payloads for queue
35
- # Elements are decoded
36
- def all(queue_name)
37
- result = list_range(queue_name, 0, size(queue_name))
38
- result.is_a?(Array) ? result : [ result ]
39
- end
40
-
41
- # Returns an array of jobs' payloads for queue.
42
- #
43
- # start and count should be integer and can be used for pagination.
44
- # start is the item to begin, count is how many items to return.
45
- #
46
- # To get the 3rd page of a 30 item, paginatied list one would use:
47
- # Resque.peek('my_list', 59, 30)
48
- def peek(queue_name, start = 0, count = 1)
49
- list_range(queue_name, start, count)
50
- end
51
-
52
- # Gets a range of jobs' payloads from queue.
53
- # Returns single element if count equal 1
54
- # Elements are decoded
55
- def list_range(key, start = 0, count = 1)
56
- data = if count == 1
57
- decode(queues[key][start])
58
- else
59
- (queues[key][start...start + count] || []).map { |entry| decode(entry) }
60
- end
61
- end
62
-
63
- # Yes, all Resque hooks!
64
- def enable_hooks!
65
- @hooks_enabled = true
66
- end
67
-
68
- def disable_hooks!
69
- @hooks_enabled = nil
70
- end
71
-
72
- # Executes all jobs in all queues in an undefined order.
73
- def run!
74
- payloads = []
75
- @queue.each do |queue_name, queue|
76
- payloads.concat queue.slice!(0, queue.size)
77
- end
78
- exec_payloads payloads.shuffle
79
- end
80
-
81
- def run_for!(queue_name, limit=false)
82
- queue = @queue[queue_name]
83
- exec_payloads queue.slice!(0, ( limit ? limit : queue.size) ).shuffle
84
- end
85
-
86
- def exec_payloads(raw_payloads)
87
- raw_payloads.each do |raw_payload|
88
- job_payload = decode(raw_payload)
89
- @hooks_enabled ? perform_with_hooks(job_payload) : perform_without_hooks(job_payload)
90
- end
91
- end
92
- private :exec_payloads
93
-
94
- # 1. Execute all jobs in all queues in an undefined order,
95
- # 2. Check if new jobs were announced, and execute them.
96
- # 3. Repeat 3
97
- def full_run!
98
- run! until empty_queues?
99
- end
100
-
101
- # Returns the size of the given queue
102
- def size(queue_name = nil)
103
- if queue_name
104
- queues[queue_name].length
105
- else
106
- queues.values.flatten.length
107
- end
108
- end
109
-
110
- # :nodoc:
111
- def enqueue(klass, *args)
112
- enqueue_to( queue_for(klass), klass, *args)
113
- end
114
-
115
- # :nodoc:
116
- def enqueue_to( queue_name, klass, *args )
117
- # Behaves like Resque, raise if no queue was specifed
118
- raise NoQueueError.new("Jobs must be placed onto a queue.") unless queue_name
119
- enqueue_unit(queue_name, {"class" => klass.to_s, "args" => args })
120
- end
121
-
122
- # :nodoc:
123
- def queue_for(klass)
124
- klass.instance_variable_get(:@queue) || (klass.respond_to?(:queue) && klass.queue)
125
- end
126
- alias :queue_from_class :queue_for
127
-
128
- # :nodoc:
129
- def empty_queues?
130
- queues.all? do |k, v|
131
- v.empty?
132
- end
133
- end
134
-
135
- def enqueue_unit(queue_name, hash)
136
- klass = constantize(hash["class"])
137
- if @hooks_enabled
138
- before_hooks = Plugin.before_enqueue_hooks(klass).map do |hook|
139
- klass.send(hook, *hash["args"])
140
- end
141
- return nil if before_hooks.any? { |result| result == false }
142
- end
143
- queue(queue_name) << encode(hash)
144
- if @hooks_enabled
145
- Plugin.after_enqueue_hooks(klass).each do |hook|
146
- klass.send(hook, *hash["args"])
147
- end
148
- end
149
- queue(queue_name).size
150
- end
151
-
152
- # Call perform on the job class
153
- def perform_without_hooks(job_payload)
154
- constantize(job_payload["class"]).perform(*job_payload["args"])
155
- end
156
-
157
- # Call perform on the job class, and adds support for Resque hooks.
158
- def perform_with_hooks(job_payload)
159
- job_class = constantize(job_payload["class"])
160
- before_hooks = Resque::Plugin.before_hooks(job_class)
161
- around_hooks = Resque::Plugin.around_hooks(job_class)
162
- after_hooks = Resque::Plugin.after_hooks(job_class)
163
- failure_hooks = Resque::Plugin.failure_hooks(job_class)
164
-
165
- begin
166
- # Execute before_perform hook. Abort the job gracefully if
167
- # Resque::DontPerform is raised.
168
- begin
169
- before_hooks.each do |hook|
170
- job_class.send(hook, *job_payload["args"])
171
- end
172
- rescue Resque::Job::DontPerform
173
- return false
174
- end
175
-
176
- # Execute the job. Do it in an around_perform hook if available.
177
- if around_hooks.empty?
178
- perform_without_hooks(job_payload)
179
- job_was_performed = true
180
- else
181
- # We want to nest all around_perform plugins, with the last one
182
- # finally calling perform
183
- stack = around_hooks.reverse.inject(nil) do |last_hook, hook|
184
- if last_hook
185
- lambda do
186
- job_class.send(hook, *job_payload["args"]) { last_hook.call }
187
- end
188
- else
189
- lambda do
190
- job_class.send(hook, *job_payload["args"]) do
191
- result = perform_without_hooks(job_payload)
192
- job_was_performed = true
193
- result
194
- end
195
- end
196
- end
197
- end
198
- stack.call
199
- end
200
-
201
- # Execute after_perform hook
202
- after_hooks.each do |hook|
203
- job_class.send(hook, *job_payload["args"])
204
- end
205
-
206
- # Return true if the job was performed
207
- return job_was_performed
208
-
209
- # If an exception occurs during the job execution, look for an
210
- # on_failure hook then re-raise.
211
- rescue Object => e
212
- failure_hooks.each { |hook| job_class.send(hook, e, *job_payload["args"]) }
213
- raise e
214
- end
215
- end
216
-
217
- class Job
218
- extend ResqueUnit::Helpers
219
- def self.create(queue, klass_name, *args)
220
- Resque.enqueue_unit(queue, {"class" => constantize(klass_name).to_s, "args" => args})
221
- end
222
- end
223
-
224
- end
1
+ require 'resque'
2
+ require 'resque_unit/resque/test_extensions'
3
+
4
+ # This is a little weird. Fakeredis registers itself as the default
5
+ # redis driver after you load it. This might not be what you want,
6
+ # though -- resque_unit needs fakeredis, but you may have a reason to
7
+ # use a different redis driver for the rest of your test code. So
8
+ # we'll store the old default here, and restore it afer we're done
9
+ # loading fakeredis. Then, we'll point resque at fakeredis
10
+ # specifically.
11
+ default_redis_driver = Redis::Connection.drivers.pop
12
+ require 'fakeredis'
13
+ Redis::Connection.drivers << default_redis_driver if default_redis_driver
14
+
15
+ Resque.extend Resque::TestExtensions
@@ -0,0 +1,68 @@
1
+ module Resque
2
+ module TestExtensions
3
+ include ResqueUnit::Helpers
4
+
5
+ # A redis connection that always uses fakeredis.
6
+ def fake_redis
7
+ @fake_redis ||= Redis.new(driver: :memory)
8
+ end
9
+
10
+ # Always return the fake redis.
11
+ def redis
12
+ fake_redis
13
+ end
14
+
15
+ # Resets all the queues to the empty state. This should be called in
16
+ # your test's +setup+ method until I can figure out a way for it to
17
+ # automatically be called.
18
+ #
19
+ # If <tt>queue_name</tt> is given, then resets only that queue.
20
+ def reset!(queue = nil)
21
+ if queue
22
+ remove_queue(queue)
23
+ else
24
+ redis.flushall
25
+ end
26
+ end
27
+
28
+ # Return an array of all jobs' payloads for queue
29
+ # Elements are decoded
30
+ def all(queue_name)
31
+ jobs = peek(queue_name, 0, size(queue_name))
32
+ jobs.kind_of?(Array) ? jobs : [jobs]
33
+ end
34
+ alias queue all
35
+
36
+ # Executes all jobs in all queues in an undefined order.
37
+ def run!
38
+ payloads = []
39
+ queues.each do |queue|
40
+ size(queue).times { payloads << pop(queue) }
41
+ end
42
+ exec_payloads payloads.shuffle
43
+ end
44
+
45
+ def run_for!(queue, limit = Float::INFINITY)
46
+ job_count = [limit, size(queue)].min
47
+ payloads = []
48
+
49
+ job_count.times { payloads << pop(queue) }
50
+ exec_payloads payloads.shuffle
51
+ end
52
+
53
+ def exec_payloads(raw_payloads)
54
+ raw_payloads.each do |raw_payload|
55
+ Resque::Job.new(:inline, raw_payload).perform
56
+ end
57
+ end
58
+
59
+ private :exec_payloads
60
+
61
+ # 1. Execute all jobs in all queues in an undefined order,
62
+ # 2. Check if new jobs were announced, and execute them.
63
+ # 3. Repeat 3
64
+ def full_run!
65
+ run! until queues.all? { |queue| size(queue) == 0 }
66
+ end
67
+ end
68
+ end
@@ -3,7 +3,7 @@
3
3
  require 'time'
4
4
 
5
5
  module ResqueUnit::SchedulerAssertions
6
-
6
+
7
7
  # Asserts that +klass+ has been queued into its appropriate queue at
8
8
  # least once, with a +timestamp+ less than or equal to
9
9
  # +expected_timestamp+. If the job wasn't queued with a timestamp,
@@ -13,7 +13,7 @@ module ResqueUnit::SchedulerAssertions
13
13
  # +args+ if you want to assert that klass has been queued without
14
14
  # arguments.
15
15
  def assert_queued_at(expected_timestamp, klass, args = nil, message = nil)
16
- queue = Resque.queue_for(klass)
16
+ queue = Resque.queue_from_class(klass)
17
17
  assert in_timestamped_queue?(queue, expected_timestamp, klass, args),
18
18
  (message || "#{klass} should have been queued in #{queue} before #{expected_timestamp}: #{Resque.queue(queue).inspect}.")
19
19
  end
@@ -23,10 +23,10 @@ module ResqueUnit::SchedulerAssertions
23
23
  def assert_queued_in(expected_time_difference, klass, args = nil, message = nil)
24
24
  assert_queued_at(Time.now + expected_time_difference, klass, args, message)
25
25
  end
26
-
26
+
27
27
  # opposite of +assert_queued_at+
28
28
  def assert_not_queued_at(expected_timestamp, klass, args = nil, message = nil)
29
- queue = Resque.queue_for(klass)
29
+ queue = Resque.queue_from_class(klass)
30
30
  assert !in_timestamped_queue?(queue, expected_timestamp, klass, args),
31
31
  (message || "#{klass} should not have been queued in #{queue} before #{expected_timestamp}.")
32
32
  end
@@ -38,10 +38,24 @@ module ResqueUnit::SchedulerAssertions
38
38
 
39
39
  private
40
40
 
41
- def in_timestamped_queue?(queue_name, expected_timestamp, klass, args = nil)
41
+ def in_queue?(queue, klass, args = nil)
42
+ super(queue, klass, args) || !matching_jobs(all_jobs_scheduled_before_or_at(:forever), klass, args).empty?
43
+ end
44
+
45
+ def in_timestamped_queue?(queue_name, max_timestamp, klass, args = nil)
42
46
  # check if we have any matching jobs with a timestamp less than
43
47
  # expected_timestamp
44
- !matching_jobs(Resque.all(queue_name), klass, args).select {|e| e["timestamp"] && Time.parse(e["timestamp"]) <= expected_timestamp}.empty?
48
+ !matching_jobs(all_jobs_scheduled_before_or_at(max_timestamp), klass, args).empty?
45
49
  end
46
-
50
+
51
+ def all_jobs_scheduled_before_or_at(max_timestamp = :forever)
52
+ timestamps = Resque.delayed_queue_peek(0, Resque.delayed_queue_schedule_size).map(&:to_i)
53
+
54
+ if max_timestamp != :forever
55
+ timestamps.select! { |timestamp| Time.at(timestamp) <= Time.at(max_timestamp) }
56
+ end
57
+
58
+ timestamps.flat_map { |timestamp| Resque.delayed_timestamp_peek(timestamp, 0, Resque.delayed_timestamp_size(timestamp)) }
59
+ end
60
+
47
61
  end