pivotal-moqueue 0.1.0.200907241602
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/README.rdoc +57 -0
- data/Rakefile +25 -0
- data/VERSION.yml +4 -0
- data/lib/moqueue.rb +15 -0
- data/lib/moqueue/fibers18.rb +57 -0
- data/lib/moqueue/matchers.rb +74 -0
- data/lib/moqueue/mock_broker.rb +44 -0
- data/lib/moqueue/mock_exchange.rb +114 -0
- data/lib/moqueue/mock_headers.rb +31 -0
- data/lib/moqueue/mock_queue.rb +126 -0
- data/lib/moqueue/object_methods.rb +31 -0
- data/lib/moqueue/overloads.rb +46 -0
- data/moqueue.gemspec +29 -0
- data/spec/examples/ack_spec.rb +60 -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 +21 -0
- data/spec/unit/matchers_spec.rb +92 -0
- data/spec/unit/mock_broker_spec.rb +34 -0
- data/spec/unit/mock_exchange_spec.rb +102 -0
- data/spec/unit/mock_headers_spec.rb +22 -0
- data/spec/unit/mock_queue_spec.rb +141 -0
- data/spec/unit/moqueue_spec.rb +9 -0
- data/spec/unit/object_methods_spec.rb +49 -0
- data/spec/unit/overloads_spec.rb +48 -0
- metadata +87 -0
@@ -0,0 +1,31 @@
|
|
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
|
+
def mock_queue(name=nil)
|
12
|
+
MockQueue.new(name || "anonymous-#{rand(2**32).to_s(16)}")
|
13
|
+
end
|
14
|
+
|
15
|
+
def mock_exchange(opts={})
|
16
|
+
MockExchange.new(opts)
|
17
|
+
end
|
18
|
+
|
19
|
+
def overload_amqp
|
20
|
+
require MOQUEUE_ROOT + "moqueue/overloads"
|
21
|
+
end
|
22
|
+
|
23
|
+
def reset_broker
|
24
|
+
MockBroker.instance.reset!
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
Object.send(:include, Moqueue::ObjectMethods)
|
@@ -0,0 +1,46 @@
|
|
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 fanout(name, opts={})
|
11
|
+
Moqueue::MockExchange.new(opts.merge(:fanout=>name))
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(*args)
|
17
|
+
end
|
18
|
+
|
19
|
+
def queue(name, opts = {})
|
20
|
+
Moqueue::MockQueue.new(name)
|
21
|
+
end
|
22
|
+
|
23
|
+
def topic(topic_name)
|
24
|
+
Moqueue::MockExchange.new(:topic=>topic_name)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
module AMQP
|
30
|
+
|
31
|
+
class << self
|
32
|
+
attr_reader :closing
|
33
|
+
alias :closing? :closing
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.start(opts={},&block)
|
37
|
+
EM.run(&block)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.stop
|
41
|
+
@closing = true
|
42
|
+
yield if block_given?
|
43
|
+
@closing = false
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
data/moqueue.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{moqueue}
|
5
|
+
s.version = "0.1.0.200907241602"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Daniel DeLeo"]
|
9
|
+
s.date = %q{2009-07-09}
|
10
|
+
s.description = %q{Mocktacular Companion to AMQP Library. Happy TATFTing!}
|
11
|
+
s.email = %q{dan@kallistec.com}
|
12
|
+
s.extra_rdoc_files = ["README.rdoc"]
|
13
|
+
s.files = ["lib", "moqueue.gemspec", "Rakefile", "README.rdoc", "spec", "VERSION.yml", "lib/moqueue", "lib/moqueue/fibers18.rb", "lib/moqueue/matchers.rb", "lib/moqueue/mock_broker.rb", "lib/moqueue/mock_exchange.rb", "lib/moqueue/mock_headers.rb", "lib/moqueue/mock_queue.rb", "lib/moqueue/object_methods.rb", "lib/moqueue/overloads.rb", "lib/moqueue.rb", "spec/examples", "spec/examples/ack_spec.rb", "spec/examples/basic_usage_spec.rb", "spec/examples/example_helper.rb", "spec/examples/logger_spec.rb", "spec/examples/ping_pong_spec.rb", "spec/examples/stocks_spec.rb", "spec/spec.opts", "spec/spec_helper.rb", "spec/unit", "spec/unit/matchers_spec.rb", "spec/unit/mock_broker_spec.rb", "spec/unit/mock_exchange_spec.rb", "spec/unit/mock_headers_spec.rb", "spec/unit/mock_queue_spec.rb", "spec/unit/moqueue_spec.rb", "spec/unit/object_methods_spec.rb", "spec/unit/overloads_spec.rb"]
|
14
|
+
s.homepage = %q{http://github.com/danielsdeleo/moqueue}
|
15
|
+
s.rdoc_options = ["--inline-source", "--charset=UTF-8"]
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
s.rubygems_version = %q{1.3.3}
|
18
|
+
s.summary = %q{Mocktacular Companion to AMQP Library. Happy TATFTing!}
|
19
|
+
|
20
|
+
if s.respond_to? :specification_version then
|
21
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
22
|
+
s.specification_version = 3
|
23
|
+
|
24
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
25
|
+
else
|
26
|
+
end
|
27
|
+
else
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,60 @@
|
|
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
|
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
|
+
h.ack
|
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
|
54
|
+
end
|
55
|
+
q = MQ.queue('awesome')
|
56
|
+
q.received_ack_for_message?('Totally rad 1').should be_true
|
57
|
+
q.received_ack_for_message?('Totally rad 2').should be_true
|
58
|
+
q.received_ack_for_message?('Totally rad 3').should be_true
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
describe "AMQP", "when mocked out by Moqueue" do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
reset_broker
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should have direct exchanges" do
|
10
|
+
queue = mock_queue("direct-exchanges")
|
11
|
+
queue.publish("you are correct, sir!")
|
12
|
+
queue.subscribe { |message| "do something with message" }
|
13
|
+
queue.received_message?("you are correct, sir!").should be_true
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should have direct exchanges with acks" do
|
17
|
+
queue = mock_queue("direct-with-acks")
|
18
|
+
queue.publish("yessir!")
|
19
|
+
queue.subscribe(:ack => true) { |headers, message| headers.ack }
|
20
|
+
queue.received_ack_for_message?("yessir!").should be_true
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should have topic exchanges" do
|
24
|
+
topic = mock_exchange(:topic => "TATFT")
|
25
|
+
queue = mock_queue("rspec-fiend")
|
26
|
+
queue.bind(topic, :key => "bdd.*").subscribe { |msg| "do something" }
|
27
|
+
topic.publish("TATFT FTW", :key=> "bdd.4life")
|
28
|
+
queue.received_message?("TATFT FTW").should be_true
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should have topic exchanges with acks" do
|
32
|
+
topic = mock_exchange(:topic => "animals")
|
33
|
+
queue = mock_queue("cat-lover")
|
34
|
+
queue.bind(topic, :key => "cats.#").subscribe(:ack => true) do |header, msg|
|
35
|
+
header.ack
|
36
|
+
"do something with message"
|
37
|
+
end
|
38
|
+
topic.publish("OMG kittehs!", :key => "cats.lolz.kittehs")
|
39
|
+
topic.received_ack_for_message?("OMG kittehs!").should be_true
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should have fanout exchanges with acks" do
|
43
|
+
film = mock_exchange(:fanout => "Godfather")
|
44
|
+
one_actor = mock_queue("Jack Woltz")
|
45
|
+
other_actor = mock_queue("Captain McCluskey")
|
46
|
+
one_actor.bind(film).subscribe(:ack =>true) { |h,msg| h.ack && "horse head" }
|
47
|
+
other_actor.bind(film).subscribe(:ack => true) { |h,msg| h.ack && "dirty cops" }
|
48
|
+
offer = "you can't refuse"
|
49
|
+
film.publish(offer)
|
50
|
+
one_actor.received_message?(offer).should be_true
|
51
|
+
other_actor.received_message?(offer).should be_true
|
52
|
+
film.should have(2).acked_messages
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
describe Moqueue, "with syntax sugar" do
|
58
|
+
|
59
|
+
before(:each) do
|
60
|
+
reset_broker
|
61
|
+
end
|
62
|
+
|
63
|
+
it "counts received messages" do
|
64
|
+
queue = mock_queue
|
65
|
+
queue.subscribe { |msg| msg.should_not be_nil }
|
66
|
+
5.times {queue.publish("no moar beers kthxbye")}
|
67
|
+
queue.should have(5).received_messages
|
68
|
+
end
|
69
|
+
|
70
|
+
it "counts acked messages" do
|
71
|
+
queue = mock_queue
|
72
|
+
queue.subscribe(:ack=>true) { |headers,msg| headers.ack }
|
73
|
+
5.times { queue.publish("time becomes a loop") }
|
74
|
+
queue.should have(5).acked_messages
|
75
|
+
end
|
76
|
+
|
77
|
+
it "makes the callback (#subscribe) block testable" do
|
78
|
+
emphasis = mock_queue
|
79
|
+
emphasis.subscribe { |msg| @emphasized = "**" + msg + "**" }
|
80
|
+
emphasis.run_callback("show emphasis").should == "**show emphasis**"
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
describe Moqueue, "when using custom rspec matchers" do
|
86
|
+
|
87
|
+
it "should accept syntax like queue.should have_received('a message')" do
|
88
|
+
queue = mock_queue("sugary")
|
89
|
+
queue.subscribe { |msg| "eat the message" }
|
90
|
+
queue.publish("a message")
|
91
|
+
queue.should have_received("a message")
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should accept syntax like queue_or_exchange.should have_ack_for('a message')" do
|
95
|
+
queue = mock_queue("more sugar")
|
96
|
+
queue.subscribe(:ack => true) { |headers, msg| headers.ack }
|
97
|
+
queue.publish("another message")
|
98
|
+
queue.should have_ack_for("another message")
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
describe Moqueue, "when running the logger example" do
|
4
|
+
|
5
|
+
class Logger
|
6
|
+
def initialize *args, &block
|
7
|
+
opts = args.pop if args.last.is_a? Hash
|
8
|
+
opts ||= {}
|
9
|
+
|
10
|
+
printer(block) if block
|
11
|
+
|
12
|
+
@prop = opts
|
13
|
+
@tags = ([:timestamp] + args).uniq
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :prop
|
17
|
+
alias :base :prop
|
18
|
+
|
19
|
+
def log severity, *args
|
20
|
+
opts = args.pop if args.last.is_a? Hash and args.size != 1
|
21
|
+
opts ||= {}
|
22
|
+
opts = @prop.clone.update(opts)
|
23
|
+
|
24
|
+
data = args.shift
|
25
|
+
|
26
|
+
data = {:type => :exception,
|
27
|
+
:name => data.class.to_s.intern,
|
28
|
+
:backtrace => data.backtrace,
|
29
|
+
:message => data.message} if data.is_a? Exception
|
30
|
+
|
31
|
+
(@tags + args).each do |tag|
|
32
|
+
tag = tag.to_sym
|
33
|
+
case tag
|
34
|
+
when :timestamp
|
35
|
+
opts.update :timestamp => Time.now
|
36
|
+
when :hostname
|
37
|
+
@hostname ||= { :hostname => `hostname`.strip }
|
38
|
+
opts.update @hostname
|
39
|
+
when :process
|
40
|
+
@process_id ||= { :process_id => Process.pid,
|
41
|
+
:process_name => $0,
|
42
|
+
:process_parent_id => Process.ppid,
|
43
|
+
:thread_id => Thread.current.object_id }
|
44
|
+
opts.update :process => @process_id
|
45
|
+
else
|
46
|
+
(opts[:tags] ||= []) << tag
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
opts.update(:severity => severity,
|
51
|
+
:msg => data)
|
52
|
+
|
53
|
+
print(opts)
|
54
|
+
unless Logger.disabled?
|
55
|
+
MQ.fanout('logging', :durable => true).publish Marshal.dump(opts)
|
56
|
+
end
|
57
|
+
|
58
|
+
opts
|
59
|
+
end
|
60
|
+
alias :method_missing :log
|
61
|
+
|
62
|
+
def print data = nil, &block
|
63
|
+
if block
|
64
|
+
@printer = block
|
65
|
+
elsif data.is_a? Proc
|
66
|
+
@printer = data
|
67
|
+
elsif data
|
68
|
+
(pr = @printer || self.class.printer) and pr.call(data)
|
69
|
+
else
|
70
|
+
@printer
|
71
|
+
end
|
72
|
+
end
|
73
|
+
alias :printer :print
|
74
|
+
|
75
|
+
def self.printer &block
|
76
|
+
@printer = block if block
|
77
|
+
@printer
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.disabled?
|
81
|
+
!!@disabled
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.enable
|
85
|
+
@disabled = false
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.disable
|
89
|
+
@disabled = true
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
before(:all) do
|
95
|
+
overload_amqp
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
def run_client
|
100
|
+
AMQP.start do
|
101
|
+
log = Logger.new
|
102
|
+
log.debug 'its working!'
|
103
|
+
|
104
|
+
log = Logger.new do |msg|
|
105
|
+
#require 'pp'
|
106
|
+
#pp msg
|
107
|
+
#puts
|
108
|
+
end
|
109
|
+
|
110
|
+
log.info '123'
|
111
|
+
log.debug [1,2,3]
|
112
|
+
log.debug :one => 1, :two => 2
|
113
|
+
log.error Exception.new('123')
|
114
|
+
|
115
|
+
log.info '123', :process_id => Process.pid
|
116
|
+
log.info '123', :process
|
117
|
+
log.debug 'login', :session => 'abc', :user => 123
|
118
|
+
|
119
|
+
log = Logger.new(:webserver, :timestamp, :hostname, &log.printer)
|
120
|
+
log.info 'Request for /', :GET, :session => 'abc'
|
121
|
+
|
122
|
+
#AMQP.stop{ EM.stop }
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def run_server
|
127
|
+
AMQP.start(:host => 'localhost') do
|
128
|
+
|
129
|
+
@server_queue = MQ.queue('logger')
|
130
|
+
@server_queue.bind(MQ.fanout('logging', :durable => true)).subscribe do |msg|
|
131
|
+
msg = Marshal.load(msg)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should get the expected results" do
|
137
|
+
EM.run do
|
138
|
+
threads = []
|
139
|
+
threads << Thread.new do
|
140
|
+
run_server
|
141
|
+
end
|
142
|
+
threads << Thread.new do
|
143
|
+
run_client
|
144
|
+
end
|
145
|
+
|
146
|
+
EM.add_timer(0.1) do
|
147
|
+
@server_queue.should have(9).received_messages
|
148
|
+
webserver_log = Marshal.load(@server_queue.received_messages.last)
|
149
|
+
webserver_log[:tags].should == [:webserver, :GET]
|
150
|
+
webserver_log[:msg].should == "Request for /"
|
151
|
+
|
152
|
+
EM.stop
|
153
|
+
threads.each { |t| t.join }
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|