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.
Files changed (71) hide show
  1. data/.gitignore +4 -0
  2. data/.rspec +2 -0
  3. data/CHANGELOG +8 -2
  4. data/CONTRIBUTORS +22 -0
  5. data/Gemfile +3 -3
  6. data/README.md +20 -11
  7. data/Rakefile +30 -6
  8. data/amqp.gemspec +1 -1
  9. data/bin/cleanify.rb +50 -0
  10. data/examples/amqp/simple.rb +6 -4
  11. data/examples/mq/ack.rb +8 -6
  12. data/examples/mq/automatic_binding_for_default_direct_exchange.rb +65 -0
  13. data/examples/mq/callbacks.rb +9 -1
  14. data/examples/mq/clock.rb +17 -17
  15. data/examples/mq/hashtable.rb +19 -10
  16. data/examples/mq/internal.rb +13 -11
  17. data/examples/mq/logger.rb +38 -36
  18. data/examples/mq/multiclock.rb +16 -7
  19. data/examples/mq/pingpong.rb +16 -7
  20. data/examples/mq/pop.rb +8 -6
  21. data/examples/mq/primes-simple.rb +2 -0
  22. data/examples/mq/primes.rb +7 -5
  23. data/examples/mq/stocks.rb +14 -5
  24. data/lib/amqp.rb +12 -8
  25. data/lib/amqp/buffer.rb +35 -158
  26. data/lib/amqp/client.rb +34 -22
  27. data/lib/amqp/frame.rb +8 -64
  28. data/lib/amqp/protocol.rb +21 -70
  29. data/lib/amqp/server.rb +11 -9
  30. data/lib/amqp/spec.rb +8 -6
  31. data/lib/amqp/version.rb +2 -0
  32. data/lib/ext/blankslate.rb +3 -1
  33. data/lib/ext/em.rb +2 -0
  34. data/lib/ext/emfork.rb +13 -11
  35. data/lib/mq.rb +253 -156
  36. data/lib/mq/collection.rb +6 -88
  37. data/lib/mq/exchange.rb +70 -13
  38. data/lib/mq/header.rb +12 -6
  39. data/lib/mq/logger.rb +9 -7
  40. data/lib/mq/queue.rb +42 -30
  41. data/lib/mq/rpc.rb +6 -4
  42. data/protocol/codegen.rb +20 -18
  43. data/research/api.rb +10 -46
  44. data/research/primes-forked.rb +9 -7
  45. data/research/primes-processes.rb +74 -72
  46. data/research/primes-threaded.rb +9 -7
  47. data/spec/integration/automatic_binding_for_default_direct_exchange_spec.rb +61 -0
  48. data/spec/mq_helper.rb +70 -0
  49. data/spec/spec_helper.rb +84 -29
  50. data/spec/unit/amqp/buffer_spec.rb +178 -0
  51. data/spec/unit/amqp/client_spec.rb +472 -0
  52. data/spec/unit/amqp/frame_spec.rb +60 -0
  53. data/spec/unit/amqp/misc_spec.rb +123 -0
  54. data/spec/unit/amqp/protocol_spec.rb +53 -0
  55. data/spec/unit/mq/channel_close_spec.rb +15 -0
  56. data/spec/unit/mq/collection_spec.rb +129 -0
  57. data/spec/unit/mq/exchange_declaration_spec.rb +524 -0
  58. data/spec/unit/mq/misc_spec.rb +228 -0
  59. data/spec/unit/mq/mq_basic_spec.rb +39 -0
  60. data/spec/unit/mq/queue_declaration_spec.rb +97 -0
  61. data/spec/unit/mq/queue_spec.rb +71 -0
  62. metadata +33 -21
  63. data/Gemfile.lock +0 -16
  64. data/old/README +0 -30
  65. data/old/Rakefile +0 -12
  66. data/old/amqp-0.8.json +0 -606
  67. data/old/amqp_spec.rb +0 -796
  68. data/old/amqpc.rb +0 -695
  69. data/old/codegen.rb +0 -148
  70. data/spec/channel_close_spec.rb +0 -13
  71. data/spec/sync_async_spec.rb +0 -52
@@ -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 *args
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
@@ -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
@@ -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
- require "bacon"
7
- require "em-spec/bacon"
8
-
9
- # Notes about Bacon & EM-spec
10
- # - Subsequent describe blocks don't work.
11
- # - Bacon doesn't support any kind of pending specs.
12
- # - Bacon doesn't support before(:all) hook (see bellow).
13
- EM.spec_backend = EventMachine::Spec::Bacon
14
-
15
- # Usage with tracer:
16
- # 1) Start tracer on a PORT_NUMBER
17
- # 2) ruby spec/sync_async_spec.rb amqp://localhost:PORT_NUMBER
18
- if ARGV.first && ARGV.first.match(/^amqps?:/)
19
- amqp_url = ARGV.first
20
- puts "Using AMQP URL #{amqp_url}"
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
- amqp_url = "amqp://localhost"
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
- # Let's use Module#define_method, so we can
30
- # access local variables defined in parent scopes.
31
- define_method(:initialize) do |*args, &block|
32
- __initialize__(*args, &block)
30
+ if RUBY_VERSION == "1.8.7"
31
+ module ArrayExtensions
32
+ def sample
33
+ self.choice
34
+ end # sample
35
+ end
33
36
 
34
- # There's no support for before(:all), so we just
35
- # save whatever AMQP.connect returns (we suppose
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