moqueue 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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