resque-clues 0.1.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.
- data/.gitignore +20 -0
- data/.travis.yml +15 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +163 -0
- data/Rakefile +6 -0
- data/TODO +4 -0
- data/lib/resque-clues.rb +78 -0
- data/lib/resque/plugins/clues/event_publisher.rb +91 -0
- data/lib/resque/plugins/clues/job_extension.rb +65 -0
- data/lib/resque/plugins/clues/queue_extension.rb +69 -0
- data/lib/resque/plugins/clues/util.rb +45 -0
- data/lib/resque/plugins/clues/version.rb +5 -0
- data/resque-clues.gemspec +26 -0
- data/spec/event_publisher_spec.rb +193 -0
- data/spec/integration_spec.rb +183 -0
- data/spec/job_extension_spec.rb +100 -0
- data/spec/queue_extension_spec.rb +137 -0
- data/spec/spec_helper.rb +73 -0
- data/spec/test_publisher.rb +42 -0
- data/spec/test_worker.rb +6 -0
- data/spec/util_spec.rb +31 -0
- metadata +193 -0
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
module Resque
|
5
|
+
module Plugins
|
6
|
+
module Clues
|
7
|
+
# Module capable of redefining the Resque#push and Resque#pop methods so
|
8
|
+
# that:
|
9
|
+
#
|
10
|
+
# * metadata will be stored in redis.
|
11
|
+
# * The metadata can be injected with arbitrary data by a configured item
|
12
|
+
# preprocessor.
|
13
|
+
# * That event data (including its metadata) will be published, provided
|
14
|
+
# an event publisher has been configured.
|
15
|
+
module QueueExtension
|
16
|
+
# push an item onto the queue. If resque-clues is configured, this
|
17
|
+
# will First create the metadata associated with the event and adds it
|
18
|
+
# to the item. This will include:
|
19
|
+
#
|
20
|
+
# * event_hash: a unique item identifying the job, will be included
|
21
|
+
# with other events arising from that job.
|
22
|
+
# * hostname: the hostname of the machine where the event occurred.
|
23
|
+
# * process: The process id of the ruby process where the event
|
24
|
+
# occurred.
|
25
|
+
# * plus any items injected into the item via a configured
|
26
|
+
# item_preprocessor.
|
27
|
+
#
|
28
|
+
# After that, an enqueued event is published and the original push
|
29
|
+
# operation is invoked.
|
30
|
+
#
|
31
|
+
# queue:: The queue to push onto
|
32
|
+
# orig:: The original item to push onto the queue.
|
33
|
+
def _clues_push(queue, item)
|
34
|
+
return _base_push(queue, item) unless Clues.clues_configured?
|
35
|
+
item['clues_metadata'] = {
|
36
|
+
'event_hash' => Clues.event_hash,
|
37
|
+
'hostname' => Clues.hostname,
|
38
|
+
'process' => Clues.process,
|
39
|
+
'enqueued_time' => Time.now.utc.to_f
|
40
|
+
}
|
41
|
+
if Resque::Plugins::Clues.item_preprocessor
|
42
|
+
Resque::Plugins::Clues.item_preprocessor.call(queue, item)
|
43
|
+
end
|
44
|
+
Clues.event_publisher.publish(:enqueued, Clues.now, queue, item['clues_metadata'], item[:class], *item[:args])
|
45
|
+
_base_push(queue, item)
|
46
|
+
end
|
47
|
+
|
48
|
+
# pops an item off the head of the queue. This will use the original
|
49
|
+
# pop operation to get the item, then calculate the time in queue and
|
50
|
+
# broadcast a dequeued event.
|
51
|
+
#
|
52
|
+
# queue:: The queue to pop from.
|
53
|
+
def _clues_pop(queue)
|
54
|
+
_base_pop(queue).tap do |item|
|
55
|
+
# TODO refactor
|
56
|
+
unless item.nil?
|
57
|
+
if Clues.clues_configured? and item['clues_metadata']
|
58
|
+
item['clues_metadata']['hostname'] = Clues.hostname
|
59
|
+
item['clues_metadata']['process'] = Clues.process
|
60
|
+
item['clues_metadata']['time_in_queue'] = Clues.time_delta_since(item['clues_metadata']['enqueued_time'])
|
61
|
+
Clues.event_publisher.publish(:dequeued, Clues.now, queue, item['clues_metadata'], item['class'], *item['args'])
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'delegate'
|
3
|
+
|
4
|
+
module Resque
|
5
|
+
module Plugins
|
6
|
+
module Clues
|
7
|
+
# A unique event hash crafted from the hostname, process and time.
|
8
|
+
def self.event_hash
|
9
|
+
Digest::MD5.hexdigest("#{hostname}#{process}#{Time.now.utc.to_f}")
|
10
|
+
end
|
11
|
+
|
12
|
+
# The hostname Resque is running on.
|
13
|
+
def self.hostname
|
14
|
+
@hostname ||= Socket.gethostname
|
15
|
+
end
|
16
|
+
|
17
|
+
# The process id.
|
18
|
+
def self.process
|
19
|
+
$$
|
20
|
+
end
|
21
|
+
|
22
|
+
# The current UTC time in iso 8601 format.
|
23
|
+
def self.now
|
24
|
+
Time.now.utc.iso8601
|
25
|
+
end
|
26
|
+
|
27
|
+
# The delta of time between the passed time and now.
|
28
|
+
def self.time_delta_since(start)
|
29
|
+
result = Time.now.utc.to_f - start.to_f
|
30
|
+
result >= 0.0 ? result : 0.0
|
31
|
+
end
|
32
|
+
|
33
|
+
# Convenience method to determine if resque-clues is properly
|
34
|
+
# configured.
|
35
|
+
def self.clues_configured?
|
36
|
+
Resque::Plugins::Clues.configured?
|
37
|
+
end
|
38
|
+
|
39
|
+
# Convenience method to access the resque-clues event publisher.
|
40
|
+
def self.event_publisher
|
41
|
+
Resque::Plugins::Clues.event_publisher
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'resque/plugins/clues/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "resque-clues"
|
8
|
+
gem.version = Resque::Clues::VERSION
|
9
|
+
gem.authors = ["Lance Woodson"]
|
10
|
+
gem.email = ["lance.woodson@peopleadmin.com"]
|
11
|
+
gem.description = %q{Adds event publishing and job tracking ability to Resque}
|
12
|
+
gem.summary = %q{Adds event publishing and job tracking}
|
13
|
+
gem.homepage = "https://github.com/PeopleAdmin/resque-clues"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
gem.add_dependency 'resque', '>= 1.20.0'
|
20
|
+
gem.add_dependency 'multi_json', '~> 1.7.4'
|
21
|
+
gem.add_development_dependency 'rake', '~> 0.9.2.2'
|
22
|
+
gem.add_development_dependency 'rspec'
|
23
|
+
gem.add_development_dependency 'pry'
|
24
|
+
gem.add_development_dependency 'pry-debugger'
|
25
|
+
gem.add_development_dependency 'cane'
|
26
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'stringio'
|
3
|
+
require 'json'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'time'
|
6
|
+
require 'tmpdir'
|
7
|
+
|
8
|
+
describe 'event publishers' do
|
9
|
+
|
10
|
+
before do
|
11
|
+
@current_time = Time.now.utc.iso8601
|
12
|
+
end
|
13
|
+
|
14
|
+
def publish_event_type(type)
|
15
|
+
@publisher.publish(type, @current_time, :test_queue, {}, "FooBar", "a", "b")
|
16
|
+
end
|
17
|
+
|
18
|
+
describe Resque::Plugins::Clues::StreamPublisher do
|
19
|
+
before do
|
20
|
+
@stream = StreamIO.new
|
21
|
+
@publisher = Resque::Plugins::Clues::StreamPublisher.new(@stream)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe Resque::Plugins::Clues::StreamPublisher do
|
26
|
+
def verify_output_for_event_type(type)
|
27
|
+
@stream.rewind
|
28
|
+
event = MultiJson.load(@stream.readlines[-1].chomp)
|
29
|
+
event["queue"].should == "test_queue"
|
30
|
+
event["metadata"].should == {}
|
31
|
+
event["timestamp"].should_not be_nil
|
32
|
+
event["event_type"].should == type.to_s
|
33
|
+
event["worker_class"].should == "FooBar"
|
34
|
+
event["args"].should == ["a", "b"]
|
35
|
+
end
|
36
|
+
|
37
|
+
before do
|
38
|
+
@stream = StringIO.new
|
39
|
+
@publisher = Resque::Plugins::Clues::StreamPublisher.new(@stream)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should pass Resque lint detection" do
|
43
|
+
Resque::Plugin.lint(Resque::Plugins::Clues::StandardOutPublisher)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should send enqueued event to STDOUT" do
|
47
|
+
publish_event_type :enqueued
|
48
|
+
verify_output_for_event_type :enqueued
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should send dequeued event to STDOUT" do
|
52
|
+
publish_event_type :dequeued
|
53
|
+
verify_output_for_event_type :dequeued
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should send perform_started event to STDOUT" do
|
57
|
+
publish_event_type :perform_started
|
58
|
+
verify_output_for_event_type :perform_started
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should send perform_finished event to STDOUT" do
|
62
|
+
publish_event_type :perform_finished
|
63
|
+
verify_output_for_event_type :perform_finished
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should send failed event to STDOUT" do
|
67
|
+
publish_event_type :failed
|
68
|
+
verify_output_for_event_type :failed
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should send destroyed event to STDOUT" do
|
72
|
+
publish_event_type :destroyed
|
73
|
+
verify_output_for_event_type :destroyed
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
describe Resque::Plugins::Clues::LogPublisher do
|
79
|
+
def verify_event_written_to_log(event_type)
|
80
|
+
last_event["event_type"].should == event_type.to_s
|
81
|
+
last_event["timestamp"].should == @current_time.to_s
|
82
|
+
last_event["queue"].should == 'test_queue'
|
83
|
+
last_event["metadata"].should == {}
|
84
|
+
last_event["worker_class"].should == "FooBar"
|
85
|
+
last_event["args"].should == ["a", "b"]
|
86
|
+
end
|
87
|
+
|
88
|
+
def last_event
|
89
|
+
MultiJson.load(File.readlines(@log_path)[-1].chomp)
|
90
|
+
end
|
91
|
+
|
92
|
+
before do
|
93
|
+
@log_path = File.join(Dir.tmpdir, "test_log.log")
|
94
|
+
FileUtils.rm(@log_path) if File.exists?(@log_path)
|
95
|
+
@publisher = Resque::Plugins::Clues::LogPublisher.new(@log_path)
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should pass Resque lint detection" do
|
99
|
+
Resque::Plugin.lint(Resque::Plugins::Clues::LogPublisher)
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should write enqueued event to file" do
|
103
|
+
publish_event_type :enqueued
|
104
|
+
verify_event_written_to_log :enqueued
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should write dequeued event to file" do
|
108
|
+
publish_event_type :dequeued
|
109
|
+
verify_event_written_to_log :dequeued
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should write destroyed event to file" do
|
113
|
+
publish_event_type :destroyed
|
114
|
+
verify_event_written_to_log :destroyed
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should write perform_started event to file" do
|
118
|
+
publish_event_type :perform_started
|
119
|
+
verify_event_written_to_log :perform_started
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should write perform_finished event to file" do
|
123
|
+
publish_event_type :perform_finished
|
124
|
+
verify_event_written_to_log :perform_finished
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should write failed event to file" do
|
128
|
+
publish_event_type :failed
|
129
|
+
verify_event_written_to_log :failed
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should write 1 event per line in the file" do
|
133
|
+
publish_event_type :enqueued
|
134
|
+
publish_event_type :dequeued
|
135
|
+
File.readlines(@log_path)[1..-1].size.should == 2
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
describe Resque::Plugins::Clues::CompositePublisher do
|
140
|
+
before do
|
141
|
+
@publisher = Resque::Plugins::Clues::CompositePublisher.new
|
142
|
+
@publisher << Resque::Plugins::Clues::StandardOutPublisher.new
|
143
|
+
@publisher << Resque::Plugins::Clues::StandardOutPublisher.new
|
144
|
+
end
|
145
|
+
|
146
|
+
def verify_event_delegated_to_children(event_type)
|
147
|
+
@publisher.each do |child|
|
148
|
+
child.should_receive(:publish).with(
|
149
|
+
event_type, @current_time, :test_queue, {}, "FooBar", "a", "b")
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
it "should pass Resque lint detection" do
|
154
|
+
Resque::Plugin.lint(Resque::Plugins::Clues::CompositePublisher)
|
155
|
+
end
|
156
|
+
|
157
|
+
it "should delegate enqueued event to children" do
|
158
|
+
verify_event_delegated_to_children :enqueued
|
159
|
+
publish_event_type :enqueued
|
160
|
+
end
|
161
|
+
|
162
|
+
it "should delegate dequeued event to children" do
|
163
|
+
verify_event_delegated_to_children :dequeued
|
164
|
+
publish_event_type :dequeued
|
165
|
+
end
|
166
|
+
|
167
|
+
it "should delegate destroyed event to children" do
|
168
|
+
verify_event_delegated_to_children :destroyed
|
169
|
+
publish_event_type :destroyed
|
170
|
+
end
|
171
|
+
|
172
|
+
it "should delegate perform_started event to children" do
|
173
|
+
verify_event_delegated_to_children :perform_started
|
174
|
+
publish_event_type :perform_started
|
175
|
+
end
|
176
|
+
|
177
|
+
it "should delegate perform_finished event to children" do
|
178
|
+
verify_event_delegated_to_children :perform_finished
|
179
|
+
publish_event_type :perform_finished
|
180
|
+
end
|
181
|
+
|
182
|
+
it "should delegate failed event to children" do
|
183
|
+
verify_event_delegated_to_children :failed
|
184
|
+
publish_event_type :failed
|
185
|
+
end
|
186
|
+
|
187
|
+
it "all children should be invoked when the first child throws an exception" do
|
188
|
+
@publisher[0].stub(:enqueued) {raise 'YOU SHALL NOT PASS'}
|
189
|
+
verify_event_delegated_to_children :enqueued
|
190
|
+
publish_event_type :enqueued
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'stringio'
|
3
|
+
require 'timeout'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
# Setting $TESTING to true will cause Resque to not fork a child to perform the
|
7
|
+
# job, doing all the work within the test process and allowing us to test
|
8
|
+
# results for all aspects of the job performance.
|
9
|
+
$TESTING = true
|
10
|
+
|
11
|
+
class DummyWorker
|
12
|
+
@queue = :test_queue
|
13
|
+
|
14
|
+
def self.invoked?
|
15
|
+
@invoked
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.reset!
|
19
|
+
@invoked = false
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.perform(msg)
|
23
|
+
@invoked = true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class FailingDummyWorker
|
28
|
+
@queue = :test_queue
|
29
|
+
|
30
|
+
def self.perform(msg)
|
31
|
+
raise msg
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe 'end-to-end integration' do
|
36
|
+
before(:each) do
|
37
|
+
DummyWorker.reset!
|
38
|
+
@stream = StringIO.new
|
39
|
+
@worker = Resque::Worker.new(:test_queue)
|
40
|
+
#@worker.very_verbose = true
|
41
|
+
Resque::Plugins::Clues.event_publisher = Resque::Plugins::Clues::StreamPublisher.new(@stream)
|
42
|
+
end
|
43
|
+
|
44
|
+
def stream_size
|
45
|
+
@stream.rewind
|
46
|
+
@stream.readlines.size
|
47
|
+
end
|
48
|
+
|
49
|
+
def enqueue_then_verify(klass, *args, &block)
|
50
|
+
Resque.enqueue(klass, *args)
|
51
|
+
work_and_verify(&block)
|
52
|
+
end
|
53
|
+
|
54
|
+
def work
|
55
|
+
timeout(0.2) do
|
56
|
+
@worker.work(0.1)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def work_and_verify(&block)
|
61
|
+
work
|
62
|
+
@stream.rewind
|
63
|
+
block.call(@stream.readlines.map{|line| MultiJson.decode(line)})
|
64
|
+
end
|
65
|
+
|
66
|
+
def verify_event(event, type, klass, *args)
|
67
|
+
event["worker_class"].should == klass.to_s
|
68
|
+
event["args"].should == args
|
69
|
+
event["event_type"].should == type.to_s
|
70
|
+
event["timestamp"].should_not be_nil
|
71
|
+
event["metadata"]["event_hash"].should_not be_nil
|
72
|
+
event["metadata"]["hostname"].should == `hostname`.strip
|
73
|
+
event["metadata"]["process"].should == $$
|
74
|
+
yield(event) if block_given?
|
75
|
+
end
|
76
|
+
|
77
|
+
context "for job that finishes normally" do
|
78
|
+
it "should publish enqueued, dequeued, perform_started and perform_finished events" do
|
79
|
+
enqueue_then_verify(DummyWorker, 'test') do |events|
|
80
|
+
events.size.should == 4
|
81
|
+
verify_event(events[0], :enqueued, DummyWorker, "test")
|
82
|
+
verify_event(events[1], :dequeued, DummyWorker, "test") do |event|
|
83
|
+
event["metadata"]["time_in_queue"].should_not be_nil
|
84
|
+
end
|
85
|
+
verify_event(events[2], :perform_started, DummyWorker, "test")
|
86
|
+
verify_event(events[3], :perform_finished, DummyWorker, "test") do |event|
|
87
|
+
event["metadata"]["time_to_perform"].should_not be_nil
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context "for job that fails" do
|
94
|
+
it "should publish enqueued, dequeued, perform_started and failed events" do
|
95
|
+
enqueue_then_verify(FailingDummyWorker, 'test') do |events|
|
96
|
+
events.size.should == 4
|
97
|
+
verify_event(events[0], :enqueued, FailingDummyWorker, "test")
|
98
|
+
verify_event(events[1], :dequeued, FailingDummyWorker, "test") do |event|
|
99
|
+
event["metadata"]["time_in_queue"].should_not be_nil
|
100
|
+
end
|
101
|
+
verify_event(events[2], :perform_started, FailingDummyWorker, "test")
|
102
|
+
verify_event(events[3], :failed, FailingDummyWorker, "test") do |event|
|
103
|
+
event["metadata"]["time_to_perform"].should_not be_nil
|
104
|
+
event["metadata"]["exception"].should == RuntimeError.to_s
|
105
|
+
event["metadata"]["message"].should == 'test'
|
106
|
+
event["metadata"]["backtrace"].should_not be_nil
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
context "for job enqueued prior to use of resque-clues gem" do
|
113
|
+
def enqueue_unpatched(worker, *args)
|
114
|
+
unpatch_resque
|
115
|
+
begin
|
116
|
+
Resque.enqueue(worker, *args)
|
117
|
+
ensure
|
118
|
+
repatch_resque
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
context "job that performs normally" do
|
123
|
+
before do
|
124
|
+
enqueue_unpatched(DummyWorker, "test")
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should succeed without failures" do
|
128
|
+
work
|
129
|
+
DummyWorker.invoked?.should == true
|
130
|
+
Resque::Failure.all.should == nil
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
context "job failures" do
|
135
|
+
before do
|
136
|
+
enqueue_unpatched(FailingDummyWorker, "test")
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should report failure normally" do
|
140
|
+
work
|
141
|
+
Resque::Failure.count.should == 1
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
context "where job enqueued with resque-clues gem but worker performing job is not" do
|
147
|
+
def unpatch_and_work
|
148
|
+
unpatch_resque
|
149
|
+
work
|
150
|
+
yield if block_given?
|
151
|
+
ensure
|
152
|
+
repatch_resque
|
153
|
+
end
|
154
|
+
|
155
|
+
context "for job that performs normally" do
|
156
|
+
before {Resque.enqueue DummyWorker, "test"}
|
157
|
+
|
158
|
+
it "should succeed without failures" do
|
159
|
+
unpatch_and_work {Resque::Failure.all.should == nil}
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
context "for job failures" do
|
164
|
+
before {Resque.enqueue FailingDummyWorker, "test"}
|
165
|
+
|
166
|
+
it "should report failure normally" do
|
167
|
+
unpatch_and_work {Resque::Failure.count.should == 1}
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
context "Resque internals assumptions" do
|
173
|
+
describe "Resque#push" do
|
174
|
+
it "should receive item with class and args as symbols" do
|
175
|
+
received_item = nil
|
176
|
+
Resque.stub(:push) {|queue, item| received_item = item}
|
177
|
+
Resque.enqueue(DummyWorker, 'test')
|
178
|
+
received_item[:class].should == "DummyWorker"
|
179
|
+
received_item[:args].should == ['test']
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|