tom_queue 0.0.1.dev
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/lib/tom_queue.rb +56 -0
- data/lib/tom_queue/deferred_work_manager.rb +233 -0
- data/lib/tom_queue/deferred_work_set.rb +165 -0
- data/lib/tom_queue/delayed_job.rb +33 -0
- data/lib/tom_queue/delayed_job/external_messages.rb +56 -0
- data/lib/tom_queue/delayed_job/job.rb +365 -0
- data/lib/tom_queue/external_consumer.rb +136 -0
- data/lib/tom_queue/logging_helper.rb +19 -0
- data/lib/tom_queue/queue_manager.rb +264 -0
- data/lib/tom_queue/sorted_array.rb +69 -0
- data/lib/tom_queue/work.rb +62 -0
- data/spec/database.yml +14 -0
- data/spec/helper.rb +75 -0
- data/spec/tom_queue/deferred_work/deferred_work_manager_integration_spec.rb +186 -0
- data/spec/tom_queue/deferred_work/deferred_work_manager_spec.rb +134 -0
- data/spec/tom_queue/deferred_work/deferred_work_set_spec.rb +134 -0
- data/spec/tom_queue/delayed_job/delayed_job_integration_spec.rb +155 -0
- data/spec/tom_queue/delayed_job/delayed_job_spec.rb +818 -0
- data/spec/tom_queue/external_consumer_integration_spec.rb +225 -0
- data/spec/tom_queue/helper.rb +91 -0
- data/spec/tom_queue/logging_helper_spec.rb +152 -0
- data/spec/tom_queue/queue_manager_spec.rb +218 -0
- data/spec/tom_queue/sorted_array_spec.rb +160 -0
- data/spec/tom_queue/tom_queue_integration_spec.rb +296 -0
- data/spec/tom_queue/tom_queue_spec.rb +30 -0
- data/spec/tom_queue/work_spec.rb +35 -0
- data/tom_queue.gemspec +21 -0
- metadata +137 -0
@@ -0,0 +1,225 @@
|
|
1
|
+
require 'tom_queue/helper'
|
2
|
+
require 'tom_queue/delayed_job'
|
3
|
+
|
4
|
+
describe "External consumers" do
|
5
|
+
|
6
|
+
let(:exchange_name) { "external-exchange-#{Time.now.to_f}" }
|
7
|
+
let(:trace) { [] }
|
8
|
+
|
9
|
+
let(:consumer_class) do
|
10
|
+
Class.new(Object) { include TomQueue::ExternalConsumer }.tap { |k| k.class_exec(trace) do |trace|
|
11
|
+
self::Trace = trace
|
12
|
+
def self.trace(*stuff); self::Trace << stuff; end
|
13
|
+
def trace(*stuff); self.class.trace(*stuff); end
|
14
|
+
end }
|
15
|
+
end
|
16
|
+
|
17
|
+
before do
|
18
|
+
# Hopefully break the core consumer loop out of DJ
|
19
|
+
TomQueue.default_prefix = "test-prefix-#{Time.now.to_f}"
|
20
|
+
|
21
|
+
TomQueue::DelayedJob.handlers.clear
|
22
|
+
TomQueue::DelayedJob.handlers << consumer_class
|
23
|
+
end
|
24
|
+
|
25
|
+
subject { Delayed::Worker.new.work_off(2) }
|
26
|
+
|
27
|
+
it "should be possible to make a consumer using the TomQueue::ExternalConsumer mixin" do
|
28
|
+
consumer_class
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "when a consumer is bound to an exchange without a block" do
|
32
|
+
|
33
|
+
before do
|
34
|
+
consumer_class.class_exec(exchange_name) do |exchange_name|
|
35
|
+
|
36
|
+
bind_exchange(:fanout, exchange_name, :auto_delete => true, :durable => :false)
|
37
|
+
|
38
|
+
def initialize(payload, headers)
|
39
|
+
@payload = payload
|
40
|
+
trace(:init, payload, headers)
|
41
|
+
end
|
42
|
+
|
43
|
+
def perform
|
44
|
+
trace(:perform, @payload)
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
consumer_class.producer.publish('message')
|
50
|
+
subject
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should call the init and perform methods the consumer" do
|
54
|
+
trace.map { |a| a[0] }.should == [:init, :perform]
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should call the init method with the payload and headers" do
|
58
|
+
trace.first.tap do |method, payload, headers|
|
59
|
+
method.should == :init
|
60
|
+
payload.should == 'message'
|
61
|
+
headers.should be_a(Hash)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should have serialized the class so ivars from init are available during the perform call" do
|
66
|
+
trace.last.should == [:perform, 'message']
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "when a block is attached to the bind_exchange call" do
|
71
|
+
|
72
|
+
before do
|
73
|
+
consumer_class.class_exec(exchange_name) do |exchange_name|
|
74
|
+
|
75
|
+
bind_exchange(:fanout, exchange_name, :auto_delete => true, :durable => :false) do |work|
|
76
|
+
trace(:bind_block, work)
|
77
|
+
self.block_method(work)
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.block_method(work)
|
81
|
+
"something other than a delayed job"
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
consumer_class.producer.publish('message')
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should call the block attached to the bind_exchange call" do
|
90
|
+
subject
|
91
|
+
trace.first.first.should == :bind_block
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should pass the TomQueue::Work object to the block" do
|
95
|
+
subject
|
96
|
+
trace.first.last.tap do |work|
|
97
|
+
work.should be_a(TomQueue::Work)
|
98
|
+
work.payload.should == 'message'
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe "if something other than a Delayed::Job instance is returned" do
|
103
|
+
it "should ack the original AMQP message" do
|
104
|
+
pending('...')
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe "if the block returns a Delayed::Job instance" do
|
109
|
+
|
110
|
+
before do
|
111
|
+
consumer_class.class_eval do
|
112
|
+
def self.block_method(work)
|
113
|
+
new('custom-arg').delay.perform
|
114
|
+
end
|
115
|
+
|
116
|
+
def initialize(arg)
|
117
|
+
trace(:init, arg)
|
118
|
+
end
|
119
|
+
def perform
|
120
|
+
trace(:job_performed)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should call the bind block, which calls the init and defers the perform call" do
|
126
|
+
subject
|
127
|
+
trace.map { |a| a[0] }.should == [:bind_block, :init, :job_performed]
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should be init'd directly with the custom arguments" do
|
131
|
+
subject
|
132
|
+
trace[1].last.should == 'custom-arg'
|
133
|
+
end
|
134
|
+
|
135
|
+
it "should perform the Delayed::Job" do
|
136
|
+
subject
|
137
|
+
trace.last.should == [:job_performed]
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
it "should use the encoder if specified"
|
146
|
+
|
147
|
+
|
148
|
+
# it "should not re-deliver the message once the delayed job has been created"
|
149
|
+
|
150
|
+
|
151
|
+
|
152
|
+
|
153
|
+
|
154
|
+
# it "should raise an exception if you publish a message without having bound the consumer"
|
155
|
+
# it "should raise an exception if you try to bind a consumer twice"
|
156
|
+
|
157
|
+
|
158
|
+
# it "should successfully round-trip a message" do
|
159
|
+
# consumer_class.producer.publish("a message")
|
160
|
+
# Delayed::Worker.new.work_off(1)
|
161
|
+
# consumer_class.messages.should == ["a message"]
|
162
|
+
# end
|
163
|
+
|
164
|
+
# it "should republish a message if an exception is raised" do
|
165
|
+
# consumer_class.producer.publish("asplode")
|
166
|
+
# consumer_class.asplode_count = 1
|
167
|
+
# Delayed::Worker.new.work_off(2)
|
168
|
+
# consumer_class.messages.should == ["asplode"]
|
169
|
+
# end
|
170
|
+
|
171
|
+
# it "should immediately run a job if one is returned out of the block"
|
172
|
+
|
173
|
+
|
174
|
+
# describe "if an exception is thrown by two workers" do
|
175
|
+
# it "should push the message to a dead-letter queue if an exception is raised twice"
|
176
|
+
# it "should trigger a log message"
|
177
|
+
# it "should notify the exception reporter"
|
178
|
+
# end
|
179
|
+
|
180
|
+
# end
|
181
|
+
|
182
|
+
# describe "temporary unit tests" do
|
183
|
+
|
184
|
+
# it "should clear any priority bindings just in case the priority changes"
|
185
|
+
# it "should not allow multiple bind_exchange calls to the consumer (for now)"
|
186
|
+
|
187
|
+
# describe "exchange options" do
|
188
|
+
# it "should set the auto-delete if specified"
|
189
|
+
# it "should default auto-delete to false"
|
190
|
+
|
191
|
+
# it "should set the durable flag if specified"
|
192
|
+
# it "should default durable to true"
|
193
|
+
# end
|
194
|
+
|
195
|
+
# describe "when a message is received" do
|
196
|
+
# it "should reject the message if an exception is thrown"
|
197
|
+
# it "should ack the message if the block succeeds"
|
198
|
+
# it "should re-deliver the message once"
|
199
|
+
# it "should post the message to a dead-letter queue if the redelivery attempt fails"
|
200
|
+
# end
|
201
|
+
|
202
|
+
|
203
|
+
# describe "when a block is provided" do
|
204
|
+
|
205
|
+
# it "should call the block with received message payload"
|
206
|
+
|
207
|
+
# end
|
208
|
+
|
209
|
+
# describe "when a block isn't provided" do
|
210
|
+
|
211
|
+
# it "should create an instance of the consumer class"
|
212
|
+
# it "should call #perform on the instance"
|
213
|
+
# it "should pass the payload as the first argument to the call"
|
214
|
+
|
215
|
+
# end
|
216
|
+
|
217
|
+
|
218
|
+
# describe "producer call" do
|
219
|
+
|
220
|
+
# it "should return an object that responds to :publish"
|
221
|
+
# it "should publish a message to the exchange when :publish is called"
|
222
|
+
|
223
|
+
# end
|
224
|
+
|
225
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'bunny'
|
3
|
+
require 'rest_client'
|
4
|
+
require 'tom_queue'
|
5
|
+
require 'tom_queue/delayed_job'
|
6
|
+
|
7
|
+
# Patch AR to allow Mock errors to escape after_commit callbacks
|
8
|
+
# There is a test to check this hook works in delayed_job_spec.rb
|
9
|
+
require 'active_record/connection_adapters/abstract/database_statements'
|
10
|
+
module ActiveRecord::ConnectionAdapters::DatabaseStatements
|
11
|
+
alias orig_commit_transaction_records commit_transaction_records
|
12
|
+
def commit_transaction_records
|
13
|
+
records = @_current_transaction_records.flatten
|
14
|
+
@_current_transaction_records.clear
|
15
|
+
unless records.blank?
|
16
|
+
records.uniq.each do |record|
|
17
|
+
begin
|
18
|
+
record.committed!
|
19
|
+
rescue Exception => e
|
20
|
+
if e.class.to_s =~ /^RSpec/
|
21
|
+
raise
|
22
|
+
else
|
23
|
+
record.logger.error(e) if record.respond_to?(:logger) && record.logger
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
begin
|
32
|
+
begin
|
33
|
+
RestClient.delete("http://guest:guest@localhost:15672/api/vhosts/test")
|
34
|
+
rescue RestClient::ResourceNotFound
|
35
|
+
end
|
36
|
+
RestClient.put("http://guest:guest@localhost:15672/api/vhosts/test", "{}", :content_type => :json, :accept => :json)
|
37
|
+
RestClient.put("http://guest:guest@localhost:15672/api/permissions/test/guest", '{"configure":".*","write":".*","read":".*"}', :content_type => :json, :accept => :json)
|
38
|
+
TheBunny = Bunny.new(:host => 'localhost', :vhost => 'test', :user => 'guest', :password => 'guest')
|
39
|
+
TheBunny.start
|
40
|
+
rescue Errno::ECONNREFUSED
|
41
|
+
$stderr.puts "\033[1;31mFailed to connect to RabbitMQ, is it running?\033[0m\n\n"
|
42
|
+
raise
|
43
|
+
end
|
44
|
+
|
45
|
+
RSpec.configure do |r|
|
46
|
+
|
47
|
+
r.before do
|
48
|
+
TomQueue.exception_reporter = Class.new do
|
49
|
+
def notify(exception)
|
50
|
+
puts "Exception reported: #{exception.inspect}"
|
51
|
+
puts exception.backtrace.join("\n")
|
52
|
+
end
|
53
|
+
end.new
|
54
|
+
|
55
|
+
TomQueue.logger = Logger.new($stdout) if ENV['DEBUG']
|
56
|
+
end
|
57
|
+
|
58
|
+
# Make sure all tests see the same Bunny instance
|
59
|
+
r.before do |test|
|
60
|
+
TomQueue.bunny = TheBunny
|
61
|
+
end
|
62
|
+
|
63
|
+
r.before do
|
64
|
+
TomQueue.logger ||= Logger.new("/dev/null")
|
65
|
+
TomQueue.default_prefix = "test-#{Time.now.to_f}"
|
66
|
+
TomQueue::DelayedJob.apply_hook!
|
67
|
+
Delayed::Job.class_variable_set(:@@tomqueue_manager, nil)
|
68
|
+
end
|
69
|
+
|
70
|
+
# All tests should take < 2 seconds !!
|
71
|
+
r.around do |test|
|
72
|
+
timeout = self.class.metadata[:timeout] || 2
|
73
|
+
if timeout == false
|
74
|
+
test.call
|
75
|
+
else
|
76
|
+
Timeout.timeout(timeout) { test.call }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
r.around do |test|
|
81
|
+
begin
|
82
|
+
TomQueue::DeferredWorkManager.reset!
|
83
|
+
|
84
|
+
test.call
|
85
|
+
|
86
|
+
ensure
|
87
|
+
TomQueue::DeferredWorkManager.reset!
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'tom_queue/helper'
|
2
|
+
require 'tempfile'
|
3
|
+
|
4
|
+
describe TomQueue::LoggingHelper do
|
5
|
+
|
6
|
+
include TomQueue::LoggingHelper
|
7
|
+
|
8
|
+
let(:file) { Tempfile.new("logfile") }
|
9
|
+
let(:logger) { Logger.new(file.path) }
|
10
|
+
let(:output) { file.flush; File.read(file.path) }
|
11
|
+
|
12
|
+
before do
|
13
|
+
logger.formatter = ::Logger::Formatter.new
|
14
|
+
TomQueue.logger = logger
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "basic behaviour" do
|
18
|
+
|
19
|
+
it "should emit a debug message passed as an argument" do
|
20
|
+
debug "Simple to compute"
|
21
|
+
output.should =~ /^D.+Simple to compute$/
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should emit an info message passed as an argument" do
|
25
|
+
info "Simple to compute"
|
26
|
+
output.should =~ /^I.+Simple to compute$/
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should emit a warning message passed as an argument" do
|
30
|
+
warn "Simple to compute"
|
31
|
+
output.should =~ /^W.+Simple to compute$/
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should emit an error message passed as an argument" do
|
35
|
+
error "Simple to compute"
|
36
|
+
output.should =~ /^E.+Simple to compute$/
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should emit a debug message returned from the block" do
|
40
|
+
debug { "Expensive to compute" }
|
41
|
+
output.should =~ /^D.+Expensive to compute$/
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should emit a info message returned from the block" do
|
45
|
+
info { "Expensive to compute" }
|
46
|
+
output.should =~ /^I.+Expensive to compute$/
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should emit a warn message returned from the block" do
|
50
|
+
warn { "Expensive to compute" }
|
51
|
+
output.should =~ /^W.+Expensive to compute$/
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should emit a error message returned from the block" do
|
55
|
+
error { "Expensive to compute" }
|
56
|
+
output.should =~ /^E.+Expensive to compute$/
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "when the log level is info" do
|
61
|
+
before do
|
62
|
+
logger.level = Logger::INFO
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should not yield the debug block" do
|
66
|
+
@called = false
|
67
|
+
debug { @called = true }
|
68
|
+
@called.should be_false
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "when the log level is warn" do
|
73
|
+
before do
|
74
|
+
logger.level = Logger::WARN
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should not yield the debug block" do
|
78
|
+
@called = false
|
79
|
+
debug { @called = true }
|
80
|
+
@called.should be_false
|
81
|
+
end
|
82
|
+
it "should not yield the info block" do
|
83
|
+
@called = false
|
84
|
+
info { @called = true }
|
85
|
+
@called.should be_false
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe "when the log level is error" do
|
90
|
+
before do
|
91
|
+
logger.level = Logger::ERROR
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should not yield the debug block" do
|
95
|
+
@called = false
|
96
|
+
debug { @called = true }
|
97
|
+
@called.should be_false
|
98
|
+
end
|
99
|
+
it "should not yield the info block" do
|
100
|
+
@called = false
|
101
|
+
info { @called = true }
|
102
|
+
@called.should be_false
|
103
|
+
end
|
104
|
+
it "should not yield the warn block" do
|
105
|
+
@called = false
|
106
|
+
warn { @called = true }
|
107
|
+
@called.should be_false
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe "when TomQueue.logger is nil" do
|
112
|
+
before do
|
113
|
+
TomQueue.logger = nil
|
114
|
+
end
|
115
|
+
it "should not yield the debug block" do
|
116
|
+
@called = false
|
117
|
+
debug { @called = true }
|
118
|
+
@called.should be_false
|
119
|
+
end
|
120
|
+
it "should not yield the info block" do
|
121
|
+
@called = false
|
122
|
+
info { @called = true }
|
123
|
+
@called.should be_false
|
124
|
+
end
|
125
|
+
it "should not yield the warn block" do
|
126
|
+
@called = false
|
127
|
+
warn { @called = true }
|
128
|
+
@called.should be_false
|
129
|
+
end
|
130
|
+
it "should not yield the error block" do
|
131
|
+
@called = false
|
132
|
+
error { @called = true }
|
133
|
+
@called.should be_false
|
134
|
+
end
|
135
|
+
it "should drop debug messages silently" do
|
136
|
+
debug "Message"
|
137
|
+
end
|
138
|
+
it "should drop info messages silently" do
|
139
|
+
info "Message"
|
140
|
+
end
|
141
|
+
it "should drop warn messages silently" do
|
142
|
+
warn "Message"
|
143
|
+
end
|
144
|
+
it "should drop error messages silently" do
|
145
|
+
error "Message"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
|
151
|
+
end
|
152
|
+
|