beetle 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +82 -0
- data/Rakefile +114 -0
- data/TODO +7 -0
- data/beetle.gemspec +127 -0
- data/etc/redis-master.conf +189 -0
- data/etc/redis-slave.conf +189 -0
- data/examples/README.rdoc +14 -0
- data/examples/attempts.rb +66 -0
- data/examples/handler_class.rb +64 -0
- data/examples/handling_exceptions.rb +73 -0
- data/examples/multiple_exchanges.rb +48 -0
- data/examples/multiple_queues.rb +43 -0
- data/examples/redis_failover.rb +65 -0
- data/examples/redundant.rb +65 -0
- data/examples/rpc.rb +45 -0
- data/examples/simple.rb +39 -0
- data/lib/beetle.rb +57 -0
- data/lib/beetle/base.rb +78 -0
- data/lib/beetle/client.rb +252 -0
- data/lib/beetle/configuration.rb +31 -0
- data/lib/beetle/deduplication_store.rb +152 -0
- data/lib/beetle/handler.rb +95 -0
- data/lib/beetle/message.rb +336 -0
- data/lib/beetle/publisher.rb +187 -0
- data/lib/beetle/r_c.rb +40 -0
- data/lib/beetle/subscriber.rb +144 -0
- data/script/start_rabbit +29 -0
- data/snafu.rb +55 -0
- data/test/beetle.yml +81 -0
- data/test/beetle/base_test.rb +52 -0
- data/test/beetle/bla.rb +0 -0
- data/test/beetle/client_test.rb +305 -0
- data/test/beetle/configuration_test.rb +5 -0
- data/test/beetle/deduplication_store_test.rb +90 -0
- data/test/beetle/handler_test.rb +105 -0
- data/test/beetle/message_test.rb +744 -0
- data/test/beetle/publisher_test.rb +407 -0
- data/test/beetle/r_c_test.rb +9 -0
- data/test/beetle/subscriber_test.rb +263 -0
- data/test/beetle_test.rb +5 -0
- data/test/test_helper.rb +20 -0
- data/tmp/master/.gitignore +2 -0
- data/tmp/slave/.gitignore +3 -0
- metadata +192 -0
@@ -0,0 +1,407 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
|
2
|
+
|
3
|
+
|
4
|
+
module Beetle
|
5
|
+
class PublisherTest < Test::Unit::TestCase
|
6
|
+
def setup
|
7
|
+
client = Client.new
|
8
|
+
@pub = Publisher.new(client)
|
9
|
+
end
|
10
|
+
|
11
|
+
test "acccessing a bunny for a server which doesn't have one should create it and associate it with the server" do
|
12
|
+
@pub.expects(:new_bunny).returns(42)
|
13
|
+
assert_equal 42, @pub.send(:bunny)
|
14
|
+
bunnies = @pub.instance_variable_get("@bunnies")
|
15
|
+
assert_equal(42, bunnies[@pub.server])
|
16
|
+
end
|
17
|
+
|
18
|
+
test "new bunnies should be created using current host and port and they should be started" do
|
19
|
+
m = mock("dummy")
|
20
|
+
expected_bunny_options = {
|
21
|
+
:host => @pub.send(:current_host), :port => @pub.send(:current_port),
|
22
|
+
:logging => false, :user => "guest", :pass => "guest", :vhost => "/"
|
23
|
+
}
|
24
|
+
Bunny.expects(:new).with(expected_bunny_options).returns(m)
|
25
|
+
m.expects(:start)
|
26
|
+
assert_equal m, @pub.send(:new_bunny)
|
27
|
+
end
|
28
|
+
|
29
|
+
test "initially there should be no bunnies" do
|
30
|
+
assert_equal({}, @pub.instance_variable_get("@bunnies"))
|
31
|
+
end
|
32
|
+
|
33
|
+
test "initially there should be no dead servers" do
|
34
|
+
assert_equal({}, @pub.instance_variable_get("@dead_servers"))
|
35
|
+
end
|
36
|
+
|
37
|
+
test "stop! should shut down bunny and clean internal data structures" do
|
38
|
+
b = mock("bunny")
|
39
|
+
b.expects(:stop).raises(Exception.new)
|
40
|
+
@pub.expects(:bunny).returns(b)
|
41
|
+
@pub.send(:stop!)
|
42
|
+
assert_equal({}, @pub.send(:exchanges))
|
43
|
+
assert_equal({}, @pub.send(:queues))
|
44
|
+
assert_nil @pub.instance_variable_get(:@bunnies)[@pub.server]
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
class PublisherPublishingTest < Test::Unit::TestCase
|
50
|
+
def setup
|
51
|
+
@client = Client.new
|
52
|
+
@pub = Publisher.new(@client)
|
53
|
+
@pub.stubs(:bind_queues_for_exchange)
|
54
|
+
@client.register_queue("mama", :exchange => "mama-exchange")
|
55
|
+
@client.register_message("mama", :ttl => 1.hour, :exchange => "mama-exchange")
|
56
|
+
@opts = { :ttl => 1.hour , :key => "mama", :persistent => true}
|
57
|
+
@data = 'XXX'
|
58
|
+
end
|
59
|
+
|
60
|
+
test "failover publishing should try to recycle dead servers before trying to publish the message" do
|
61
|
+
@pub.servers << "localhost:3333"
|
62
|
+
@pub.send(:mark_server_dead)
|
63
|
+
publishing = sequence('publishing')
|
64
|
+
@pub.expects(:recycle_dead_servers).in_sequence(publishing)
|
65
|
+
@pub.expects(:publish_with_failover).with("mama-exchange", "mama", @data, @opts).in_sequence(publishing)
|
66
|
+
@pub.publish("mama", @data)
|
67
|
+
end
|
68
|
+
|
69
|
+
test "redundant publishing should try to recycle dead servers before trying to publish the message" do
|
70
|
+
@pub.servers << "localhost:3333"
|
71
|
+
@pub.send(:mark_server_dead)
|
72
|
+
publishing = sequence('publishing')
|
73
|
+
@pub.expects(:recycle_dead_servers).in_sequence(publishing)
|
74
|
+
@pub.expects(:publish_with_redundancy).with("mama-exchange", "mama", @data, @opts.merge(:redundant => true)).in_sequence(publishing)
|
75
|
+
@pub.publish("mama", @data, :redundant => true)
|
76
|
+
end
|
77
|
+
|
78
|
+
test "publishing should fail over to the next server" do
|
79
|
+
failover = sequence('failover')
|
80
|
+
@pub.expects(:select_next_server).in_sequence(failover)
|
81
|
+
e = mock("exchange")
|
82
|
+
@pub.expects(:exchange).with("mama-exchange").returns(e).in_sequence(failover)
|
83
|
+
e.expects(:publish).raises(Bunny::ConnectionError).in_sequence(failover)
|
84
|
+
@pub.expects(:stop!).in_sequence(failover)
|
85
|
+
@pub.expects(:mark_server_dead).in_sequence(failover)
|
86
|
+
@pub.publish_with_failover("mama-exchange", "mama", @data, @opts)
|
87
|
+
end
|
88
|
+
|
89
|
+
test "redundant publishing should send the message to two servers" do
|
90
|
+
redundant = sequence("redundant")
|
91
|
+
@pub.servers = ["someserver", "someotherserver"]
|
92
|
+
@pub.server = "someserver"
|
93
|
+
|
94
|
+
e = mock("exchange")
|
95
|
+
@pub.expects(:exchange).with("mama-exchange").returns(e).in_sequence(redundant)
|
96
|
+
e.expects(:publish).in_sequence(redundant)
|
97
|
+
@pub.expects(:exchange).with("mama-exchange").returns(e).in_sequence(redundant)
|
98
|
+
e.expects(:publish).in_sequence(redundant)
|
99
|
+
|
100
|
+
assert_equal 2, @pub.publish_with_redundancy("mama-exchange", "mama", @data, @opts)
|
101
|
+
end
|
102
|
+
|
103
|
+
test "redundant publishing should return 1 if the message was published to one server only" do
|
104
|
+
redundant = sequence("redundant")
|
105
|
+
@pub.servers = ["someserver", "someotherserver"]
|
106
|
+
@pub.server = "someserver"
|
107
|
+
|
108
|
+
e = mock("exchange")
|
109
|
+
@pub.expects(:exchange).with("mama-exchange").returns(e).in_sequence(redundant)
|
110
|
+
e.expects(:publish).raises(Bunny::ConnectionError).in_sequence(redundant)
|
111
|
+
@pub.expects(:exchange).with("mama-exchange").returns(e).in_sequence(redundant)
|
112
|
+
e.expects(:publish).in_sequence(redundant)
|
113
|
+
|
114
|
+
assert_equal 1, @pub.publish_with_redundancy("mama-exchange", "mama", @data, @opts)
|
115
|
+
end
|
116
|
+
|
117
|
+
test "redundant publishing should return 0 if the message was published to no server" do
|
118
|
+
redundant = sequence("redundant")
|
119
|
+
@pub.servers = ["someserver", "someotherserver"]
|
120
|
+
@pub.server = "someserver"
|
121
|
+
|
122
|
+
e = mock("exchange")
|
123
|
+
@pub.expects(:exchange).with("mama-exchange").returns(e).in_sequence(redundant)
|
124
|
+
e.expects(:publish).raises(Bunny::ConnectionError).in_sequence(redundant)
|
125
|
+
@pub.expects(:exchange).with("mama-exchange").returns(e).in_sequence(redundant)
|
126
|
+
e.expects(:publish).raises(Bunny::ConnectionError).in_sequence(redundant)
|
127
|
+
|
128
|
+
assert_equal 0, @pub.publish_with_redundancy("mama-exchange", "mama", @data, @opts)
|
129
|
+
end
|
130
|
+
|
131
|
+
test "redundant publishing should fallback to failover publishing if less than one server is available" do
|
132
|
+
@pub.server = ["a server"]
|
133
|
+
@pub.expects(:publish_with_failover).with("mama-exchange", "mama", @data, @opts).returns(1)
|
134
|
+
assert_equal 1, @pub.publish_with_redundancy("mama-exchange", "mama", @data, @opts)
|
135
|
+
end
|
136
|
+
|
137
|
+
test "redundant publishing should publish to two of three servers if one server is dead" do
|
138
|
+
@pub.servers = %w(server1 server2 server3)
|
139
|
+
@pub.server = "server1"
|
140
|
+
redundant = sequence("redundant")
|
141
|
+
|
142
|
+
e = mock("exchange")
|
143
|
+
|
144
|
+
@pub.expects(:exchange).returns(e).in_sequence(redundant)
|
145
|
+
e.expects(:publish).in_sequence(redundant)
|
146
|
+
|
147
|
+
@pub.expects(:exchange).returns(e).in_sequence(redundant)
|
148
|
+
e.expects(:publish).raises(Bunny::ConnectionError).in_sequence(redundant)
|
149
|
+
@pub.expects(:stop!).in_sequence(redundant)
|
150
|
+
|
151
|
+
@pub.expects(:exchange).returns(e).in_sequence(redundant)
|
152
|
+
e.expects(:publish).in_sequence(redundant)
|
153
|
+
|
154
|
+
assert_equal 2, @pub.publish_with_redundancy("mama-exchange", "mama", @data, @opts)
|
155
|
+
end
|
156
|
+
|
157
|
+
test "publishing should use the message ttl passed in the options hash to encode the message body" do
|
158
|
+
opts = {:ttl => 1.day}
|
159
|
+
Message.expects(:publishing_options).with(:ttl => 1.day).returns({})
|
160
|
+
@pub.expects(:select_next_server)
|
161
|
+
e = mock("exchange")
|
162
|
+
@pub.expects(:exchange).returns(e)
|
163
|
+
e.expects(:publish)
|
164
|
+
assert_equal 1, @pub.publish_with_failover("mama-exchange", "mama", @data, opts)
|
165
|
+
end
|
166
|
+
|
167
|
+
test "publishing with redundancy should use the message ttl passed in the options hash to encode the message body" do
|
168
|
+
opts = {:ttl => 1.day}
|
169
|
+
Message.expects(:publishing_options).with(:ttl => 1.day).returns({})
|
170
|
+
@pub.expects(:select_next_server)
|
171
|
+
e = mock("exchange")
|
172
|
+
@pub.expects(:exchange).returns(e)
|
173
|
+
e.expects(:publish)
|
174
|
+
assert_equal 1, @pub.publish_with_redundancy("mama-exchange", "mama", @data, opts)
|
175
|
+
end
|
176
|
+
|
177
|
+
test "publishing should use the message ttl from the message configuration if no ttl is passed in via the options hash" do
|
178
|
+
@pub.expects(:publish_with_failover).with("mama-exchange", "mama", @data, @opts).returns(1)
|
179
|
+
assert_equal 1, @pub.publish("mama", @data)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
class PublisherQueueManagementTest < Test::Unit::TestCase
|
184
|
+
def setup
|
185
|
+
@client = Client.new
|
186
|
+
@pub = Publisher.new(@client)
|
187
|
+
end
|
188
|
+
|
189
|
+
test "initially there should be no queues for the current server" do
|
190
|
+
assert_equal({}, @pub.send(:queues))
|
191
|
+
assert !@pub.send(:queues)["some_queue"]
|
192
|
+
end
|
193
|
+
|
194
|
+
test "binding a queue should create it using the config and bind it to the exchange with the name specified" do
|
195
|
+
@client.register_queue("some_queue", :exchange => "some_exchange", :key => "haha.#")
|
196
|
+
@pub.expects(:exchange).with("some_exchange").returns(:the_exchange)
|
197
|
+
q = mock("queue")
|
198
|
+
q.expects(:bind).with(:the_exchange, {:key => "haha.#"})
|
199
|
+
m = mock("Bunny")
|
200
|
+
m.expects(:queue).with("some_queue", :durable => true, :passive => false, :auto_delete => false, :exclusive => false).returns(q)
|
201
|
+
@pub.expects(:bunny).returns(m)
|
202
|
+
|
203
|
+
@pub.send(:queue, "some_queue")
|
204
|
+
assert_equal q, @pub.send(:queues)["some_queue"]
|
205
|
+
end
|
206
|
+
|
207
|
+
test "should bind the defined queues for the used exchanges when publishing" do
|
208
|
+
@client.register_queue('test_queue_1', :exchange => 'test_exchange')
|
209
|
+
@client.register_queue('test_queue_2', :exchange => 'test_exchange')
|
210
|
+
@client.register_queue('test_queue_3', :exchange => 'test_exchange_2')
|
211
|
+
@pub.expects(:bind_queue!).returns(1).times(3)
|
212
|
+
@pub.send(:bind_queues_for_exchange, 'test_exchange')
|
213
|
+
@pub.send(:bind_queues_for_exchange, 'test_exchange_2')
|
214
|
+
end
|
215
|
+
|
216
|
+
test "should not rebind the defined queues for the used exchanges if they already have been bound" do
|
217
|
+
@client.register_queue('test_queue_1', :exchange => 'test_exchange')
|
218
|
+
@client.register_queue('test_queue_2', :exchange => 'test_exchange')
|
219
|
+
@pub.expects(:bind_queue!).twice
|
220
|
+
@pub.send(:bind_queues_for_exchange, 'test_exchange')
|
221
|
+
@pub.send(:bind_queues_for_exchange, 'test_exchange')
|
222
|
+
end
|
223
|
+
|
224
|
+
test "call the queue binding method when publishing" do
|
225
|
+
data = "XXX"
|
226
|
+
opts = {}
|
227
|
+
@client.register_queue("mama", :exchange => "mama-exchange")
|
228
|
+
@client.register_message("mama", :ttl => 1.hour, :exchange => "mama-exchange")
|
229
|
+
e = stub('exchange', 'publish')
|
230
|
+
@pub.expects(:exchange).with('mama-exchange').returns(e)
|
231
|
+
@pub.expects(:bind_queues_for_exchange).with('mama-exchange').returns(true)
|
232
|
+
@pub.publish('mama', data)
|
233
|
+
end
|
234
|
+
|
235
|
+
test "purging a queue should purge the queues on all servers" do
|
236
|
+
@pub.servers = %w(a b)
|
237
|
+
queue = mock("queue")
|
238
|
+
s = sequence("purging")
|
239
|
+
@pub.expects(:set_current_server).with("a").in_sequence(s)
|
240
|
+
@pub.expects(:queue).with("queue").returns(queue).in_sequence(s)
|
241
|
+
queue.expects(:purge).in_sequence(s)
|
242
|
+
@pub.expects(:set_current_server).with("b").in_sequence(s)
|
243
|
+
@pub.expects(:queue).with("queue").returns(queue).in_sequence(s)
|
244
|
+
queue.expects(:purge).in_sequence(s)
|
245
|
+
@pub.purge("queue")
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
class PublisherExchangeManagementTest < Test::Unit::TestCase
|
250
|
+
def setup
|
251
|
+
@client = Client.new
|
252
|
+
@pub = Publisher.new(@client)
|
253
|
+
end
|
254
|
+
|
255
|
+
test "initially there should be no exchanges for the current server" do
|
256
|
+
assert_equal({}, @pub.send(:exchanges))
|
257
|
+
end
|
258
|
+
|
259
|
+
test "accessing a given exchange should create it using the config. further access should return the created exchange" do
|
260
|
+
m = mock("Bunny")
|
261
|
+
m.expects(:exchange).with("some_exchange", :type => :topic, :durable => true).returns(42)
|
262
|
+
@client.register_exchange("some_exchange", :type => :topic, :durable => true)
|
263
|
+
@pub.expects(:bunny).returns(m)
|
264
|
+
ex = @pub.send(:exchange, "some_exchange")
|
265
|
+
assert @pub.send(:exchanges).include?("some_exchange")
|
266
|
+
ex2 = @pub.send(:exchange, "some_exchange")
|
267
|
+
assert_equal ex2, ex
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
class PublisherServerManagementTest < Test::Unit::TestCase
|
272
|
+
def setup
|
273
|
+
@client = Client.new
|
274
|
+
@pub = Publisher.new(@client)
|
275
|
+
end
|
276
|
+
|
277
|
+
test "marking the current server as dead should add it to the dead servers hash and remove it from the active servers list" do
|
278
|
+
@pub.servers = ["localhost:1111", "localhost:2222"]
|
279
|
+
@pub.send(:set_current_server, "localhost:2222")
|
280
|
+
@pub.send(:mark_server_dead)
|
281
|
+
assert_equal ["localhost:1111"], @pub.servers
|
282
|
+
dead = @pub.instance_variable_get "@dead_servers"
|
283
|
+
assert_equal ["localhost:2222"], dead.keys
|
284
|
+
assert_kind_of Time, dead["localhost:2222"]
|
285
|
+
end
|
286
|
+
|
287
|
+
test "recycle_dead_servers should move servers from the dead server hash to the servers list only if the have been markd dead for longer than 10 seconds" do
|
288
|
+
@pub.servers = ["a:1", "b:2"]
|
289
|
+
@pub.send(:set_current_server, "a:1")
|
290
|
+
@pub.send(:mark_server_dead)
|
291
|
+
assert_equal ["b:2"], @pub.servers
|
292
|
+
dead = @pub.instance_variable_get("@dead_servers")
|
293
|
+
dead["a:1"] = 9.seconds.ago
|
294
|
+
@pub.send(:recycle_dead_servers)
|
295
|
+
assert_equal ["a:1"], dead.keys
|
296
|
+
dead["a:1"] = 11.seconds.ago
|
297
|
+
@pub.send(:recycle_dead_servers)
|
298
|
+
assert_equal ["b:2", "a:1"], @pub.servers
|
299
|
+
assert_equal({}, dead)
|
300
|
+
end
|
301
|
+
|
302
|
+
test "select_next_server should cycle through the list of all servers" do
|
303
|
+
@pub.servers = ["a:1", "b:2"]
|
304
|
+
@pub.send(:set_current_server, "a:1")
|
305
|
+
@pub.send(:select_next_server)
|
306
|
+
assert_equal "b:2", @pub.server
|
307
|
+
@pub.send(:select_next_server)
|
308
|
+
assert_equal "a:1", @pub.server
|
309
|
+
end
|
310
|
+
|
311
|
+
test "select_next_server should return 0 if there are no servers to publish to" do
|
312
|
+
@pub.servers = []
|
313
|
+
logger = mock('logger')
|
314
|
+
logger.expects(:error).returns(true)
|
315
|
+
@pub.expects(:logger).returns(logger)
|
316
|
+
assert_equal 0, @pub.send(:select_next_server)
|
317
|
+
end
|
318
|
+
|
319
|
+
test "stop should shut down all bunnies" do
|
320
|
+
@pub.servers = ["localhost:1111", "localhost:2222"]
|
321
|
+
s = sequence("shutdown")
|
322
|
+
bunny = mock("bunny")
|
323
|
+
@pub.expects(:set_current_server).with("localhost:1111").in_sequence(s)
|
324
|
+
@pub.expects(:bunny).returns(bunny).in_sequence(s)
|
325
|
+
bunny.expects(:stop).in_sequence(s)
|
326
|
+
@pub.expects(:set_current_server).with("localhost:2222").in_sequence(s)
|
327
|
+
@pub.expects(:bunny).returns(bunny).in_sequence(s)
|
328
|
+
bunny.expects(:stop).in_sequence(s)
|
329
|
+
@pub.stop
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
|
334
|
+
class RPCTest < Test::Unit::TestCase
|
335
|
+
def setup
|
336
|
+
@client = Client.new
|
337
|
+
@pub = Publisher.new(@client)
|
338
|
+
@client.register_message(:test, :exchange => :some_exchange)
|
339
|
+
end
|
340
|
+
|
341
|
+
test "rpc should return a timeout status if bunny throws an exception" do
|
342
|
+
bunny = mock("bunny")
|
343
|
+
@pub.expects(:bunny).returns(bunny)
|
344
|
+
bunny.expects(:queue).raises(Bunny::ConnectionError.new)
|
345
|
+
s = sequence("rpc")
|
346
|
+
@pub.expects(:select_next_server).in_sequence(s)
|
347
|
+
@pub.expects(:bind_queues_for_exchange).with("some_exchange").in_sequence(s)
|
348
|
+
@pub.expects(:stop!)
|
349
|
+
assert_equal "TIMEOUT", @pub.rpc("test", "hello").first
|
350
|
+
end
|
351
|
+
|
352
|
+
test "rpc should return a timeout status if the answer doesn't arrive in time" do
|
353
|
+
bunny = mock("bunny")
|
354
|
+
reply_queue = mock("reply_queue")
|
355
|
+
exchange = mock("exchange")
|
356
|
+
@pub.expects(:bunny).returns(bunny)
|
357
|
+
bunny.expects(:queue).returns(reply_queue)
|
358
|
+
reply_queue.stubs(:name).returns("reply_queue")
|
359
|
+
s = sequence("rpc")
|
360
|
+
@pub.expects(:select_next_server).in_sequence(s)
|
361
|
+
@pub.expects(:bind_queues_for_exchange).with("some_exchange").in_sequence(s)
|
362
|
+
@pub.expects(:exchange).with("some_exchange").returns(exchange).in_sequence(s)
|
363
|
+
exchange.expects(:publish).in_sequence(s)
|
364
|
+
reply_queue.expects(:subscribe).with(:message_max => 1, :timeout => 10).in_sequence(s)
|
365
|
+
assert_equal "TIMEOUT", @pub.rpc("test", "hello").first
|
366
|
+
end
|
367
|
+
|
368
|
+
test "rpc should recover dead servers before selecting the next server" do
|
369
|
+
@pub.servers << "localhost:3333"
|
370
|
+
@pub.send(:mark_server_dead)
|
371
|
+
bunny = mock("bunny")
|
372
|
+
reply_queue = mock("reply_queue")
|
373
|
+
exchange = mock("exchange")
|
374
|
+
@pub.expects(:bunny).returns(bunny)
|
375
|
+
bunny.expects(:queue).returns(reply_queue)
|
376
|
+
reply_queue.stubs(:name).returns("reply_queue")
|
377
|
+
s = sequence("rpc")
|
378
|
+
@pub.expects(:recycle_dead_servers).in_sequence(s)
|
379
|
+
@pub.expects(:select_next_server).in_sequence(s)
|
380
|
+
@pub.expects(:bind_queues_for_exchange).with("some_exchange").in_sequence(s)
|
381
|
+
@pub.expects(:exchange).with("some_exchange").returns(exchange).in_sequence(s)
|
382
|
+
exchange.expects(:publish).in_sequence(s)
|
383
|
+
reply_queue.expects(:subscribe).with(:message_max => 1, :timeout => 10).in_sequence(s)
|
384
|
+
assert_equal "TIMEOUT", @pub.rpc("test", "hello").first
|
385
|
+
end
|
386
|
+
|
387
|
+
test "rpc should fetch the result and the status code from the reply message" do
|
388
|
+
bunny = mock("bunny")
|
389
|
+
reply_queue = mock("reply_queue")
|
390
|
+
exchange = mock("exchange")
|
391
|
+
@pub.expects(:bunny).returns(bunny)
|
392
|
+
bunny.expects(:queue).returns(reply_queue)
|
393
|
+
reply_queue.stubs(:name).returns("reply_queue")
|
394
|
+
s = sequence("rpc")
|
395
|
+
@pub.expects(:select_next_server).in_sequence(s)
|
396
|
+
@pub.expects(:bind_queues_for_exchange).with("some_exchange").in_sequence(s)
|
397
|
+
@pub.expects(:exchange).with("some_exchange").returns(exchange).in_sequence(s)
|
398
|
+
exchange.expects(:publish).in_sequence(s)
|
399
|
+
header = mock("header")
|
400
|
+
header.expects(:properties).returns({:headers => {:status => "OK"}})
|
401
|
+
msg = {:payload => 1, :header => header}
|
402
|
+
reply_queue.expects(:subscribe).with(:message_max => 1, :timeout => 10).in_sequence(s).yields(msg)
|
403
|
+
assert_equal ["OK",1], @pub.rpc("test", "hello")
|
404
|
+
end
|
405
|
+
|
406
|
+
end
|
407
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
|
2
|
+
|
3
|
+
module Beetle
|
4
|
+
class ReturnCodesTest < Test::Unit::TestCase
|
5
|
+
test "inspecting a return code should display the name of the returncode" do
|
6
|
+
assert_equal "Beetle::RC::OK", Beetle::RC::OK.inspect
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,263 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
|
2
|
+
|
3
|
+
module Beetle
|
4
|
+
class SubscriberTest < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
client = Client.new
|
7
|
+
@sub = client.send(:subscriber)
|
8
|
+
end
|
9
|
+
|
10
|
+
test "initially there should be no amqp connections" do
|
11
|
+
assert_equal({}, @sub.instance_variable_get("@amqp_connections"))
|
12
|
+
end
|
13
|
+
|
14
|
+
test "initially there should be no instances of MQ" do
|
15
|
+
assert_equal({}, @sub.instance_variable_get("@mqs"))
|
16
|
+
end
|
17
|
+
|
18
|
+
test "acccessing an amq_connection for a server which doesn't have one should create it and associate it with the server" do
|
19
|
+
@sub.expects(:new_amqp_connection).returns(42)
|
20
|
+
# TODO: smarter way to test? what triggers the amqp_connection private method call?
|
21
|
+
assert_equal 42, @sub.send(:amqp_connection)
|
22
|
+
connections = @sub.instance_variable_get("@amqp_connections")
|
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 => "/"
|
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]
|
45
|
+
end
|
46
|
+
|
47
|
+
test "stop! should stop the event loop" do
|
48
|
+
EM.expects(:stop_event_loop)
|
49
|
+
@sub.send(:stop!)
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
class SubscriberQueueManagementTest < Test::Unit::TestCase
|
55
|
+
def setup
|
56
|
+
@client = Client.new
|
57
|
+
@sub = @client.send(:subscriber)
|
58
|
+
end
|
59
|
+
|
60
|
+
test "initially there should be no queues for the current server" do
|
61
|
+
assert_equal({}, @sub.send(:queues))
|
62
|
+
assert !@sub.send(:queues)["some_queue"]
|
63
|
+
end
|
64
|
+
|
65
|
+
test "binding a queue should create it using the config and bind it to the exchange with the name specified" do
|
66
|
+
@client.register_queue("some_queue", "durable" => true, "exchange" => "some_exchange", "key" => "haha.#")
|
67
|
+
@sub.expects(:exchange).with("some_exchange").returns(:the_exchange)
|
68
|
+
q = mock("queue")
|
69
|
+
q.expects(:bind).with(:the_exchange, {:key => "haha.#"})
|
70
|
+
m = mock("MQ")
|
71
|
+
m.expects(:queue).with("some_queue", :durable => true, :passive => false, :auto_delete => false, :exclusive => false).returns(q)
|
72
|
+
@sub.expects(:mq).returns(m)
|
73
|
+
|
74
|
+
@sub.send(:queue, "some_queue")
|
75
|
+
assert_equal q, @sub.send(:queues)["some_queue"]
|
76
|
+
end
|
77
|
+
|
78
|
+
test "binding queues should iterate over all servers" do
|
79
|
+
s = sequence("binding")
|
80
|
+
@client.register_queue(:x)
|
81
|
+
@client.register_queue(:y)
|
82
|
+
@client.register_handler(%w(x y)){}
|
83
|
+
@sub.servers = %w(a b)
|
84
|
+
@sub.expects(:set_current_server).with("a").in_sequence(s)
|
85
|
+
@sub.expects(:queue).with("x").in_sequence(s)
|
86
|
+
@sub.expects(:queue).with("y").in_sequence(s)
|
87
|
+
@sub.expects(:set_current_server).with("b").in_sequence(s)
|
88
|
+
@sub.expects(:queue).with("x").in_sequence(s)
|
89
|
+
@sub.expects(:queue).with("y").in_sequence(s)
|
90
|
+
@sub.send(:bind_queues, %W(x y))
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
class SubscriberExchangeManagementTest < Test::Unit::TestCase
|
96
|
+
def setup
|
97
|
+
@client = Client.new
|
98
|
+
@sub = @client.send(:subscriber)
|
99
|
+
end
|
100
|
+
|
101
|
+
test "initially there should be no exchanges for the current server" do
|
102
|
+
assert_equal({}, @sub.send(:exchanges))
|
103
|
+
end
|
104
|
+
|
105
|
+
test "accessing a given exchange should create it using the config. further access should return the created exchange" do
|
106
|
+
@client.register_exchange("some_exchange", "type" => "topic", "durable" => true)
|
107
|
+
m = mock("AMQP")
|
108
|
+
m.expects(:topic).with("some_exchange", :durable => true).returns(42)
|
109
|
+
@sub.expects(:mq).returns(m)
|
110
|
+
ex = @sub.send(:exchange, "some_exchange")
|
111
|
+
assert @sub.send(:exchanges).include?("some_exchange")
|
112
|
+
ex2 = @sub.send(:exchange, "some_exchange")
|
113
|
+
assert_equal ex2, ex
|
114
|
+
end
|
115
|
+
|
116
|
+
test "should create exchanges for all exchanges passed to create_exchanges, for all servers" do
|
117
|
+
@sub.servers = %w(x y)
|
118
|
+
@client.register_queue(:donald, :exchange => 'duck')
|
119
|
+
@client.register_queue(:mickey)
|
120
|
+
@client.register_queue(:mouse, :exchange => 'mickey')
|
121
|
+
|
122
|
+
exchange_creation = sequence("exchange creation")
|
123
|
+
@sub.expects(:set_current_server).with('x').in_sequence(exchange_creation)
|
124
|
+
@sub.expects(:create_exchange!).with("duck", anything).in_sequence(exchange_creation)
|
125
|
+
@sub.expects(:create_exchange!).with("mickey", anything).in_sequence(exchange_creation)
|
126
|
+
@sub.expects(:set_current_server).with('y', anything).in_sequence(exchange_creation)
|
127
|
+
@sub.expects(:create_exchange!).with("duck", anything).in_sequence(exchange_creation)
|
128
|
+
@sub.expects(:create_exchange!).with("mickey", anything).in_sequence(exchange_creation)
|
129
|
+
@sub.send(:create_exchanges, %w(duck mickey))
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
class CallBackExecutionTest < Test::Unit::TestCase
|
134
|
+
def setup
|
135
|
+
client = Client.new
|
136
|
+
@queue = "somequeue"
|
137
|
+
client.register_queue(@queue)
|
138
|
+
@sub = client.send(:subscriber)
|
139
|
+
@exception = Exception.new "murks"
|
140
|
+
@handler = Handler.create(lambda{|*args| raise @exception})
|
141
|
+
@callback = @sub.send(:create_subscription_callback, "my myessage", @queue, @handler, :exceptions => 1)
|
142
|
+
end
|
143
|
+
|
144
|
+
test "exceptions raised from message processing should be ignored" do
|
145
|
+
header = header_with_params({})
|
146
|
+
Message.any_instance.expects(:process).raises(Exception.new)
|
147
|
+
assert_nothing_raised { @callback.call(header, 'foo') }
|
148
|
+
end
|
149
|
+
|
150
|
+
test "should call recover on the server when processing the handler returns true on recover?" do
|
151
|
+
header = header_with_params({})
|
152
|
+
result = mock("result")
|
153
|
+
result.expects(:recover?).returns(true)
|
154
|
+
Message.any_instance.expects(:process).returns(result)
|
155
|
+
@sub.expects(:sleep).with(1)
|
156
|
+
mq = mock("MQ")
|
157
|
+
mq.expects(:recover)
|
158
|
+
@sub.expects(:mq).with(@sub.server).returns(mq)
|
159
|
+
@callback.call(header, 'foo')
|
160
|
+
end
|
161
|
+
|
162
|
+
test "should sent a reply with status OK if the message reply_to header is set and processing the handler succeeds" do
|
163
|
+
header = header_with_params(:reply_to => "tmp-queue")
|
164
|
+
result = RC::OK
|
165
|
+
Message.any_instance.expects(:process).returns(result)
|
166
|
+
Message.any_instance.expects(:handler_result).returns("response-data")
|
167
|
+
mq = mock("MQ")
|
168
|
+
@sub.expects(:mq).with(@sub.server).returns(mq)
|
169
|
+
exchange = mock("exchange")
|
170
|
+
exchange.expects(:publish).with("response-data", :headers => {:status => "OK"})
|
171
|
+
MQ::Exchange.expects(:new).with(mq, :direct, "", :key => "tmp-queue").returns(exchange)
|
172
|
+
@callback.call(header, 'foo')
|
173
|
+
end
|
174
|
+
|
175
|
+
test "should sent a reply with status FAILED if the message reply_to header is set and processing the handler fails" do
|
176
|
+
header = header_with_params(:reply_to => "tmp-queue")
|
177
|
+
result = RC::AttemptsLimitReached
|
178
|
+
Message.any_instance.expects(:process).returns(result)
|
179
|
+
Message.any_instance.expects(:handler_result).returns(nil)
|
180
|
+
mq = mock("MQ")
|
181
|
+
@sub.expects(:mq).with(@sub.server).returns(mq)
|
182
|
+
exchange = mock("exchange")
|
183
|
+
exchange.expects(:publish).with("", :headers => {:status => "FAILED"})
|
184
|
+
MQ::Exchange.expects(:new).with(mq, :direct, "", :key => "tmp-queue").returns(exchange)
|
185
|
+
@callback.call(header, 'foo')
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
|
190
|
+
class SubscriptionTest < Test::Unit::TestCase
|
191
|
+
def setup
|
192
|
+
@client = Client.new
|
193
|
+
@sub = @client.send(:subscriber)
|
194
|
+
end
|
195
|
+
|
196
|
+
test "subscribe should create subscriptions on all queues for all servers" do
|
197
|
+
@sub.servers << "localhost:7777"
|
198
|
+
@client.register_message(:a)
|
199
|
+
@client.register_message(:b)
|
200
|
+
@client.register_queue(:a)
|
201
|
+
@client.register_queue(:b)
|
202
|
+
@client.register_handler(%W(a b)){}
|
203
|
+
@sub.expects(:subscribe).with("a").times(2)
|
204
|
+
@sub.expects(:subscribe).with("b").times(2)
|
205
|
+
@sub.send(:subscribe_queues, %W(a b))
|
206
|
+
end
|
207
|
+
|
208
|
+
test "subscribe should subscribe with a subscription callback created from the registered block" do
|
209
|
+
@client.register_queue(:some_queue, :exchange => "some_exchange", :key => "some_key")
|
210
|
+
server = @sub.server
|
211
|
+
header = header_with_params({})
|
212
|
+
header.expects(:ack)
|
213
|
+
block_called = false
|
214
|
+
proc = lambda do |m|
|
215
|
+
block_called = true
|
216
|
+
assert_equal header, m.header
|
217
|
+
assert_equal "data", m.data
|
218
|
+
assert_equal server, m.server
|
219
|
+
end
|
220
|
+
@sub.register_handler("some_queue", &proc)
|
221
|
+
q = mock("QUEUE")
|
222
|
+
q.expects(:subscribe).with({:ack => true, :key => "#"}).yields(header, 'foo')
|
223
|
+
@sub.expects(:queues).returns({"some_queue" => q})
|
224
|
+
@sub.send(:subscribe, "some_queue")
|
225
|
+
assert block_called
|
226
|
+
end
|
227
|
+
|
228
|
+
test "subscribe should fail if no handler exists for given message" do
|
229
|
+
assert_raises(Error){ @sub.send(:subscribe, "some_queue") }
|
230
|
+
end
|
231
|
+
|
232
|
+
test "listening should use eventmachine. create exchanges. bind queues. install subscribers. and yield." do
|
233
|
+
@client.register_queue(:a)
|
234
|
+
@client.register_message(:a)
|
235
|
+
EM.expects(:run).yields
|
236
|
+
@sub.expects(:create_exchanges).with(["a"])
|
237
|
+
@sub.expects(:bind_queues).with(["a"])
|
238
|
+
@sub.expects(:subscribe_queues).with(["a"])
|
239
|
+
@sub.listen(["a"]) {}
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
class HandlersTest < Test::Unit::TestCase
|
244
|
+
def setup
|
245
|
+
@client = Client.new
|
246
|
+
@sub = @client.send(:subscriber)
|
247
|
+
end
|
248
|
+
|
249
|
+
test "initially we should have no handlers" do
|
250
|
+
assert_equal({}, @sub.instance_variable_get("@handlers"))
|
251
|
+
end
|
252
|
+
|
253
|
+
test "registering a handler for a queue should store it in the configuration with symbolized option keys" do
|
254
|
+
opts = {"ack" => true}
|
255
|
+
@sub.register_handler("some_queue", opts){ |*args| 42 }
|
256
|
+
opts, block = @sub.instance_variable_get("@handlers")["some_queue"]
|
257
|
+
assert_equal({:ack => true}, opts)
|
258
|
+
assert_equal 42, block.call(1)
|
259
|
+
end
|
260
|
+
|
261
|
+
end
|
262
|
+
|
263
|
+
end
|