beetle 0.3.0.rc.2 → 0.3.0.rc.5
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/README.rdoc +1 -1
- data/RELEASE_NOTES.rdoc +5 -0
- data/Rakefile +2 -6
- data/beetle.gemspec +13 -6
- data/examples/nonexistent_server.rb +28 -0
- data/examples/redundant.rb +1 -1
- data/examples/rpc.rb +3 -3
- data/lib/beetle/base.rb +4 -0
- data/lib/beetle/client.rb +1 -1
- data/lib/beetle/message.rb +3 -2
- data/lib/beetle/redis_configuration_client.rb +1 -1
- data/lib/beetle/redis_configuration_server.rb +4 -3
- data/lib/beetle/subscriber.rb +67 -44
- data/lib/beetle/version.rb +3 -0
- data/lib/beetle.rb +23 -8
- data/test/beetle/amqp_gem_behavior_test.rb +30 -0
- data/test/beetle/base_test.rb +4 -0
- data/test/beetle/deduplication_store_test.rb +1 -1
- data/test/beetle/redis_ext_test.rb +3 -5
- data/test/beetle/subscriber_test.rb +114 -78
- data/test/colorized_test_output.rb +25 -0
- data/test/test_helper.rb +4 -6
- metadata +165 -199
- data/examples/test_publisher.rb +0 -32
@@ -8,40 +8,18 @@ module Beetle
|
|
8
8
|
end
|
9
9
|
|
10
10
|
test "initially there should be no amqp connections" do
|
11
|
-
assert_equal({}, @sub.instance_variable_get("@
|
11
|
+
assert_equal({}, @sub.instance_variable_get("@connections"))
|
12
12
|
end
|
13
13
|
|
14
|
-
test "initially there should be no
|
15
|
-
assert_equal({}, @sub.instance_variable_get("@
|
14
|
+
test "initially there should be no channels" do
|
15
|
+
assert_equal({}, @sub.instance_variable_get("@channels"))
|
16
16
|
end
|
17
17
|
|
18
|
-
test "
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
assert_equal 42, connections[@sub.server]
|
24
|
-
end
|
25
|
-
|
26
|
-
test "new amqp connections should be created using current host and port" do
|
27
|
-
m = mock("dummy")
|
28
|
-
expected_amqp_options = {
|
29
|
-
:host => @sub.send(:current_host), :port => @sub.send(:current_port),
|
30
|
-
:user => "guest", :pass => "guest", :vhost => "/", :logging => false
|
31
|
-
}
|
32
|
-
AMQP.expects(:connect).with(expected_amqp_options).returns(m)
|
33
|
-
# TODO: smarter way to test? what triggers the amqp_connection private method call?
|
34
|
-
assert_equal m, @sub.send(:new_amqp_connection)
|
35
|
-
end
|
36
|
-
|
37
|
-
test "mq instances should be created for the current server if accessed" do
|
38
|
-
@sub.expects(:amqp_connection).returns(11)
|
39
|
-
mq_mock = mock('mq')
|
40
|
-
mq_mock.expects(:prefetch).with(1).returns(42)
|
41
|
-
MQ.expects(:new).with(11).returns(mq_mock)
|
42
|
-
assert_equal 42, @sub.send(:mq)
|
43
|
-
mqs = @sub.instance_variable_get("@mqs")
|
44
|
-
assert_equal 42, mqs[@sub.server]
|
18
|
+
test "channel should return the channel associated with the current server, if there is one" do
|
19
|
+
channel = mock("donald")
|
20
|
+
@sub.instance_variable_set("@channels", {"donald:1" => channel})
|
21
|
+
assert_nil @sub.send(:channel, "goofy:123")
|
22
|
+
assert_equal channel, @sub.send(:channel, "donald:1")
|
45
23
|
end
|
46
24
|
|
47
25
|
test "stop! should close all amqp connections and then stop the event loop" do
|
@@ -49,7 +27,7 @@ module Beetle
|
|
49
27
|
connection1.expects(:close).yields
|
50
28
|
connection2 = mock('con2')
|
51
29
|
connection2.expects(:close).yields
|
52
|
-
@sub.instance_variable_set "@
|
30
|
+
@sub.instance_variable_set "@connections", [["server1", connection1], ["server2",connection2]]
|
53
31
|
EM.expects(:stop_event_loop)
|
54
32
|
@sub.send(:stop!)
|
55
33
|
end
|
@@ -156,31 +134,43 @@ module Beetle
|
|
156
134
|
q.expects(:bind).with(:the_exchange, {:key => "haha.#"})
|
157
135
|
m = mock("MQ")
|
158
136
|
m.expects(:queue).with("some_queue", :durable => true, :passive => false, :auto_delete => false, :exclusive => false).returns(q)
|
159
|
-
@sub.expects(:
|
137
|
+
@sub.expects(:channel).returns(m)
|
160
138
|
|
161
139
|
@sub.send(:queue, "some_queue")
|
162
140
|
assert_equal q, @sub.send(:queues)["some_queue"]
|
163
141
|
end
|
164
142
|
|
165
|
-
test "binding queues should
|
166
|
-
s = sequence("binding")
|
143
|
+
test "binding queues should bind all queues" do
|
167
144
|
@client.register_queue(:x)
|
168
145
|
@client.register_queue(:y)
|
169
146
|
@client.register_handler(%w(x y)){}
|
170
|
-
@sub.
|
171
|
-
@sub.expects(:
|
172
|
-
@sub.expects(:queue).with("x").in_sequence(s)
|
173
|
-
@sub.expects(:queue).with("y").in_sequence(s)
|
174
|
-
@sub.expects(:set_current_server).with("b").in_sequence(s)
|
175
|
-
@sub.expects(:queue).with("x").in_sequence(s)
|
176
|
-
@sub.expects(:queue).with("y").in_sequence(s)
|
147
|
+
@sub.expects(:queue).with("x")
|
148
|
+
@sub.expects(:queue).with("y")
|
177
149
|
@sub.send(:bind_queues, %W(x y))
|
178
150
|
end
|
179
151
|
|
152
|
+
test "subscribing to queues should subscribe on all queues" do
|
153
|
+
@client.register_queue(:x)
|
154
|
+
@client.register_queue(:y)
|
155
|
+
@client.register_handler(%w(x y)){}
|
156
|
+
@sub.expects(:subscribe).with("x")
|
157
|
+
@sub.expects(:subscribe).with("y")
|
158
|
+
@sub.send(:subscribe_queues, %W(x y))
|
159
|
+
end
|
160
|
+
|
180
161
|
test "should not try to bind a queue for an exchange which has no queue" do
|
181
162
|
@client.register_message(:without_queue)
|
182
163
|
assert_equal [], @sub.send(:queues_for_exchanges, ["without_queue"])
|
183
164
|
end
|
165
|
+
|
166
|
+
test "should not subscribe on a queue for which there is no handler" do
|
167
|
+
@client.register_queue(:x)
|
168
|
+
@client.register_queue(:y)
|
169
|
+
@client.register_handler(%w(y)){}
|
170
|
+
@sub.expects(:subscribe).with("y")
|
171
|
+
@sub.send(:subscribe_queues, %W(x y))
|
172
|
+
end
|
173
|
+
|
184
174
|
end
|
185
175
|
|
186
176
|
class SubscriberExchangeManagementTest < Test::Unit::TestCase
|
@@ -197,26 +187,20 @@ module Beetle
|
|
197
187
|
@client.register_exchange("some_exchange", "type" => "topic", "durable" => true)
|
198
188
|
m = mock("AMQP")
|
199
189
|
m.expects(:topic).with("some_exchange", :durable => true).returns(42)
|
200
|
-
@sub.expects(:
|
190
|
+
@sub.expects(:channel).returns(m)
|
201
191
|
ex = @sub.send(:exchange, "some_exchange")
|
202
192
|
assert @sub.send(:exchanges).include?("some_exchange")
|
203
193
|
ex2 = @sub.send(:exchange, "some_exchange")
|
204
194
|
assert_equal ex2, ex
|
205
195
|
end
|
206
196
|
|
207
|
-
test "should create exchanges for all exchanges passed to create_exchanges
|
208
|
-
@sub.servers = %w(x y)
|
197
|
+
test "should create exchanges for all exchanges passed to create_exchanges for the current server" do
|
209
198
|
@client.register_queue(:donald, :exchange => 'duck')
|
210
199
|
@client.register_queue(:mickey)
|
211
200
|
@client.register_queue(:mouse, :exchange => 'mickey')
|
212
201
|
|
213
|
-
|
214
|
-
@sub.expects(:
|
215
|
-
@sub.expects(:create_exchange!).with("duck", anything).in_sequence(exchange_creation)
|
216
|
-
@sub.expects(:create_exchange!).with("mickey", anything).in_sequence(exchange_creation)
|
217
|
-
@sub.expects(:set_current_server).with('y', anything).in_sequence(exchange_creation)
|
218
|
-
@sub.expects(:create_exchange!).with("duck", anything).in_sequence(exchange_creation)
|
219
|
-
@sub.expects(:create_exchange!).with("mickey", anything).in_sequence(exchange_creation)
|
202
|
+
@sub.expects(:create_exchange!).with("duck", anything)
|
203
|
+
@sub.expects(:create_exchange!).with("mickey", anything)
|
220
204
|
@sub.send(:create_exchanges, %w(duck mickey))
|
221
205
|
end
|
222
206
|
end
|
@@ -240,7 +224,7 @@ module Beetle
|
|
240
224
|
assert_nothing_raised { @callback.call(header, 'foo') }
|
241
225
|
end
|
242
226
|
|
243
|
-
test "should call reject on the message header when processing the handler returns true on
|
227
|
+
test "should call reject on the message header when processing the handler returns true on reject?" do
|
244
228
|
header = header_with_params({})
|
245
229
|
result = mock("result")
|
246
230
|
result.expects(:reject?).returns(true)
|
@@ -256,10 +240,10 @@ module Beetle
|
|
256
240
|
Message.any_instance.expects(:process).returns(result)
|
257
241
|
Message.any_instance.expects(:handler_result).returns("response-data")
|
258
242
|
mq = mock("MQ")
|
259
|
-
@sub.expects(:
|
243
|
+
@sub.expects(:channel).with(@sub.server).returns(mq)
|
260
244
|
exchange = mock("exchange")
|
261
|
-
exchange.expects(:publish).with("response-data", :headers => {:status => "OK"})
|
262
|
-
|
245
|
+
exchange.expects(:publish).with("response-data", :routing_key => "tmp-queue", :headers => {:status => "OK"}, :persistent => false)
|
246
|
+
AMQP::Exchange.expects(:new).with(mq, :direct, "").returns(exchange)
|
263
247
|
@callback.call(header, 'foo')
|
264
248
|
end
|
265
249
|
|
@@ -269,10 +253,10 @@ module Beetle
|
|
269
253
|
Message.any_instance.expects(:process).returns(result)
|
270
254
|
Message.any_instance.expects(:handler_result).returns(nil)
|
271
255
|
mq = mock("MQ")
|
272
|
-
@sub.expects(:
|
256
|
+
@sub.expects(:channel).with(@sub.server).returns(mq)
|
273
257
|
exchange = mock("exchange")
|
274
|
-
exchange.expects(:publish).with("", :headers => {:status => "FAILED"})
|
275
|
-
|
258
|
+
exchange.expects(:publish).with("", :routing_key => "tmp-queue", :headers => {:status => "FAILED"}, :persistent => false)
|
259
|
+
AMQP::Exchange.expects(:new).with(mq, :direct, "").returns(exchange)
|
276
260
|
@callback.call(header, 'foo')
|
277
261
|
end
|
278
262
|
|
@@ -284,18 +268,6 @@ module Beetle
|
|
284
268
|
@sub = @client.send(:subscriber)
|
285
269
|
end
|
286
270
|
|
287
|
-
test "subscribe should create subscriptions on all queues for all servers" do
|
288
|
-
@sub.servers << "localhost:7777"
|
289
|
-
@client.register_message(:a)
|
290
|
-
@client.register_message(:b)
|
291
|
-
@client.register_queue(:a)
|
292
|
-
@client.register_queue(:b)
|
293
|
-
@client.register_handler(%W(a b)){}
|
294
|
-
@sub.expects(:subscribe).with("a").times(2)
|
295
|
-
@sub.expects(:subscribe).with("b").times(2)
|
296
|
-
@sub.send(:subscribe_queues, %W(a b))
|
297
|
-
end
|
298
|
-
|
299
271
|
test "subscribe should subscribe with a subscription callback created from the registered block and remember the subscription" do
|
300
272
|
@client.register_queue(:some_queue, :exchange => "some_exchange", :key => "some_key")
|
301
273
|
server = @sub.server
|
@@ -312,28 +284,32 @@ module Beetle
|
|
312
284
|
q = mock("QUEUE")
|
313
285
|
subscription_options = {:ack => true, :key => "#"}
|
314
286
|
q.expects(:subscribe).with(subscription_options).yields(header, "foo")
|
315
|
-
@sub.expects(:queues).returns({"some_queue" => q}).
|
287
|
+
@sub.expects(:queues).returns({"some_queue" => q}).once
|
316
288
|
@sub.send(:subscribe, "some_queue")
|
317
289
|
assert block_called
|
318
290
|
assert @sub.__send__(:has_subscription?, "some_queue")
|
319
|
-
q.expects(:subscribe).with(subscription_options).raises(MQ::Error)
|
320
|
-
assert_raises(Error) { @sub.send(:subscribe, "some_queue") }
|
291
|
+
# q.expects(:subscribe).with(subscription_options).raises(MQ::Error)
|
292
|
+
# assert_raises(Error) { @sub.send(:subscribe, "some_queue") }
|
321
293
|
end
|
322
294
|
|
323
295
|
test "subscribe should fail if no handler exists for given message" do
|
324
296
|
assert_raises(Error){ @sub.send(:subscribe, "some_queue") }
|
325
297
|
end
|
326
298
|
|
327
|
-
test "listening on queues should use eventmachine
|
299
|
+
test "listening on queues should use eventmachine, connect to each server, and yield" do
|
328
300
|
@client.register_exchange(:an_exchange)
|
329
301
|
@client.register_queue(:a_queue, :exchange => :an_exchange)
|
330
302
|
@client.register_message(:a_message, :key => "foo", :exchange => :an_exchange)
|
303
|
+
@sub.servers << "localhost:7777"
|
331
304
|
|
305
|
+
@sub.expects(:connect_server).twice
|
332
306
|
EM.expects(:run).yields
|
333
|
-
@sub.expects(:create_exchanges).with(["an_exchange"])
|
334
|
-
@sub.expects(:bind_queues).with(["a_queue"])
|
335
|
-
@sub.expects(:subscribe_queues).with(["a_queue"])
|
336
|
-
|
307
|
+
# @sub.expects(:create_exchanges).with(["an_exchange"])
|
308
|
+
# @sub.expects(:bind_queues).with(["a_queue"])
|
309
|
+
# @sub.expects(:subscribe_queues).with(["a_queue"])
|
310
|
+
yielded = false
|
311
|
+
@sub.listen_queues(["a_queue"]) { yielded = true}
|
312
|
+
assert yielded
|
337
313
|
end
|
338
314
|
end
|
339
315
|
|
@@ -357,4 +333,64 @@ module Beetle
|
|
357
333
|
|
358
334
|
end
|
359
335
|
|
336
|
+
class ConnectionTest < Test::Unit::TestCase
|
337
|
+
def setup
|
338
|
+
@client = Client.new
|
339
|
+
@sub = @client.send(:subscriber)
|
340
|
+
@sub.send(:set_current_server, "mickey:42")
|
341
|
+
@settings = @sub.send(:connection_settings)
|
342
|
+
end
|
343
|
+
|
344
|
+
test "connection settings should use current host and port and specify connection failure callback" do
|
345
|
+
assert_equal "mickey", @settings[:host]
|
346
|
+
assert_equal 42, @settings[:port]
|
347
|
+
assert @settings.has_key?(:on_tcp_connection_failure)
|
348
|
+
end
|
349
|
+
|
350
|
+
test "tcp connection failure should try to connect again after 10 seconds" do
|
351
|
+
cb = @sub.send(:on_tcp_connection_failure)
|
352
|
+
EM::Timer.expects(:new).with(10).yields
|
353
|
+
@sub.expects(:connect_server).with(@settings)
|
354
|
+
@sub.logger.expects(:warn).with("Beetle: connection failed: mickey:42")
|
355
|
+
cb.call(@settings)
|
356
|
+
end
|
357
|
+
|
358
|
+
test "tcp connection loss handler tries to reconnect" do
|
359
|
+
connection = mock("connection")
|
360
|
+
connection.expects(:reconnect).with(false, 10)
|
361
|
+
@sub.logger.expects(:warn).with("Beetle: lost connection: mickey:42. reconnecting.")
|
362
|
+
@sub.send(:on_tcp_connection_loss, connection, {:host => "mickey", :port => 42})
|
363
|
+
end
|
364
|
+
|
365
|
+
test "event machine connection error" do
|
366
|
+
connection = mock("connection")
|
367
|
+
AMQP.expects(:connect).raises(EventMachine::ConnectionError)
|
368
|
+
@settings[:on_tcp_connection_failure].expects(:call).with(@settings)
|
369
|
+
@sub.send(:connect_server, @settings)
|
370
|
+
end
|
371
|
+
|
372
|
+
test "successfull connection to broker" do
|
373
|
+
connection = mock("connection")
|
374
|
+
connection.expects(:on_tcp_connection_loss)
|
375
|
+
@sub.expects(:open_channel_and_subscribe).with(connection, @settings)
|
376
|
+
AMQP.expects(:connect).with(@settings).yields(connection)
|
377
|
+
@sub.send(:connect_server, @settings)
|
378
|
+
assert_equal connection, @sub.instance_variable_get("@connections")["mickey:42"]
|
379
|
+
end
|
380
|
+
|
381
|
+
test "channel opening, exchange creation, queue bindings and subscription" do
|
382
|
+
connection = mock("connection")
|
383
|
+
channel = mock("channel")
|
384
|
+
channel.expects(:prefetch).with(1)
|
385
|
+
channel.expects(:auto_recovery=).with(true)
|
386
|
+
AMQP::Channel.expects(:new).with(connection).yields(channel)
|
387
|
+
@sub.expects(:create_exchanges)
|
388
|
+
@sub.expects(:bind_queues)
|
389
|
+
@sub.expects(:subscribe_queues)
|
390
|
+
@sub.send(:open_channel_and_subscribe, connection, @settings)
|
391
|
+
assert_equal channel, @sub.instance_variable_get("@channels")["mickey:42"]
|
392
|
+
end
|
393
|
+
|
394
|
+
end
|
395
|
+
|
360
396
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# colorized output for Test::Unit / MiniTest
|
2
|
+
begin
|
3
|
+
if RUBY_VERSION < "1.9"
|
4
|
+
require 'redgreen'
|
5
|
+
else
|
6
|
+
module ColorizedDots
|
7
|
+
require 'ansi/code'
|
8
|
+
def run(runner)
|
9
|
+
r = super
|
10
|
+
ANSI.ansi(r, ANSI_COLOR_MAPPING[r])
|
11
|
+
end
|
12
|
+
ANSI_COLOR_MAPPING = Hash.new(:white).merge!('.' => :green, 'S' => :magenta, 'F' => :yellow, 'E' => :red )
|
13
|
+
end
|
14
|
+
class MiniTest::Unit
|
15
|
+
TestCase.send(:include, ColorizedDots)
|
16
|
+
def status(io = @@out)
|
17
|
+
format = "%d tests, %d assertions, %d failures, %d errors, %d skips"
|
18
|
+
color = (errors + failures) > 0 ? :red : :green
|
19
|
+
io.puts ANSI.ansi(format % [test_count, assertion_count, failures, errors, skips], color)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
rescue LoadError => e
|
24
|
+
# do nothing
|
25
|
+
end unless ENV['TM_FILENAME']
|
data/test/test_helper.rb
CHANGED
@@ -4,11 +4,7 @@ require 'mocha'
|
|
4
4
|
require 'active_support/testing/declarative'
|
5
5
|
|
6
6
|
require File.expand_path(File.dirname(__FILE__) + '/../lib/beetle')
|
7
|
-
|
8
|
-
begin
|
9
|
-
require 'redgreen' unless ENV['TM_FILENAME']
|
10
|
-
rescue LoadError => e
|
11
|
-
end
|
7
|
+
require File.expand_path(File.dirname(__FILE__) + '/colorized_test_output')
|
12
8
|
|
13
9
|
# we can remove this hack which is needed only for testing
|
14
10
|
begin
|
@@ -29,13 +25,15 @@ end
|
|
29
25
|
Beetle.config.logger = Logger.new(File.dirname(__FILE__) + '/../test.log')
|
30
26
|
Beetle.config.redis_server = "localhost:6379"
|
31
27
|
|
28
|
+
|
32
29
|
def header_with_params(opts = {})
|
33
30
|
beetle_headers = Beetle::Message.publishing_options(opts)
|
34
31
|
header = mock("header")
|
35
|
-
header.stubs(:
|
32
|
+
header.stubs(:attributes).returns(beetle_headers)
|
36
33
|
header
|
37
34
|
end
|
38
35
|
|
36
|
+
|
39
37
|
def redis_stub(name, opts = {})
|
40
38
|
default_port = opts['port'] || "1234"
|
41
39
|
default_host = opts['host'] || "foo"
|