amqp 0.7.0.pre → 0.7.0

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