beetle 0.3.0.rc.2 → 0.3.0.rc.5
Sign up to get free protection for your applications and to get access to all the features.
- 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"
|