beetle 0.3.0.rc.3 → 0.3.0.rc.4

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
1
  require 'rake'
2
2
  require 'rake/testtask'
3
3
  require 'rcov/rcovtask'
4
+ require 'bundler/gem_tasks'
5
+
4
6
  # rake 0.9.2 hack to supress deprecation warnings caused by cucumber
5
7
  include Rake::DSL if RAKEVERSION >= "0.9"
6
8
  require 'cucumber/rake/task'
@@ -30,7 +32,6 @@ namespace :test do
30
32
  end if RUBY_PLATFORM =~ /darwin/
31
33
  end
32
34
 
33
-
34
35
  namespace :beetle do
35
36
  task :test do
36
37
  Beetle::Client.new.test
@@ -108,8 +109,3 @@ Rake::RDocTask.new do |rdoc|
108
109
  rdoc.rdoc_files.include('MIT-LICENSE')
109
110
  rdoc.rdoc_files.include('lib/**/*.rb')
110
111
  end
111
-
112
- desc "build the beetle gem"
113
- task :build do
114
- system("gem build beetle.gemspec")
115
- end
data/beetle.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "beetle"
3
- s.version = "0.3.0.rc.3"
3
+ s.version = "0.3.0.rc.4"
4
4
  s.required_rubygems_version = ">= 1.3.7"
5
5
  s.authors = ["Stefan Kaes", "Pascal Friederich", "Ali Jelveh", "Sebastian Roebke"]
6
6
  s.date = Time.now.strftime('%Y-%m-%d')
@@ -29,10 +29,12 @@ Gem::Specification.new do |s|
29
29
 
30
30
  s.specification_version = 3
31
31
  s.add_runtime_dependency("uuid4r", [">= 0.1.2"])
32
- s.add_runtime_dependency("bunny", ["~> 0.7.1"])
32
+ s.add_runtime_dependency("bunny", ["= 0.7.8"])
33
33
  s.add_runtime_dependency("redis", ["= 2.2.0"])
34
34
  s.add_runtime_dependency("hiredis", ["= 0.3.2"])
35
- s.add_runtime_dependency("amqp", ["~> 0.6.7"])
35
+ s.add_runtime_dependency("amq-client", ["= 0.8.3"])
36
+ s.add_runtime_dependency("amq-protocol", ["= 0.8.1"])
37
+ s.add_runtime_dependency("amqp", ["= 0.8.0"])
36
38
  s.add_runtime_dependency("activesupport", [">= 2.3.4"])
37
39
  s.add_runtime_dependency("daemons", [">= 1.0.10"])
38
40
  s.add_development_dependency("rake", [">= 0.8.7"])
@@ -0,0 +1,28 @@
1
+ # nonexistent_server.rb
2
+ # this example shows what happens when you try connect to a nonexistent server
3
+ #
4
+ # start it with ruby nonexistent_server.rb
5
+
6
+ require "rubygems"
7
+ require File.expand_path("../lib/beetle", File.dirname(__FILE__))
8
+
9
+ # set Beetle log level to info, less noisy than debug
10
+ Beetle.config.logger.level = Logger::INFO
11
+
12
+ Beetle.config.servers = "unknown.railsexpress.de:5672"
13
+
14
+ # setup client
15
+ client = Beetle::Client.new
16
+ client.register_queue(:test)
17
+ client.register_message(:test)
18
+
19
+ # register our handler to the message, check out the message.rb for more stuff you can get from the message object
20
+ client.register_handler(:test) {|message| puts "got message: #{message.data}"}
21
+
22
+ # start listening
23
+ # this starts the event machine event loop using EM.run
24
+ # the block passed to listen will be yielded as the last step of the setup process
25
+ client.listen do
26
+ EM.add_timer(10) { client.stop_listening }
27
+ end
28
+
@@ -58,7 +58,7 @@ end
58
58
  # this starts the event machine event loop using EM.run
59
59
  # the block passed to listen will be yielded as the last step of the setup process
60
60
  client.listen do
61
- EM.add_timer(0.1) { client.stop_listening }
61
+ EM.add_timer(0.2) { client.stop_listening }
62
62
  end
63
63
 
64
64
  puts "Received #{k} test messages"
data/examples/rpc.rb CHANGED
@@ -3,7 +3,7 @@ require "rubygems"
3
3
  require File.expand_path(File.dirname(__FILE__)+"/../lib/beetle")
4
4
 
5
5
  # suppress debug messages
6
- Beetle.config.logger.level = Logger::DEBUG
6
+ Beetle.config.logger.level = Logger::INFO
7
7
  Beetle.config.servers = "localhost:5672, localhost:5673"
8
8
  # instantiate a client
9
9
 
@@ -28,12 +28,12 @@ if ARGV.include?("--server")
28
28
  trap("INT") { puts "stopped echo server"; client.stop_listening }
29
29
  end
30
30
  else
31
- n = 100
31
+ n = 10000
32
32
  ms = Benchmark.ms do
33
33
  n.times do |i|
34
34
  content = "Hello #{i}"
35
35
  # puts "performing RPC with message content '#{content}'"
36
- status, result = client.rpc(:echo, content)
36
+ status, result = client.rpc(:echo, content, :persistent => false)
37
37
  # puts "status #{status}"
38
38
  # puts "result #{result}"
39
39
  # puts
data/lib/beetle/base.rb CHANGED
@@ -33,6 +33,10 @@ module Beetle
33
33
  @server = s
34
34
  end
35
35
 
36
+ def server_from_settings(settings)
37
+ settings.values_at(:host,:port).join(':')
38
+ end
39
+
36
40
  def each_server
37
41
  @servers.each { |s| set_current_server(s); yield }
38
42
  end
@@ -73,9 +73,10 @@ module Beetle
73
73
 
74
74
  # extracts various values form the AMQP header properties
75
75
  def decode #:nodoc:
76
- amqp_headers = header.properties
76
+ # p header.attributes
77
+ amqp_headers = header.attributes
77
78
  @uuid = amqp_headers[:message_id]
78
- headers = amqp_headers[:headers]
79
+ headers = amqp_headers[:headers].symbolize_keys
79
80
  @format_version = headers[:format_version].to_i
80
81
  @flags = headers[:flags].to_i
81
82
  @expires_at = headers[:expires_at].to_i
@@ -1,5 +1,4 @@
1
1
  require 'amqp'
2
- require 'mq'
3
2
 
4
3
  module Beetle
5
4
  # Manages subscriptions and message processing on the receiver side of things.
@@ -10,9 +9,10 @@ module Beetle
10
9
  super
11
10
  @servers.concat @client.additional_subscription_servers
12
11
  @handlers = {}
13
- @amqp_connections = {}
14
- @mqs = {}
12
+ @connections = {}
13
+ @channels = {}
15
14
  @subscriptions = {}
15
+ @listened_queues = []
16
16
  end
17
17
 
18
18
  # the client calls this method to subscribe to a list of queues.
@@ -24,11 +24,12 @@ module Beetle
24
24
  #
25
25
  # yields before entering the eventmachine loop (if a block was given)
26
26
  def listen_queues(queues) #:nodoc:
27
+ @listened_queues = queues
28
+ @exchanges_for_queues = exchanges_for_queues(queues)
27
29
  EM.run do
28
- exchanges = exchanges_for_queues(queues)
29
- create_exchanges(exchanges)
30
- bind_queues(queues)
31
- subscribe_queues(queues)
30
+ each_server do
31
+ connect_server connection_settings
32
+ end
32
33
  yield if block_given?
33
34
  end
34
35
  end
@@ -47,10 +48,10 @@ module Beetle
47
48
 
48
49
  # closes all AMQP connections and stops the eventmachine loop
49
50
  def stop! #:nodoc:
50
- if @amqp_connections.empty?
51
+ if @connections.empty?
51
52
  EM.stop_event_loop
52
53
  else
53
- server, connection = @amqp_connections.shift
54
+ server, connection = @connections.shift
54
55
  logger.debug "Beetle: closing connection to #{server}"
55
56
  connection.close { stop! }
56
57
  end
@@ -74,30 +75,19 @@ module Beetle
74
75
  end
75
76
 
76
77
  def create_exchanges(exchanges)
77
- each_server do
78
- exchanges.each { |name| exchange(name) }
79
- end
78
+ exchanges.each { |name| exchange(name) }
80
79
  end
81
80
 
82
81
  def bind_queues(queues)
83
- each_server do
84
- queues.each { |name| queue(name) }
85
- end
82
+ queues.each { |name| queue(name) }
86
83
  end
87
84
 
88
85
  def subscribe_queues(queues)
89
- each_server do
90
- queues.each { |name| subscribe(name) if @handlers.include?(name) }
91
- end
86
+ queues.each { |name| subscribe(name) if @handlers.include?(name) }
92
87
  end
93
88
 
94
- # returns the mq object for the given server or returns a new one created with the
95
- # prefetch(1) option. this tells it to just send one message to the receiving buffer
96
- # (instead of filling it). this is necesssary to ensure that one subscriber always just
97
- # handles one single message. we cannot ensure reliability if the buffer is filled with
98
- # messages and crashes.
99
- def mq(server=@server)
100
- @mqs[server] ||= MQ.new(amqp_connection).prefetch(1)
89
+ def channel(server=@server)
90
+ @channels[server]
101
91
  end
102
92
 
103
93
  def subscriptions(server=@server)
@@ -116,12 +106,8 @@ module Beetle
116
106
  callback = create_subscription_callback(queue_name, amqp_queue_name, handler, opts)
117
107
  keys = opts.slice(*SUBSCRIPTION_KEYS).merge(:key => "#", :ack => true)
118
108
  logger.debug "Beetle: subscribing to queue #{amqp_queue_name} with key # on server #{@server}"
119
- begin
120
- queues[queue_name].subscribe(keys, &callback)
121
- subscriptions[queue_name] = [keys, callback]
122
- rescue MQ::Error
123
- error("Beetle: binding multiple handlers for the same queue isn't possible.")
124
- end
109
+ queues[queue_name].subscribe(keys, &callback)
110
+ subscriptions[queue_name] = [keys, callback]
125
111
  end
126
112
 
127
113
  def pause(queue_name)
@@ -147,13 +133,14 @@ module Beetle
147
133
  if result.reject?
148
134
  sleep 1
149
135
  header.reject(:requeue => true)
150
- elsif reply_to = header.properties[:reply_to]
136
+ elsif reply_to = header.attributes[:reply_to]
137
+ # logger.info "Beetle: sending reply to queue #{reply_to}"
151
138
  # require 'ruby-debug'
152
139
  # Debugger.start
153
140
  # debugger
154
141
  status = result == Beetle::RC::OK ? "OK" : "FAILED"
155
- exchange = MQ::Exchange.new(mq(server), :direct, "", :key => reply_to)
156
- exchange.publish(m.handler_result.to_s, :headers => {:status => status})
142
+ exchange = AMQP::Exchange.new(channel(server), :direct, "")
143
+ exchange.publish(m.handler_result.to_s, :routing_key => reply_to, :persistent => false, :headers => {:status => status})
157
144
  end
158
145
  # logger.debug "Beetle: processed message"
159
146
  rescue Exception
@@ -168,27 +155,63 @@ module Beetle
168
155
  end
169
156
 
170
157
  def create_exchange!(name, opts)
171
- mq.__send__(opts[:type], name, opts.slice(*EXCHANGE_CREATION_KEYS))
158
+ channel.__send__(opts[:type], name, opts.slice(*EXCHANGE_CREATION_KEYS))
172
159
  end
173
160
 
174
161
  def bind_queue!(queue_name, creation_keys, exchange_name, binding_keys)
175
- queue = mq.queue(queue_name, creation_keys)
162
+ queue = channel.queue(queue_name, creation_keys)
176
163
  exchange = exchange(exchange_name)
177
164
  queue.bind(exchange, binding_keys)
178
165
  queue
179
166
  end
180
167
 
181
- def amqp_connection(server=@server)
182
- @amqp_connections[server] ||= new_amqp_connection
168
+ def connection_settings
169
+ {
170
+ :host => current_host, :port => current_port, :logging => false,
171
+ :user => Beetle.config.user, :pass => Beetle.config.password, :vhost => Beetle.config.vhost,
172
+ :on_tcp_connection_failure => on_tcp_connection_failure
173
+ }
174
+ end
175
+
176
+ def on_tcp_connection_failure
177
+ Proc.new do |settings|
178
+ logger.warn "Beetle: connection failed: #{server_from_settings(settings)}"
179
+ EM::Timer.new(10) { connect_server(settings) }
180
+ end
183
181
  end
184
182
 
185
- def new_amqp_connection
186
- # FIXME: wtf, how to test that reconnection feature....
187
- con = AMQP.connect(:host => current_host, :port => current_port, :logging => false,
188
- :user => Beetle.config.user, :pass => Beetle.config.password, :vhost => Beetle.config.vhost)
189
- con.instance_variable_set("@on_disconnect", proc{ con.__send__(:reconnect) })
190
- con
183
+ def on_tcp_connection_loss(connection, settings)
184
+ # reconnect in 10 seconds, without enforcement
185
+ logger.warn "Beetle: lost connection: #{server_from_settings(settings)}. reconnecting."
186
+ connection.reconnect(false, 10)
191
187
  end
192
188
 
189
+ def connect_server(settings)
190
+ server = server_from_settings settings
191
+ logger.info "Beetle: connecting to rabbit #{server}"
192
+ AMQP.connect(settings) do |connection|
193
+ connection.on_tcp_connection_loss(&method(:on_tcp_connection_loss))
194
+ @connections[server] = connection
195
+ open_channel_and_subscribe(connection, settings)
196
+ end
197
+ rescue EventMachine::ConnectionError => e
198
+ # something serious went wrong, for example DNS lookup failure
199
+ # in this case, the on_tcp_connection_failure callback is never called automatically
200
+ logger.error "Beetle: connection failed: #{e.class}(#{e})"
201
+ settings[:on_tcp_connection_failure].call(settings)
202
+ end
203
+
204
+ def open_channel_and_subscribe(connection, settings)
205
+ server = server_from_settings settings
206
+ AMQP::Channel.new(connection) do |channel|
207
+ channel.auto_recovery = true
208
+ channel.prefetch(1)
209
+ set_current_server server
210
+ @channels[server] = channel
211
+ create_exchanges(@exchanges_for_queues)
212
+ bind_queues(@listened_queues)
213
+ subscribe_queues(@listened_queues)
214
+ end
215
+ end
193
216
  end
194
217
  end
data/script/console~ ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.expand_path("../lib",__FILE__))
@@ -0,0 +1,30 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+ require 'eventmachine'
3
+ require 'amqp'
4
+
5
+ class AMQPGemBehaviorTest < Test::Unit::TestCase
6
+ test "subscribing twice to the same queue raises a RuntimeError which throws us out of the event loop" do
7
+ begin
8
+ @exception = nil
9
+ EM.run do
10
+ AMQP.start do |connection|
11
+ begin
12
+ EM::Timer.new(1){ connection.close { EM.stop }}
13
+ channel = AMQP::Channel.new(connection)
14
+ channel.on_error { puts "woot"}
15
+ exchange = channel.topic("beetle_tests")
16
+ queue = AMQP::Queue.new(channel)
17
+ queue.bind(exchange, :key => "#")
18
+ queue.subscribe { }
19
+ queue.subscribe { }
20
+ rescue
21
+ # we never get here, because the subscription is deferred
22
+ # the only known way to avoid this is to use the block version of AMQP::Queue.new
23
+ end
24
+ end
25
+ end
26
+ rescue Exception => @exception
27
+ end
28
+ assert @exception
29
+ end
30
+ end
@@ -48,5 +48,9 @@ module Beetle
48
48
  @bs.send(:set_current_server, "xxx:123")
49
49
  assert_equal "xxx:123", @bs.server
50
50
  end
51
+
52
+ test "server_from_settings should create a valid server string from an AMQP settings hash" do
53
+ assert_equal "goofy:123", @bs.send(:server_from_settings, {:host => "goofy", :port => 123})
54
+ end
51
55
  end
52
56
  end
@@ -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("@amqp_connections"))
11
+ assert_equal({}, @sub.instance_variable_get("@connections"))
12
12
  end
13
13
 
14
- test "initially there should be no instances of MQ" do
15
- assert_equal({}, @sub.instance_variable_get("@mqs"))
14
+ test "initially there should be no channels" do
15
+ assert_equal({}, @sub.instance_variable_get("@channels"))
16
16
  end
17
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 => "/", :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 "@amqp_connections", [["server1", connection1], ["server2",connection2]]
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(:mq).returns(m)
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 iterate over all servers" do
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.servers = %w(a b)
171
- @sub.expects(:set_current_server).with("a").in_sequence(s)
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(:mq).returns(m)
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, for all servers" do
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
- exchange_creation = sequence("exchange creation")
214
- @sub.expects(:set_current_server).with('x').in_sequence(exchange_creation)
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 recover?" do
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(:mq).with(@sub.server).returns(mq)
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
- MQ::Exchange.expects(:new).with(mq, :direct, "", :key => "tmp-queue").returns(exchange)
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(:mq).with(@sub.server).returns(mq)
256
+ @sub.expects(:channel).with(@sub.server).returns(mq)
273
257
  exchange = mock("exchange")
274
- exchange.expects(:publish).with("", :headers => {:status => "FAILED"})
275
- MQ::Exchange.expects(:new).with(mq, :direct, "", :key => "tmp-queue").returns(exchange)
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}).twice
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. create exchanges. bind queues. install subscribers. and yield." do
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
- @sub.listen_queues(["a_queue"]) {}
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(:properties).returns(beetle_headers)
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"
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: beetle
3
3
  version: !ruby/object:Gem::Version
4
- hash: 15424035
4
+ hash: 15424045
5
5
  prerelease: 6
6
6
  segments:
7
7
  - 0
8
8
  - 3
9
9
  - 0
10
10
  - rc
11
- - 3
12
- version: 0.3.0.rc.3
11
+ - 4
12
+ version: 0.3.0.rc.4
13
13
  platform: ruby
14
14
  authors:
15
15
  - Stefan Kaes
@@ -20,7 +20,8 @@ autorequire:
20
20
  bindir: bin
21
21
  cert_chain: []
22
22
 
23
- date: 2011-10-05 00:00:00 Z
23
+ date: 2011-10-11 00:00:00 +02:00
24
+ default_executable: beetle
24
25
  dependencies:
25
26
  - !ruby/object:Gem::Dependency
26
27
  name: uuid4r
@@ -44,14 +45,14 @@ dependencies:
44
45
  requirement: &id002 !ruby/object:Gem::Requirement
45
46
  none: false
46
47
  requirements:
47
- - - ~>
48
+ - - "="
48
49
  - !ruby/object:Gem::Version
49
- hash: 1
50
+ hash: 19
50
51
  segments:
51
52
  - 0
52
53
  - 7
53
- - 1
54
- version: 0.7.1
54
+ - 8
55
+ version: 0.7.8
55
56
  type: :runtime
56
57
  version_requirements: *id002
57
58
  - !ruby/object:Gem::Dependency
@@ -87,25 +88,57 @@ dependencies:
87
88
  type: :runtime
88
89
  version_requirements: *id004
89
90
  - !ruby/object:Gem::Dependency
90
- name: amqp
91
+ name: amq-client
91
92
  prerelease: false
92
93
  requirement: &id005 !ruby/object:Gem::Requirement
93
94
  none: false
94
95
  requirements:
95
- - - ~>
96
+ - - "="
96
97
  - !ruby/object:Gem::Version
97
- hash: 9
98
+ hash: 57
98
99
  segments:
99
100
  - 0
100
- - 6
101
- - 7
102
- version: 0.6.7
101
+ - 8
102
+ - 3
103
+ version: 0.8.3
103
104
  type: :runtime
104
105
  version_requirements: *id005
105
106
  - !ruby/object:Gem::Dependency
106
- name: activesupport
107
+ name: amq-protocol
107
108
  prerelease: false
108
109
  requirement: &id006 !ruby/object:Gem::Requirement
110
+ none: false
111
+ requirements:
112
+ - - "="
113
+ - !ruby/object:Gem::Version
114
+ hash: 61
115
+ segments:
116
+ - 0
117
+ - 8
118
+ - 1
119
+ version: 0.8.1
120
+ type: :runtime
121
+ version_requirements: *id006
122
+ - !ruby/object:Gem::Dependency
123
+ name: amqp
124
+ prerelease: false
125
+ requirement: &id007 !ruby/object:Gem::Requirement
126
+ none: false
127
+ requirements:
128
+ - - "="
129
+ - !ruby/object:Gem::Version
130
+ hash: 63
131
+ segments:
132
+ - 0
133
+ - 8
134
+ - 0
135
+ version: 0.8.0
136
+ type: :runtime
137
+ version_requirements: *id007
138
+ - !ruby/object:Gem::Dependency
139
+ name: activesupport
140
+ prerelease: false
141
+ requirement: &id008 !ruby/object:Gem::Requirement
109
142
  none: false
110
143
  requirements:
111
144
  - - ">="
@@ -117,11 +150,11 @@ dependencies:
117
150
  - 4
118
151
  version: 2.3.4
119
152
  type: :runtime
120
- version_requirements: *id006
153
+ version_requirements: *id008
121
154
  - !ruby/object:Gem::Dependency
122
155
  name: daemons
123
156
  prerelease: false
124
- requirement: &id007 !ruby/object:Gem::Requirement
157
+ requirement: &id009 !ruby/object:Gem::Requirement
125
158
  none: false
126
159
  requirements:
127
160
  - - ">="
@@ -133,11 +166,11 @@ dependencies:
133
166
  - 10
134
167
  version: 1.0.10
135
168
  type: :runtime
136
- version_requirements: *id007
169
+ version_requirements: *id009
137
170
  - !ruby/object:Gem::Dependency
138
171
  name: rake
139
172
  prerelease: false
140
- requirement: &id008 !ruby/object:Gem::Requirement
173
+ requirement: &id010 !ruby/object:Gem::Requirement
141
174
  none: false
142
175
  requirements:
143
176
  - - ">="
@@ -149,11 +182,11 @@ dependencies:
149
182
  - 7
150
183
  version: 0.8.7
151
184
  type: :development
152
- version_requirements: *id008
185
+ version_requirements: *id010
153
186
  - !ruby/object:Gem::Dependency
154
187
  name: mocha
155
188
  prerelease: false
156
- requirement: &id009 !ruby/object:Gem::Requirement
189
+ requirement: &id011 !ruby/object:Gem::Requirement
157
190
  none: false
158
191
  requirements:
159
192
  - - ">="
@@ -163,11 +196,11 @@ dependencies:
163
196
  - 0
164
197
  version: "0"
165
198
  type: :development
166
- version_requirements: *id009
199
+ version_requirements: *id011
167
200
  - !ruby/object:Gem::Dependency
168
201
  name: rcov
169
202
  prerelease: false
170
- requirement: &id010 !ruby/object:Gem::Requirement
203
+ requirement: &id012 !ruby/object:Gem::Requirement
171
204
  none: false
172
205
  requirements:
173
206
  - - ">="
@@ -177,11 +210,11 @@ dependencies:
177
210
  - 0
178
211
  version: "0"
179
212
  type: :development
180
- version_requirements: *id010
213
+ version_requirements: *id012
181
214
  - !ruby/object:Gem::Dependency
182
215
  name: redgreen
183
216
  prerelease: false
184
- requirement: &id011 !ruby/object:Gem::Requirement
217
+ requirement: &id013 !ruby/object:Gem::Requirement
185
218
  none: false
186
219
  requirements:
187
220
  - - ">="
@@ -191,11 +224,11 @@ dependencies:
191
224
  - 0
192
225
  version: "0"
193
226
  type: :development
194
- version_requirements: *id011
227
+ version_requirements: *id013
195
228
  - !ruby/object:Gem::Dependency
196
229
  name: wirble
197
230
  prerelease: false
198
- requirement: &id012 !ruby/object:Gem::Requirement
231
+ requirement: &id014 !ruby/object:Gem::Requirement
199
232
  none: false
200
233
  requirements:
201
234
  - - ">="
@@ -205,11 +238,11 @@ dependencies:
205
238
  - 0
206
239
  version: "0"
207
240
  type: :development
208
- version_requirements: *id012
241
+ version_requirements: *id014
209
242
  - !ruby/object:Gem::Dependency
210
243
  name: cucumber
211
244
  prerelease: false
212
- requirement: &id013 !ruby/object:Gem::Requirement
245
+ requirement: &id015 !ruby/object:Gem::Requirement
213
246
  none: false
214
247
  requirements:
215
248
  - - ">="
@@ -221,11 +254,11 @@ dependencies:
221
254
  - 2
222
255
  version: 0.7.2
223
256
  type: :development
224
- version_requirements: *id013
257
+ version_requirements: *id015
225
258
  - !ruby/object:Gem::Dependency
226
259
  name: daemon_controller
227
260
  prerelease: false
228
- requirement: &id014 !ruby/object:Gem::Requirement
261
+ requirement: &id016 !ruby/object:Gem::Requirement
229
262
  none: false
230
263
  requirements:
231
264
  - - ">="
@@ -235,7 +268,7 @@ dependencies:
235
268
  - 0
236
269
  version: "0"
237
270
  type: :development
238
- version_requirements: *id014
271
+ version_requirements: *id016
239
272
  description: A highly available, reliable messaging infrastructure
240
273
  email: developers@xing.com
241
274
  executables:
@@ -255,6 +288,7 @@ files:
255
288
  - examples/handling_exceptions.rb
256
289
  - examples/multiple_exchanges.rb
257
290
  - examples/multiple_queues.rb
291
+ - examples/nonexistent_server.rb
258
292
  - examples/pause_and_resume.rb
259
293
  - examples/redundant.rb
260
294
  - examples/rpc.rb
@@ -289,6 +323,7 @@ files:
289
323
  - features/support/test_daemons/redis_configuration_client.rb
290
324
  - features/support/test_daemons/redis_configuration_server.rb
291
325
  - script/console
326
+ - script/console~
292
327
  - script/start_rabbit
293
328
  - beetle.gemspec
294
329
  - Rakefile
@@ -297,6 +332,7 @@ files:
297
332
  - REDIS_AUTO_FAILOVER.rdoc
298
333
  - RELEASE_NOTES.rdoc
299
334
  - MIT-LICENSE
335
+ - test/beetle/amqp_gem_behavior_test.rb
300
336
  - test/beetle/base_test.rb
301
337
  - test/beetle/client_test.rb
302
338
  - test/beetle/configuration_test.rb
@@ -311,8 +347,10 @@ files:
311
347
  - test/beetle/redis_master_file_test.rb
312
348
  - test/beetle/subscriber_test.rb
313
349
  - test/beetle_test.rb
350
+ - test/colorized_test_output.rb
314
351
  - test/test_helper.rb
315
352
  - bin/beetle
353
+ has_rdoc: true
316
354
  homepage: http://xing.github.com/beetle/
317
355
  licenses: []
318
356
 
@@ -344,11 +382,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
344
382
  requirements: []
345
383
 
346
384
  rubyforge_project:
347
- rubygems_version: 1.8.11
385
+ rubygems_version: 1.6.2
348
386
  signing_key:
349
387
  specification_version: 3
350
388
  summary: High Availability AMQP Messaging with Redundant Queues
351
389
  test_files:
390
+ - test/beetle/amqp_gem_behavior_test.rb
352
391
  - test/beetle/base_test.rb
353
392
  - test/beetle/client_test.rb
354
393
  - test/beetle/configuration_test.rb
@@ -363,4 +402,5 @@ test_files:
363
402
  - test/beetle/redis_master_file_test.rb
364
403
  - test/beetle/subscriber_test.rb
365
404
  - test/beetle_test.rb
405
+ - test/colorized_test_output.rb
366
406
  - test/test_helper.rb