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.
@@ -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
@@ -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