amqp 0.7.0.pre → 0.7.0
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/.gitignore +4 -0
- data/.rspec +2 -0
- data/CHANGELOG +8 -2
- data/CONTRIBUTORS +22 -0
- data/Gemfile +3 -3
- data/README.md +20 -11
- data/Rakefile +30 -6
- data/amqp.gemspec +1 -1
- data/bin/cleanify.rb +50 -0
- data/examples/amqp/simple.rb +6 -4
- data/examples/mq/ack.rb +8 -6
- data/examples/mq/automatic_binding_for_default_direct_exchange.rb +65 -0
- data/examples/mq/callbacks.rb +9 -1
- data/examples/mq/clock.rb +17 -17
- data/examples/mq/hashtable.rb +19 -10
- data/examples/mq/internal.rb +13 -11
- data/examples/mq/logger.rb +38 -36
- data/examples/mq/multiclock.rb +16 -7
- data/examples/mq/pingpong.rb +16 -7
- data/examples/mq/pop.rb +8 -6
- data/examples/mq/primes-simple.rb +2 -0
- data/examples/mq/primes.rb +7 -5
- data/examples/mq/stocks.rb +14 -5
- data/lib/amqp.rb +12 -8
- data/lib/amqp/buffer.rb +35 -158
- data/lib/amqp/client.rb +34 -22
- data/lib/amqp/frame.rb +8 -64
- data/lib/amqp/protocol.rb +21 -70
- data/lib/amqp/server.rb +11 -9
- data/lib/amqp/spec.rb +8 -6
- data/lib/amqp/version.rb +2 -0
- data/lib/ext/blankslate.rb +3 -1
- data/lib/ext/em.rb +2 -0
- data/lib/ext/emfork.rb +13 -11
- data/lib/mq.rb +253 -156
- data/lib/mq/collection.rb +6 -88
- data/lib/mq/exchange.rb +70 -13
- data/lib/mq/header.rb +12 -6
- data/lib/mq/logger.rb +9 -7
- data/lib/mq/queue.rb +42 -30
- data/lib/mq/rpc.rb +6 -4
- data/protocol/codegen.rb +20 -18
- data/research/api.rb +10 -46
- data/research/primes-forked.rb +9 -7
- data/research/primes-processes.rb +74 -72
- data/research/primes-threaded.rb +9 -7
- data/spec/integration/automatic_binding_for_default_direct_exchange_spec.rb +61 -0
- data/spec/mq_helper.rb +70 -0
- data/spec/spec_helper.rb +84 -29
- data/spec/unit/amqp/buffer_spec.rb +178 -0
- data/spec/unit/amqp/client_spec.rb +472 -0
- data/spec/unit/amqp/frame_spec.rb +60 -0
- data/spec/unit/amqp/misc_spec.rb +123 -0
- data/spec/unit/amqp/protocol_spec.rb +53 -0
- data/spec/unit/mq/channel_close_spec.rb +15 -0
- data/spec/unit/mq/collection_spec.rb +129 -0
- data/spec/unit/mq/exchange_declaration_spec.rb +524 -0
- data/spec/unit/mq/misc_spec.rb +228 -0
- data/spec/unit/mq/mq_basic_spec.rb +39 -0
- data/spec/unit/mq/queue_declaration_spec.rb +97 -0
- data/spec/unit/mq/queue_spec.rb +71 -0
- metadata +33 -21
- data/Gemfile.lock +0 -16
- data/old/README +0 -30
- data/old/Rakefile +0 -12
- data/old/amqp-0.8.json +0 -606
- data/old/amqp_spec.rb +0 -796
- data/old/amqpc.rb +0 -695
- data/old/codegen.rb +0 -148
- data/spec/channel_close_spec.rb +0 -13
- data/spec/sync_async_spec.rb +0 -52
data/research/primes-threaded.rb
CHANGED
@@ -1,15 +1,17 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
$:.unshift File.dirname(__FILE__) + '/../lib'
|
2
4
|
require 'mq'
|
3
5
|
|
4
6
|
MAX = 500
|
5
7
|
|
6
|
-
def log
|
8
|
+
def log(*args)
|
7
9
|
p args
|
8
10
|
end
|
9
11
|
|
10
12
|
# MQ.logging = true
|
11
13
|
|
12
|
-
EM.run{
|
14
|
+
EM.run {
|
13
15
|
|
14
16
|
# worker
|
15
17
|
|
@@ -21,21 +23,21 @@ EM.run{
|
|
21
23
|
end
|
22
24
|
end
|
23
25
|
|
24
|
-
MQ.queue('prime checker').subscribe{ |info, num|
|
25
|
-
EM.defer(proc{
|
26
|
+
MQ.queue('prime checker').subscribe { |info, num|
|
27
|
+
EM.defer(proc {
|
26
28
|
|
27
29
|
log "prime checker #{Process.pid}-#{Thread.current.object_id}", :prime?, num
|
28
30
|
if Integer(num).prime?
|
29
31
|
MQ.queue(info.reply_to).publish(num, :reply_to => "#{Process.pid}-#{Thread.current.object_id}")
|
30
32
|
EM.stop_event_loop if num == '499'
|
31
33
|
end
|
32
|
-
|
34
|
+
|
33
35
|
})
|
34
36
|
}
|
35
37
|
|
36
38
|
# controller
|
37
39
|
|
38
|
-
MQ.queue('prime collector').subscribe{ |info, prime|
|
40
|
+
MQ.queue('prime collector').subscribe { |info, prime|
|
39
41
|
log 'prime collector', :received, prime, :from, info.reply_to
|
40
42
|
(@primes ||= []) << Integer(prime)
|
41
43
|
}
|
@@ -46,4 +48,4 @@ EM.run{
|
|
46
48
|
end
|
47
49
|
end
|
48
50
|
|
49
|
-
}
|
51
|
+
}
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe "Queue that was bound to default direct exchange thanks to Automatic Mode (section 2.1.2.4 in AMQP 0.9.1 spec" do
|
6
|
+
|
7
|
+
#
|
8
|
+
# Environment
|
9
|
+
#
|
10
|
+
|
11
|
+
include AMQP::Spec
|
12
|
+
|
13
|
+
default_timeout 10
|
14
|
+
|
15
|
+
amqp_before do
|
16
|
+
@channel = MQ.new
|
17
|
+
|
18
|
+
@queue1 = @channel.queue("queue1")
|
19
|
+
@queue2 = @channel.queue("queue2")
|
20
|
+
|
21
|
+
# Rely on default direct exchange binding, see section 2.1.2.4 Automatic Mode in AMQP 0.9.1 spec.
|
22
|
+
@exchange = MQ::Exchange.default
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
|
27
|
+
#
|
28
|
+
# Examples
|
29
|
+
#
|
30
|
+
|
31
|
+
it "receives messages with routing key equals it's name" do
|
32
|
+
number_of_received_messages = 0
|
33
|
+
expected_number_of_messages = 3
|
34
|
+
dispatched_data = "to be received by queue1"
|
35
|
+
|
36
|
+
@queue1.subscribe do |payload|
|
37
|
+
number_of_received_messages += 1
|
38
|
+
payload.should == dispatched_data
|
39
|
+
|
40
|
+
if number_of_received_messages == expected_number_of_messages
|
41
|
+
$stdout.puts "Got all the messages I expected, wrapping up..."
|
42
|
+
done
|
43
|
+
else
|
44
|
+
n = expected_number_of_messages - number_of_received_messages
|
45
|
+
$stdout.puts "Still waiting for #{n} more message(s)"
|
46
|
+
end
|
47
|
+
end # subscribe
|
48
|
+
|
49
|
+
4.times do
|
50
|
+
@exchange.publish("some white noise", :routing_key => "killa key")
|
51
|
+
end
|
52
|
+
|
53
|
+
expected_number_of_messages.times do
|
54
|
+
@exchange.publish(dispatched_data, :routing_key => @queue1.name)
|
55
|
+
end
|
56
|
+
|
57
|
+
4.times do
|
58
|
+
@exchange.publish("some white noise", :routing_key => "killa key")
|
59
|
+
end
|
60
|
+
end # it
|
61
|
+
end # describe
|
data/spec/mq_helper.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# This helper supports writing specs for MQ (channel)
|
2
|
+
|
3
|
+
require 'mq'
|
4
|
+
|
5
|
+
# Mocking AMQP::Client::EM_CONNECTION_CLASS in order to
|
6
|
+
# specify MQ instance behavior without the need to start EM loop.
|
7
|
+
class MockConnection
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
EM.stub(:reactor_running?).and_return(true)
|
11
|
+
end
|
12
|
+
|
13
|
+
def callback &block
|
14
|
+
callbacks << block
|
15
|
+
block.call(self) if block
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_channel mq
|
19
|
+
channels[key = (channels.keys.max || 0) + 1] = mq
|
20
|
+
key
|
21
|
+
end
|
22
|
+
|
23
|
+
def send data, opts = {}
|
24
|
+
messages << {:data => data, :opts => opts}
|
25
|
+
end
|
26
|
+
|
27
|
+
def connected?
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
def channels
|
32
|
+
@channels||={}
|
33
|
+
end
|
34
|
+
|
35
|
+
def close
|
36
|
+
end
|
37
|
+
|
38
|
+
# Not part of AMQP::Client::EM_CONNECTION_CLASS interface, for mock introspection only
|
39
|
+
def callbacks
|
40
|
+
@callbacks||=[]
|
41
|
+
end
|
42
|
+
|
43
|
+
def messages
|
44
|
+
@messages||=[]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Sets expectations about @header and @body passed to @consumer.
|
49
|
+
def should_pass_updated_header_and_data_to consumer, opts={}
|
50
|
+
# - Why update @header.properties if @header becomes nil anyways?'
|
51
|
+
# - Because @consumer receives @header and may inspect its properties
|
52
|
+
|
53
|
+
subject_mock(:@method).should_receive(:arguments).
|
54
|
+
with(no_args).and_return(:myprop => 'mine')
|
55
|
+
consumer.should_receive(:receive) do |header, body|
|
56
|
+
header.klass.should == (opts[:klass] || AMQP::Protocol::Test)
|
57
|
+
header.size.should == (opts[:size] || 4)
|
58
|
+
header.weight.should == (opts[:weight] || 2)
|
59
|
+
header.properties.should == (opts[:properties] ||
|
60
|
+
{:delivery_mode => 1, :myprop => 'mine'})
|
61
|
+
body.should == (opts[:body] || 'data')
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Sets expectations that all listed instance variables for subject are nil
|
66
|
+
def should_be_nil *ivars
|
67
|
+
ivars.each do |ivar|
|
68
|
+
subject.instance_variable_get(ivar).should be_nil
|
69
|
+
end
|
70
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -2,40 +2,95 @@
|
|
2
2
|
|
3
3
|
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
|
4
4
|
|
5
|
+
require 'bundler'
|
6
|
+
Bundler.setup
|
7
|
+
Bundler.require :default, :test
|
8
|
+
|
5
9
|
require "mq"
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
10
|
+
|
11
|
+
amqp_config = File.dirname(__FILE__) + '/amqp.yml'
|
12
|
+
|
13
|
+
if File.exists? amqp_config
|
14
|
+
class Hash
|
15
|
+
def symbolize_keys
|
16
|
+
self.inject({}) do |result, (key, value)|
|
17
|
+
new_key = key.is_a?(String) ? key.to_sym : key
|
18
|
+
new_value = value.is_a?(Hash) ? value.symbolize_keys : value
|
19
|
+
result[new_key] = new_value
|
20
|
+
result
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
AMQP_OPTS = YAML::load_file(amqp_config).symbolize_keys[:test]
|
21
25
|
else
|
22
|
-
|
26
|
+
AMQP_OPTS = {:host => 'localhost', :port => 5672}
|
23
27
|
end
|
24
28
|
|
25
|
-
# Bacon doesn't seem to have some global before hook
|
26
|
-
Bacon::Context.class_eval do
|
27
|
-
alias_method :__initialize__, :initialize
|
28
29
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
30
|
+
if RUBY_VERSION == "1.8.7"
|
31
|
+
module ArrayExtensions
|
32
|
+
def sample
|
33
|
+
self.choice
|
34
|
+
end # sample
|
35
|
+
end
|
33
36
|
|
34
|
-
|
35
|
-
|
36
|
-
# it isn't nil) and use value = value || connect().
|
37
|
-
self.before do
|
38
|
-
@connected ||= AMQP.connect(amqp_url)
|
39
|
-
end
|
37
|
+
class Array
|
38
|
+
include ArrayExtensions
|
40
39
|
end
|
41
40
|
end
|
41
|
+
|
42
|
+
|
43
|
+
|
44
|
+
# Shorthand for mocking subject's instance variable
|
45
|
+
def subject_mock(name, as_null = false)
|
46
|
+
mock = mock(name)
|
47
|
+
mock.as_null_object if as_null
|
48
|
+
subject.instance_variable_set(name.to_sym, mock)
|
49
|
+
mock
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns Header that should be correctly parsed
|
53
|
+
def basic_header(opts = {})
|
54
|
+
AMQP::Frame::Header.new(
|
55
|
+
AMQP::Protocol::Header.new(
|
56
|
+
AMQP::Protocol::Basic, :priority => 1), opts[:channel] || 0)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns AMQP Frame::Header frame that contains Protocol::Header header
|
60
|
+
# with (pre-)defined accessors set.
|
61
|
+
def test_header opts = {}
|
62
|
+
AMQP::Frame::Header.new(
|
63
|
+
AMQP::Protocol::Header.new(
|
64
|
+
opts[:klass] || AMQP::Protocol::Test,
|
65
|
+
opts[:size] || 4,
|
66
|
+
opts[:weight] || 2,
|
67
|
+
opts[:properties] || {:delivery_mode => 1}))
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns AMQP Frame::Method frame that contains Protocol::Basic::Deliver
|
71
|
+
# with (pre-)defined accessors set.
|
72
|
+
def test_method_deliver opts = {}
|
73
|
+
AMQP::Frame::Method.new(
|
74
|
+
AMQP::Protocol::Basic::Deliver.new(
|
75
|
+
:consumer_tag => opts[:consumer_tag] || 'test_consumer'))
|
76
|
+
end
|
77
|
+
|
78
|
+
require "stringio"
|
79
|
+
|
80
|
+
def capture_stdout(&block)
|
81
|
+
$stdout = StringIO.new
|
82
|
+
block.call
|
83
|
+
$stdout.rewind
|
84
|
+
result = $stdout.read
|
85
|
+
$stdout = STDOUT
|
86
|
+
return result
|
87
|
+
end
|
88
|
+
|
89
|
+
def capture_stderr(&block)
|
90
|
+
$stderr = StringIO.new
|
91
|
+
block.call
|
92
|
+
$stderr.rewind
|
93
|
+
result = $stderr.read
|
94
|
+
$stderr = STDOUT
|
95
|
+
return result
|
96
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
require "amqp/buffer"
|
5
|
+
|
6
|
+
|
7
|
+
if [].map.respond_to? :with_index
|
8
|
+
class Array #:nodoc:
|
9
|
+
def enum_with_index
|
10
|
+
each.with_index
|
11
|
+
end
|
12
|
+
end
|
13
|
+
else
|
14
|
+
require 'enumerator'
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
describe AMQP::Buffer do
|
19
|
+
|
20
|
+
#
|
21
|
+
# Examples
|
22
|
+
#
|
23
|
+
|
24
|
+
it "has contents" do
|
25
|
+
subject.contents.should == ""
|
26
|
+
end
|
27
|
+
|
28
|
+
it "can be initialized with data" do
|
29
|
+
@buffer = described_class.new("abc")
|
30
|
+
@buffer.contents.should == "abc"
|
31
|
+
end
|
32
|
+
|
33
|
+
it "can append strings" do
|
34
|
+
subject << "abc"
|
35
|
+
subject << "def"
|
36
|
+
|
37
|
+
subject.contents.should == "abcdef"
|
38
|
+
end
|
39
|
+
|
40
|
+
it "can append other buffers" do
|
41
|
+
subject << described_class.new("abc")
|
42
|
+
|
43
|
+
subject.data.should == "abc"
|
44
|
+
end
|
45
|
+
|
46
|
+
it "has current position" do
|
47
|
+
subject.pos.should == 0
|
48
|
+
|
49
|
+
subject << described_class.new("abc")
|
50
|
+
|
51
|
+
subject.pos.should == 0
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
it "has length" do
|
56
|
+
subject.length.should == 0
|
57
|
+
|
58
|
+
subject << "abc"
|
59
|
+
|
60
|
+
subject.length.should == 3
|
61
|
+
# check for a crazy case when both RSpec and Bacon are loaded;
|
62
|
+
# then == matcher ALWAYS PASSES. Le sigh.
|
63
|
+
subject.length.should_not == 300
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
it "provides emptiness predicate" do
|
68
|
+
subject.should be_empty
|
69
|
+
subject << "xyz"
|
70
|
+
|
71
|
+
subject.should_not be_empty
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
it "supports writing of data" do
|
76
|
+
subject._write("abc")
|
77
|
+
subject.pos.should == 3
|
78
|
+
subject.rewind
|
79
|
+
subject.pos.should == 0
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
it "supports reading of data" do
|
84
|
+
subject._write("abc")
|
85
|
+
subject.rewind
|
86
|
+
|
87
|
+
subject._read(2).should == "ab"
|
88
|
+
subject.pos.should == 0
|
89
|
+
subject._read(1).should == "c"
|
90
|
+
subject.pos.should == 0
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
it "raises on overflow" do
|
95
|
+
expect {
|
96
|
+
subject._read(1)
|
97
|
+
}.should raise_error(described_class::Overflow)
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
it "refuses reading of unsupported types" do
|
102
|
+
expect {
|
103
|
+
subject.read(:junk)
|
104
|
+
}.to raise_error(described_class::InvalidType)
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
it "refuses writing of unsupported types" do
|
109
|
+
expect {
|
110
|
+
subject.write(:junk, 1)
|
111
|
+
}.to raise_error(described_class::InvalidType)
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
{ :octet => 0b10101010,
|
116
|
+
:short => 100,
|
117
|
+
:long => 100_000_000,
|
118
|
+
:longlong => 666_555_444_333_222_111,
|
119
|
+
:shortstr => 'hello',
|
120
|
+
:longstr => 'bye'*500,
|
121
|
+
:timestamp => time = Time.at(Time.now.to_i),
|
122
|
+
:table => { :this => 'is', :a => 'hash', :with => {:nested => 123, :and => time, :also => 123.456} },
|
123
|
+
:bit => true
|
124
|
+
}.each do |type, value|
|
125
|
+
it "can read and write a #{type}" do
|
126
|
+
subject.write(type, value)
|
127
|
+
subject.rewind
|
128
|
+
|
129
|
+
subject.read(type).should == value
|
130
|
+
subject.should be_empty
|
131
|
+
end # it
|
132
|
+
end # each
|
133
|
+
|
134
|
+
|
135
|
+
it 'can read and write multiple bits' do
|
136
|
+
bits = [true, false, false, true, true, false, false, true, true, false]
|
137
|
+
subject.write(:bit, bits)
|
138
|
+
subject.write(:octet, 100)
|
139
|
+
|
140
|
+
subject.rewind
|
141
|
+
|
142
|
+
bits.map do
|
143
|
+
subject.read(:bit)
|
144
|
+
end.should == bits
|
145
|
+
subject.read(:octet).should == 100
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'can read and write property tables' do
|
149
|
+
properties = ([
|
150
|
+
[:octet, 1],
|
151
|
+
[:shortstr, 'abc'],
|
152
|
+
[:bit, true],
|
153
|
+
[:bit, false],
|
154
|
+
[:shortstr, nil],
|
155
|
+
[:timestamp, nil],
|
156
|
+
[:table, { :a => 'hash' }],
|
157
|
+
]*5).sort_by {rand}
|
158
|
+
|
159
|
+
subject.write(:properties, properties)
|
160
|
+
subject.rewind
|
161
|
+
subject.read(:properties, *properties.map { |type, _| type }).should == properties.map { |_, value| value }
|
162
|
+
subject.should be_empty
|
163
|
+
end
|
164
|
+
|
165
|
+
it 'does transactional reads with #extract' do
|
166
|
+
subject.write :octet, 8
|
167
|
+
orig = subject.to_s
|
168
|
+
|
169
|
+
subject.rewind
|
170
|
+
subject.extract do |b|
|
171
|
+
b.read :octet
|
172
|
+
b.read :short
|
173
|
+
end
|
174
|
+
|
175
|
+
subject.pos.should == 0
|
176
|
+
subject.data.should == orig
|
177
|
+
end
|
178
|
+
end # describe
|