moqueue 0.1.4
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/CONTRIBUTORS.rdoc +3 -0
- data/README.rdoc +55 -0
- data/Rakefile +63 -0
- data/VERSION.yml +4 -0
- data/lib/moqueue.rb +15 -0
- data/lib/moqueue/fibers18.rb +57 -0
- data/lib/moqueue/matchers.rb +113 -0
- data/lib/moqueue/mock_broker.rb +53 -0
- data/lib/moqueue/mock_exchange.rb +147 -0
- data/lib/moqueue/mock_headers.rb +31 -0
- data/lib/moqueue/mock_queue.rb +138 -0
- data/lib/moqueue/object_methods.rb +41 -0
- data/lib/moqueue/overloads.rb +60 -0
- data/moqueue.gemspec +86 -0
- data/spec/examples/ack_spec.rb +93 -0
- data/spec/examples/basic_usage_spec.rb +101 -0
- data/spec/examples/example_helper.rb +16 -0
- data/spec/examples/logger_spec.rb +159 -0
- data/spec/examples/ping_pong_spec.rb +50 -0
- data/spec/examples/stocks_spec.rb +64 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/unit/matchers_spec.rb +102 -0
- data/spec/unit/mock_broker_spec.rb +40 -0
- data/spec/unit/mock_exchange_spec.rb +168 -0
- data/spec/unit/mock_headers_spec.rb +22 -0
- data/spec/unit/mock_queue_spec.rb +151 -0
- data/spec/unit/moqueue_spec.rb +5 -0
- data/spec/unit/object_methods_spec.rb +49 -0
- data/spec/unit/overloads_spec.rb +56 -0
- metadata +107 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
module Moqueue
|
2
|
+
|
3
|
+
class MockHeaders
|
4
|
+
attr_accessor :size, :weight
|
5
|
+
|
6
|
+
def initialize(properties={})
|
7
|
+
@properties = properties
|
8
|
+
end
|
9
|
+
|
10
|
+
def ack
|
11
|
+
@received_ack = true
|
12
|
+
end
|
13
|
+
|
14
|
+
def received_ack?
|
15
|
+
@received_ack || false
|
16
|
+
end
|
17
|
+
|
18
|
+
def properties
|
19
|
+
@properties
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_frame
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def method_missing method, *args, &blk
|
27
|
+
@properties.has_key?(method) ? @properties[method] : super
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
module Moqueue
|
2
|
+
|
3
|
+
class DoubleSubscribeError < StandardError
|
4
|
+
end
|
5
|
+
|
6
|
+
class MockQueue
|
7
|
+
attr_reader :name
|
8
|
+
|
9
|
+
class << self
|
10
|
+
|
11
|
+
def new(name)
|
12
|
+
if existing_queue = MockBroker.instance.find_queue(name)
|
13
|
+
return existing_queue
|
14
|
+
end
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(name)
|
21
|
+
@name = name
|
22
|
+
MockBroker.instance.register_queue(self)
|
23
|
+
end
|
24
|
+
|
25
|
+
def subscribe(opts={}, &block)
|
26
|
+
if @subscribe_block
|
27
|
+
raise DoubleSubscribeError, "you can't subscribe to the same queue twice"
|
28
|
+
end
|
29
|
+
@subscribe_block = block
|
30
|
+
@ack_msgs = opts[:ack] || false
|
31
|
+
process_unhandled_messages
|
32
|
+
end
|
33
|
+
|
34
|
+
def receive(message, header_opts={})
|
35
|
+
if callback = message_handler_callback
|
36
|
+
headers = MockHeaders.new(header_opts)
|
37
|
+
callback.call(*(callback.arity == 1 ? [message] : [headers, message]))
|
38
|
+
received_messages_and_headers << {:message => message, :headers => headers}
|
39
|
+
else
|
40
|
+
receive_message_later(message, header_opts)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def received_message?(message_content)
|
45
|
+
received_messages.include?(message_content)
|
46
|
+
end
|
47
|
+
|
48
|
+
def received_routing_key?(key)
|
49
|
+
received_messages_and_headers.find { |r| r[:headers] && r[:headers].properties[:routing_key] == key }
|
50
|
+
end
|
51
|
+
|
52
|
+
def unsubscribe
|
53
|
+
true
|
54
|
+
end
|
55
|
+
|
56
|
+
def prefetch(size)
|
57
|
+
# noop
|
58
|
+
end
|
59
|
+
|
60
|
+
def received_ack_for_message?(message_content)
|
61
|
+
acked_messages.include?(message_content)
|
62
|
+
end
|
63
|
+
|
64
|
+
def publish(message, opts = {})
|
65
|
+
if message_handler_callback
|
66
|
+
receive(message)
|
67
|
+
else
|
68
|
+
deferred_publishing_fibers << Fiber.new do
|
69
|
+
receive(message)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def bind(exchange, key=nil)
|
75
|
+
exchange.attach_queue(self, key)
|
76
|
+
self
|
77
|
+
end
|
78
|
+
|
79
|
+
def received_messages_and_headers
|
80
|
+
@received_messages_and_headers ||= []
|
81
|
+
end
|
82
|
+
|
83
|
+
def received_messages
|
84
|
+
received_messages_and_headers.map{|r| r[:message] }
|
85
|
+
end
|
86
|
+
|
87
|
+
def received_headers
|
88
|
+
received_messages_and_headers.map{ |r| r[:headers] }
|
89
|
+
end
|
90
|
+
|
91
|
+
def acked_messages
|
92
|
+
received_messages_and_headers.map do |r|
|
93
|
+
r[:message] if @ack_msgs && r[:headers].received_ack?
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def run_callback(*args)
|
98
|
+
callback = message_handler_callback
|
99
|
+
callback.call(*(callback.arity == 1 ? [args.first] : args))
|
100
|
+
end
|
101
|
+
|
102
|
+
def callback_defined?
|
103
|
+
!!message_handler_callback
|
104
|
+
end
|
105
|
+
|
106
|
+
# configures a do-nothing subscribe block to force
|
107
|
+
# received messages to be processed and stored in
|
108
|
+
# #received_messages
|
109
|
+
def null_subscribe
|
110
|
+
subscribe {|msg| nil}
|
111
|
+
self
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def receive_message_later(message, header_opts)
|
117
|
+
deferred_publishing_fibers << Fiber.new do
|
118
|
+
self.receive(message, header_opts)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def deferred_publishing_fibers
|
123
|
+
@deferred_publishing_fibers ||= []
|
124
|
+
end
|
125
|
+
|
126
|
+
def message_handler_callback
|
127
|
+
@subscribe_block || @pop_block || false
|
128
|
+
end
|
129
|
+
|
130
|
+
def process_unhandled_messages
|
131
|
+
while fiber = deferred_publishing_fibers.shift
|
132
|
+
fiber.resume
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Moqueue
|
2
|
+
|
3
|
+
module ObjectMethods
|
4
|
+
def mock_queue_and_exchange(name=nil)
|
5
|
+
queue = mock_queue(name)
|
6
|
+
exchange = mock_exchange
|
7
|
+
exchange.attached_queues << queue
|
8
|
+
[queue, exchange]
|
9
|
+
end
|
10
|
+
|
11
|
+
# Takes a string name as a parameter. Each queue name may only be used
|
12
|
+
# once. Multiple calls to #mock_queue with the same +name+ will return
|
13
|
+
# the same object.
|
14
|
+
def mock_queue(name=nil)
|
15
|
+
MockQueue.new(name || "anonymous-#{rand(2**32).to_s(16)}")
|
16
|
+
end
|
17
|
+
|
18
|
+
# Takes a hash to specify the exchange type and its name.
|
19
|
+
#
|
20
|
+
# topic = mock_exchange(:topic => 'topic exchange')
|
21
|
+
def mock_exchange(opts={})
|
22
|
+
MockExchange.new(opts)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Overloads the class-level method calls typically used by AMQP code
|
26
|
+
# such as MQ.direct, MQ.queue, MQ.topic, etc.
|
27
|
+
def overload_amqp
|
28
|
+
require MOQUEUE_ROOT + "moqueue/overloads"
|
29
|
+
end
|
30
|
+
|
31
|
+
# Deletes all exchanges and queues from the mock broker. As a consequence of
|
32
|
+
# removing queues, all bindings and subscriptions are also deleted.
|
33
|
+
def reset_broker
|
34
|
+
MockBroker.instance.reset!
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
Object.send(:include, Moqueue::ObjectMethods)
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require "eventmachine"
|
2
|
+
|
3
|
+
class MQ
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def queue(name)
|
7
|
+
Moqueue::MockQueue.new(name)
|
8
|
+
end
|
9
|
+
|
10
|
+
def direct(name, opts={})
|
11
|
+
Moqueue::MockExchange.new(opts.merge(:direct=>name))
|
12
|
+
end
|
13
|
+
|
14
|
+
def fanout(name, opts={})
|
15
|
+
Moqueue::MockExchange.new(opts.merge(:fanout=>name))
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(*args)
|
21
|
+
end
|
22
|
+
|
23
|
+
def direct(name, opts = {})
|
24
|
+
Moqueue::MockExchange.new(opts.merge(:direct => name))
|
25
|
+
end
|
26
|
+
|
27
|
+
def fanout(name, opts = {})
|
28
|
+
Moqueue::MockExchange.new(opts.merge(:fanout => name))
|
29
|
+
end
|
30
|
+
|
31
|
+
def queue(name, opts = {})
|
32
|
+
Moqueue::MockQueue.new(name)
|
33
|
+
end
|
34
|
+
|
35
|
+
def topic(topic_name)
|
36
|
+
Moqueue::MockExchange.new(:topic=>topic_name)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
module AMQP
|
42
|
+
|
43
|
+
class << self
|
44
|
+
attr_reader :closing
|
45
|
+
alias :closing? :closing
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.start(opts={},&block)
|
49
|
+
EM.run(&block)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.stop
|
53
|
+
@closing = true
|
54
|
+
yield if block_given?
|
55
|
+
@closing = false
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.connect(*args)
|
59
|
+
end
|
60
|
+
end
|
data/moqueue.gemspec
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{moqueue}
|
8
|
+
s.version = "0.1.4"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Daniel DeLeo"]
|
12
|
+
s.date = %q{2009-09-21}
|
13
|
+
s.description = %q{Mocktacular Companion to AMQP Library. Happy TATFTing!}
|
14
|
+
s.email = %q{dan@kallistec.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README.rdoc"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
"CONTRIBUTORS.rdoc",
|
20
|
+
"README.rdoc",
|
21
|
+
"Rakefile",
|
22
|
+
"VERSION.yml",
|
23
|
+
"lib/moqueue.rb",
|
24
|
+
"lib/moqueue/fibers18.rb",
|
25
|
+
"lib/moqueue/matchers.rb",
|
26
|
+
"lib/moqueue/mock_broker.rb",
|
27
|
+
"lib/moqueue/mock_exchange.rb",
|
28
|
+
"lib/moqueue/mock_headers.rb",
|
29
|
+
"lib/moqueue/mock_queue.rb",
|
30
|
+
"lib/moqueue/object_methods.rb",
|
31
|
+
"lib/moqueue/overloads.rb",
|
32
|
+
"moqueue.gemspec",
|
33
|
+
"spec/examples/ack_spec.rb",
|
34
|
+
"spec/examples/basic_usage_spec.rb",
|
35
|
+
"spec/examples/example_helper.rb",
|
36
|
+
"spec/examples/logger_spec.rb",
|
37
|
+
"spec/examples/ping_pong_spec.rb",
|
38
|
+
"spec/examples/stocks_spec.rb",
|
39
|
+
"spec/spec.opts",
|
40
|
+
"spec/spec_helper.rb",
|
41
|
+
"spec/unit/matchers_spec.rb",
|
42
|
+
"spec/unit/mock_broker_spec.rb",
|
43
|
+
"spec/unit/mock_exchange_spec.rb",
|
44
|
+
"spec/unit/mock_headers_spec.rb",
|
45
|
+
"spec/unit/mock_queue_spec.rb",
|
46
|
+
"spec/unit/moqueue_spec.rb",
|
47
|
+
"spec/unit/object_methods_spec.rb",
|
48
|
+
"spec/unit/overloads_spec.rb"
|
49
|
+
]
|
50
|
+
s.homepage = %q{http://github.com/danielsdeleo/moqueue}
|
51
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
52
|
+
s.require_paths = ["lib"]
|
53
|
+
s.rubyforge_project = %q{moqueue}
|
54
|
+
s.rubygems_version = %q{1.3.5}
|
55
|
+
s.summary = %q{Mocktacular Companion to AMQP Library. Happy TATFTing!}
|
56
|
+
s.test_files = [
|
57
|
+
"spec/examples/ack_spec.rb",
|
58
|
+
"spec/examples/basic_usage_spec.rb",
|
59
|
+
"spec/examples/example_helper.rb",
|
60
|
+
"spec/examples/logger_spec.rb",
|
61
|
+
"spec/examples/ping_pong_spec.rb",
|
62
|
+
"spec/examples/stocks_spec.rb",
|
63
|
+
"spec/spec_helper.rb",
|
64
|
+
"spec/unit/matchers_spec.rb",
|
65
|
+
"spec/unit/mock_broker_spec.rb",
|
66
|
+
"spec/unit/mock_exchange_spec.rb",
|
67
|
+
"spec/unit/mock_headers_spec.rb",
|
68
|
+
"spec/unit/mock_queue_spec.rb",
|
69
|
+
"spec/unit/moqueue_spec.rb",
|
70
|
+
"spec/unit/object_methods_spec.rb",
|
71
|
+
"spec/unit/overloads_spec.rb"
|
72
|
+
]
|
73
|
+
|
74
|
+
if s.respond_to? :specification_version then
|
75
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
76
|
+
s.specification_version = 3
|
77
|
+
|
78
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
79
|
+
s.add_runtime_dependency(%q<amqp>, [">= 0"])
|
80
|
+
else
|
81
|
+
s.add_dependency(%q<amqp>, [">= 0"])
|
82
|
+
end
|
83
|
+
else
|
84
|
+
s.add_dependency(%q<amqp>, [">= 0"])
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
require File.dirname(__FILE__) + '/example_helper'
|
3
|
+
|
4
|
+
# NOTE: moqueue currently does not mimic AMQP's behavior of:
|
5
|
+
# 1) requiring graceful shutdown for acks to be delivered
|
6
|
+
# 2) returning messages to the queue if not acked
|
7
|
+
# 3) not processing messages when AMQP isn't "running"
|
8
|
+
#
|
9
|
+
# This causes the result of this test to differ from the actual result when run
|
10
|
+
# with a real broker. The true behavior should be that the 3rd message
|
11
|
+
# published should be unacknowledged and returned to the queue. In this test,
|
12
|
+
# all messages get acknowleged
|
13
|
+
describe Moqueue, "when running the ack example" do
|
14
|
+
include ExampleHelper
|
15
|
+
|
16
|
+
def run_ack_example(&perform_ack)
|
17
|
+
AMQP.start(:host => 'localhost') do
|
18
|
+
MQ.queue('awesome').publish('Totally rad 1')
|
19
|
+
MQ.queue('awesome').publish('Totally rad 2')
|
20
|
+
MQ.queue('awesome').publish('Totally rad 3')
|
21
|
+
|
22
|
+
i = 0
|
23
|
+
|
24
|
+
# Stopping after the second item was acked will keep the 3rd item in the queue
|
25
|
+
MQ.queue('awesome').subscribe(:ack => true) do |h,m|
|
26
|
+
if (i+=1) == 3
|
27
|
+
#puts 'Shutting down...'
|
28
|
+
AMQP.stop{ EM.stop }
|
29
|
+
end
|
30
|
+
|
31
|
+
if AMQP.closing?
|
32
|
+
#puts "#{m} (ignored, redelivered later)"
|
33
|
+
else
|
34
|
+
#puts "received message: " + m
|
35
|
+
perform_ack.call(h)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
before(:all) do
|
43
|
+
overload_amqp
|
44
|
+
end
|
45
|
+
|
46
|
+
before(:each) do
|
47
|
+
reset_broker
|
48
|
+
reset!
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should get the correct result without errors" do
|
52
|
+
Timeout::timeout(2) do
|
53
|
+
run_ack_example {|h| h.ack }
|
54
|
+
end
|
55
|
+
q = MQ.queue('awesome')
|
56
|
+
q.should have(3).acked_messages
|
57
|
+
q.received_ack_for_message?('Totally rad 1').should be_true
|
58
|
+
q.received_ack_for_message?('Totally rad 2').should be_true
|
59
|
+
q.received_ack_for_message?('Totally rad 3').should be_true
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should be able to ack in an EM.next_tick" do
|
63
|
+
Timeout::timeout(2) do
|
64
|
+
run_ack_example do |h|
|
65
|
+
EM.next_tick { h.ack }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
q = MQ.queue('awesome')
|
69
|
+
q.should have(3).acked_messages
|
70
|
+
q.received_ack_for_message?('Totally rad 1').should be_true
|
71
|
+
q.received_ack_for_message?('Totally rad 2').should be_true
|
72
|
+
q.received_ack_for_message?('Totally rad 3').should be_true
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should be able to ack in an EM.defer callback" do
|
76
|
+
Timeout::timeout(2) do
|
77
|
+
run_ack_example do |h|
|
78
|
+
EM.defer(proc {
|
79
|
+
1337
|
80
|
+
},
|
81
|
+
proc { |result|
|
82
|
+
result.should == 1337
|
83
|
+
h.ack
|
84
|
+
})
|
85
|
+
end
|
86
|
+
end
|
87
|
+
q = MQ.queue('awesome')
|
88
|
+
q.should have(3).acked_messages
|
89
|
+
q.received_ack_for_message?('Totally rad 1').should be_true
|
90
|
+
q.received_ack_for_message?('Totally rad 2').should be_true
|
91
|
+
q.received_ack_for_message?('Totally rad 3').should be_true
|
92
|
+
end
|
93
|
+
end
|