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
@@ -0,0 +1,228 @@
1
+ require 'spec_helper'
2
+ require 'mq_helper'
3
+
4
+ describe 'MQ', 'class object' do
5
+ # after{AMQP.cleanup_state} # Tips off Thread.current[:mq] call
6
+ subject { MQ }
7
+
8
+ its(:logging) { should be_false }
9
+
10
+
11
+ describe '.logging=' do
12
+ it 'is independent from AMQP.logging' do
13
+ #
14
+ AMQP.logging = true
15
+ MQ.logging.should be_false
16
+ MQ.logging = false
17
+ AMQP.logging.should == true
18
+
19
+ #
20
+ AMQP.logging = false
21
+ MQ.logging = true
22
+ AMQP.logging.should be_false
23
+ MQ.logging = false
24
+ end
25
+ end # .logging=
26
+
27
+
28
+ describe '.default' do
29
+ it 'creates MQ object and stashes it in a Thread-local Hash' do
30
+ MQ.should_receive(:new).with(no_args).and_return('MQ mock')
31
+ Thread.current.should_receive(:[]).with(:mq)
32
+ Thread.current.should_receive(:[]=).with(:mq, 'MQ mock')
33
+ MQ.default.should == 'MQ mock'
34
+ end
35
+ end # .default
36
+
37
+
38
+
39
+ describe '.id' do
40
+ it 'returns Thread-local uuid for mq' do
41
+ uuid_pattern = /.*-[0-9]*-[0-9]*/
42
+ Thread.current.should_receive(:[]).with(:mq_id)
43
+ Thread.current.should_receive(:[]=).with(:mq_id, uuid_pattern)
44
+ MQ.id.should =~ uuid_pattern
45
+ end
46
+ end # .id
47
+
48
+
49
+
50
+ describe '.error' do
51
+ after(:all) { MQ.instance_variable_set(:@error_callback, nil) } # clear error callback
52
+
53
+ it 'is noop unless @error_callback was previously set' do
54
+ MQ.instance_variable_get(:@error_callback).should be_nil
55
+ MQ.error("Msg").should be_nil
56
+ end
57
+
58
+ context 'when @error_callback is set' do
59
+ before { MQ.error { |message| message.should == "Msg"; @callback_fired = true } }
60
+
61
+ it 'is setting @error_callback to given block' do
62
+ MQ.instance_variable_get(:@error_callback).should_not be_nil
63
+ @callback_fired.should be_false
64
+ end
65
+
66
+ it 'does not fire @error_callback immediately' do
67
+ @callback_fired.should be_false
68
+ end
69
+
70
+ it 'fires @error_callback on subsequent .error calls' do
71
+ MQ.error("Msg")
72
+ @callback_fired.should be_true
73
+ end
74
+ end
75
+ end # .error
76
+
77
+
78
+
79
+ describe '.method_missing' do
80
+ it 'routes all unrecognized methods to MQ.default' do
81
+ MQ.should_receive(:new).with(no_args).and_return(mock('channel'))
82
+ MQ.default.should_receive(:foo).with("Msg")
83
+ MQ.foo("Msg")
84
+ end
85
+ end # .method_missing
86
+ end # describe 'MQ as a class'
87
+
88
+
89
+
90
+
91
+ describe 'Channel object' do
92
+ context 'when initialized with a mock connection' do
93
+ before { @client = MockConnection.new }
94
+ after { AMQP.cleanup_state }
95
+ subject { MQ.new(@client).tap { |mq| mq.succeed } } # Indicates that channel is connected
96
+
97
+ it 'should have a channel' do
98
+ subject.channel.should be_kind_of Fixnum
99
+ subject.channel.should == 1 # Essentially, this channel (mq) number
100
+ end
101
+
102
+ it 'has publicly accessible collections' do
103
+ subject.exchanges.should be_empty
104
+ subject.queues.should be_empty
105
+ subject.consumers.should be_empty
106
+ subject.rpcs.should be_empty
107
+ end
108
+
109
+ describe '#send' do
110
+ it 'sends given data through its connection' do
111
+ args = [ 'data1', 'data2', 'data3']
112
+ subject.send *args
113
+ @client.messages[-3..-1].map { |message| message[:data] }.should == args
114
+ end #send
115
+
116
+ it 'sets data`s ticket property if @ticket is set for MQ object' do
117
+ ticket = subject_mock(:@ticket)
118
+ data = mock('data')
119
+ data.should_receive(:ticket=).with(ticket)
120
+ subject.send data
121
+ end
122
+ end
123
+
124
+
125
+
126
+ describe '#reset' do
127
+ it 'resets and reinitializes the channel, clears and resets its queues/exchanges' do
128
+ subject.queue('test').should_receive(:reset)
129
+ subject.fanout('fanout').should_receive(:reset)
130
+ subject.should_receive(:initialize).with(@client)
131
+
132
+ subject.reset
133
+ subject.queues.should be_empty
134
+ subject.exchanges.should be_empty
135
+ subject.consumers.should be_empty
136
+ end
137
+ end #reset
138
+
139
+
140
+
141
+ describe '#prefetch', 'setting :prefetch_count for broker' do
142
+ it 'sends Protocol::Basic::Qos' do
143
+ subject.prefetch 13
144
+ @client.messages.last[:data].should be_an AMQP::Protocol::Basic::Qos
145
+ @client.messages.last[:data].instance_variable_get(:@prefetch_count).should == 13
146
+ end
147
+
148
+ it 'returns MQ object itself, allowing for method chains' do
149
+ subject.prefetch(1).should == subject
150
+ end
151
+ end #prefetch
152
+
153
+
154
+
155
+ describe '#recover', "asks broker to redeliver unack'ed messages on this channel" do
156
+ it "sends Basic::Recover through @connection" do
157
+ subject.recover
158
+ @client.messages.last[:data].should be_an AMQP::Protocol::Basic::Recover
159
+ end
160
+
161
+ it 'defaults to :requeue=nil, redeliver messages to the original recipient' do
162
+ subject.recover
163
+ @client.messages.last[:data].instance_variable_get(:@requeue).should == nil
164
+ end #prefetch
165
+
166
+ it 'prompts broker to requeue messages (to other subs) if :requeue set to true' do
167
+ subject.recover true
168
+ @client.messages.last[:data].instance_variable_get(:@requeue).should == true
169
+ end
170
+
171
+ it 'returns MQ object itself, allowing for method chains' do
172
+ subject.recover.should == subject
173
+ end
174
+ end #recover
175
+
176
+
177
+
178
+ describe '#close', 'closes channel' do
179
+ it 'can be simplified, getting rid of @closing ivar?'
180
+ # TODO: Just set a callback sending Protocol::Channel::Close...'
181
+
182
+ it 'sends Channel::Close through @connection' do
183
+ subject.close
184
+ @client.messages.last[:data].should be_an AMQP::Protocol::Channel::Close
185
+ end
186
+
187
+ it 'does not actually delete the channel or close connection' do
188
+ @client.channels.should_not_receive(:delete)
189
+ @client.should_not_receive(:close)
190
+ subject.close
191
+ end
192
+
193
+ it 'actual channel closing happens ONLY when Channel::CloseOk is received' do
194
+ @client.channels.should_receive(:delete) do |key|
195
+ key.should == 1
196
+ @client.channels.clear
197
+ end
198
+ @client.should_receive(:close)
199
+ subject.process_frame AMQP::Frame::Method.new(AMQP::Protocol::Channel::CloseOk.new)
200
+ end
201
+ end #close
202
+
203
+
204
+
205
+
206
+ describe '#get_queue', 'supports #pop (Basic::Get) operation on queues' do
207
+ it 'yields a FIFO queue to a given block' do
208
+ subject.get_queue do |fifo|
209
+ @block_called = true
210
+ fifo.should == []
211
+ end
212
+ @block_called.should be_true
213
+ end
214
+
215
+ it 'FIFO queue contains consumers that called Queue#pop' do
216
+ queue1 = subject.queue('test1')
217
+ queue2 = subject.queue('test2')
218
+ queue1.pop
219
+ queue2.pop
220
+ subject.get_queue do |fifo|
221
+ fifo.should have(2).consumers
222
+ fifo.first.should == queue1
223
+ fifo.last.should == queue2
224
+ end
225
+ end
226
+ end #get_queue
227
+ end # context 'when initialized with a mock connection'
228
+ end # describe MQ object, also vaguely known as "channel"
@@ -0,0 +1,39 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe MQ do
6
+
7
+ #
8
+ # Environment
9
+ #
10
+
11
+ include AMQP::Spec
12
+
13
+ default_timeout 5
14
+
15
+ amqp_before do
16
+ @channel = MQ.new
17
+ end
18
+
19
+
20
+ #
21
+ # Examples
22
+ #
23
+
24
+
25
+ describe ".channel" do
26
+ it 'gives each thread a separate channel' do
27
+ pending 'This is not implemented in current lib'
28
+ class MQ
29
+ @@cur_channel = 0
30
+ end
31
+
32
+ described_class.channel.should == 1
33
+
34
+ Thread.new { described_class.channel }.value.should == 2
35
+ Thread.new { described_class.channel }.value.should == 3
36
+ done
37
+ end
38
+ end
39
+ end # describe MQ
@@ -0,0 +1,97 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe MQ do
6
+
7
+ #
8
+ # Environment
9
+ #
10
+
11
+ include AMQP::Spec
12
+
13
+ default_timeout 5
14
+
15
+ amqp_before do
16
+ @channel = MQ.new
17
+ end
18
+
19
+
20
+ #
21
+ # Examples
22
+ #
23
+
24
+ describe "#queue" do
25
+ context "when queue name is specified" do
26
+ let(:name) { "a queue declared at #{Time.now.to_i}" }
27
+
28
+ it "declares a new queue with that name" do
29
+ queue = @channel.queue(name)
30
+ queue.name.should == name
31
+
32
+ done
33
+ end
34
+
35
+ it "caches that queue" do
36
+ queue = @channel.queue(name)
37
+ @channel.queue(name).object_id.should == queue.object_id
38
+
39
+ done
40
+ end
41
+ end # context
42
+
43
+ context "when queue name is passed on as an empty string" do
44
+ it "uses server-assigned queue name" do
45
+ @channel.queue("") do |queue, *args|
46
+ queue.name.should_not be_empty
47
+ done
48
+ end
49
+ end
50
+ end # context
51
+
52
+
53
+
54
+
55
+ context "when passive option is used" do
56
+ context "and exchange with given name already exists" do
57
+ it "silently returns" do
58
+ name = "a_new_queue declared at #{Time.now.to_i}"
59
+
60
+ original_exchange = @channel.queue(name)
61
+ exchange = @channel.queue(name, :passive => true)
62
+
63
+ exchange.should == original_exchange
64
+
65
+ done
66
+ end # it
67
+ end
68
+
69
+ context "and exchange with given name DOES NOT exist" do
70
+ it "raises an exception" do
71
+ pending "Not yet supported"
72
+
73
+ expect {
74
+ exchange = @channel.queue("queue declared at #{Time.now.to_i}", :passive => true)
75
+ }.to raise_error
76
+
77
+ done
78
+ end # it
79
+ end # context
80
+ end # context
81
+
82
+
83
+
84
+
85
+ context "when queue is re-declared with parameters different from original declaration" do
86
+ it "raises an exception" do
87
+ @channel.queue("previously.declared.durable.queue", :durable => true)
88
+
89
+ expect {
90
+ @channel.queue("previously.declared.durable.queue", :durable => false)
91
+ }.to raise_error(MQ::IncompatibleOptionsError)
92
+
93
+ done
94
+ end # it
95
+ end # context
96
+ end # describe
97
+ end # describe MQ
@@ -0,0 +1,71 @@
1
+ require 'spec_helper'
2
+ require 'mq_helper'
3
+
4
+ describe MQ::Queue, :broker => true do
5
+
6
+ #
7
+ # Environment
8
+ #
9
+
10
+ include AMQP::Spec
11
+
12
+ default_options AMQP_OPTS
13
+ default_timeout 3
14
+
15
+ amqp_before do
16
+ @channel = MQ.new
17
+ @queue = MQ::Queue.new(@channel, 'test_queue', :option => 'useless')
18
+ end
19
+
20
+ amqp_after { @queue.purge; AMQP.cleanup_state }
21
+
22
+
23
+ #
24
+ # Examples
25
+ #
26
+
27
+ context 'declared with name of "test_queue"' do
28
+ amqp_after {@queue.unsubscribe; @queue.delete}
29
+ # TODO: Fix amqp_after: it is NOT run in case of raised exceptions, it seems
30
+
31
+ it 'supports asynchronous subscription to broker-predefined amq.direct exchange' do
32
+ data = "data sent via amq.direct exchange"
33
+
34
+ @queue.subscribe do |header, message|
35
+ header.should be_an MQ::Header
36
+ message.should == data
37
+ done
38
+ end
39
+ @queue.publish(data)
40
+ end # it
41
+
42
+ it 'supports synchronous message fetching via #pop (Basic.Get)' do
43
+ @queue.publish('send some data down the pipe')
44
+
45
+ @queue.pop do |header, message|
46
+ header.should be_an MQ::Header
47
+ message.should == 'send some data down the pipe'
48
+ done
49
+ end
50
+ end # it
51
+
52
+ xit 'asynchronously receives own status from the broker' do
53
+ pending "TODO: this example currently fails; do investigate why. MK."
54
+ total_number_of_messages = 123
55
+
56
+ total_number_of_messages.times do |n|
57
+ @queue.publish('message # #{n}')
58
+ end
59
+
60
+ @on_status_fired = false
61
+ @queue.status do |messages, consumers|
62
+ @on_status_fired = true
63
+ messages.should == total_number_of_messages
64
+ consumers.should == 0
65
+ end # it
66
+
67
+ @on_status_fired.should be_true
68
+ done(1)
69
+ end # it
70
+ end # context
71
+ end # MQ::Queue, 'with real connection
metadata CHANGED
@@ -1,13 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: amqp
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: true
4
+ prerelease: false
5
5
  segments:
6
6
  - 0
7
7
  - 7
8
8
  - 0
9
- - pre
10
- version: 0.7.0.pre
9
+ version: 0.7.0
11
10
  platform: ruby
12
11
  authors:
13
12
  - Aman Gupta
@@ -15,7 +14,7 @@ authors:
15
14
  autorequire:
16
15
  bindir: bin
17
16
  cert_chain:
18
- date: 2011-01-05 00:00:00 +00:00
17
+ date: 2011-01-18 00:00:00 +00:00
19
18
  default_executable:
20
19
  dependencies:
21
20
  - !ruby/object:Gem::Dependency
@@ -50,14 +49,16 @@ extra_rdoc_files:
50
49
  - doc/EXAMPLE_06_HASHTABLE
51
50
  files:
52
51
  - .gitignore
52
+ - .rspec
53
53
  - CHANGELOG
54
+ - CONTRIBUTORS
54
55
  - Gemfile
55
- - Gemfile.lock
56
56
  - README.md
57
57
  - Rakefile
58
58
  - amqp.gemspec
59
59
  - amqp.pre.gemspec
60
60
  - amqp.todo
61
+ - bin/cleanify.rb
61
62
  - bin/irb
62
63
  - doc/EXAMPLE_01_PINGPONG
63
64
  - doc/EXAMPLE_02_CLOCK
@@ -68,6 +69,7 @@ files:
68
69
  - doc/EXAMPLE_06_HASHTABLE
69
70
  - examples/amqp/simple.rb
70
71
  - examples/mq/ack.rb
72
+ - examples/mq/automatic_binding_for_default_direct_exchange.rb
71
73
  - examples/mq/callbacks.rb
72
74
  - examples/mq/clock.rb
73
75
  - examples/mq/hashtable.rb
@@ -97,12 +99,6 @@ files:
97
99
  - lib/mq/logger.rb
98
100
  - lib/mq/queue.rb
99
101
  - lib/mq/rpc.rb
100
- - old/README
101
- - old/Rakefile
102
- - old/amqp-0.8.json
103
- - old/amqp_spec.rb
104
- - old/amqpc.rb
105
- - old/codegen.rb
106
102
  - protocol/amqp-0.8.json
107
103
  - protocol/amqp-0.8.xml
108
104
  - protocol/codegen.rb
@@ -111,17 +107,35 @@ files:
111
107
  - research/primes-forked.rb
112
108
  - research/primes-processes.rb
113
109
  - research/primes-threaded.rb
114
- - spec/channel_close_spec.rb
110
+ - spec/integration/automatic_binding_for_default_direct_exchange_spec.rb
111
+ - spec/mq_helper.rb
115
112
  - spec/spec_helper.rb
116
- - spec/sync_async_spec.rb
113
+ - spec/unit/amqp/buffer_spec.rb
114
+ - spec/unit/amqp/client_spec.rb
115
+ - spec/unit/amqp/frame_spec.rb
116
+ - spec/unit/amqp/misc_spec.rb
117
+ - spec/unit/amqp/protocol_spec.rb
118
+ - spec/unit/mq/channel_close_spec.rb
119
+ - spec/unit/mq/collection_spec.rb
120
+ - spec/unit/mq/exchange_declaration_spec.rb
121
+ - spec/unit/mq/misc_spec.rb
122
+ - spec/unit/mq/mq_basic_spec.rb
123
+ - spec/unit/mq/queue_declaration_spec.rb
124
+ - spec/unit/mq/queue_spec.rb
117
125
  has_rdoc: true
118
- homepage: http://github.com/tmm1/amqp
126
+ homepage: http://github.com/ruby-amqp/amqp
119
127
  licenses: []
120
128
 
121
129
  post_install_message: "[\e[32mVersion 0.7\e[0m] [BUG] Sync API for queues and exchanges, support for server-generated queues & exchange names (via semi-lazy collection).\n\
122
130
  [\e[32mVersion 0.7\e[0m] [BUG] Sync API for MQ#close (Channel.Close) [issue #34].\n\
123
- [\e[32mVersion 0.7\e[0m] [FEATURE] From majek's fork, with some fixes. Example: AMQP.start(\"amqps://\")\n\
124
- [\e[32mVersion 0.7\e[0m] [DEVELOP] some em-spec-based specs, bin/irb, Gemfile.\n"
131
+ [\e[32mVersion 0.7\e[0m] [FEATURE] AMQP URL from majek's fork, with some fixes. Example: AMQP.start(\"amqps://\")\n\
132
+ [\e[32mVersion 0.7\e[0m] [DEVELOP] Added some em-spec-based specs, bin/irb, Gemfile.\n\
133
+ [\e[32mVersion 0.7\e[0m] [FEATURE] Added MQ::Exchange.default for the default exchange.\n\
134
+ [\e[32mVersion 0.7\e[0m] [FEATURE] Raise an exception if we're trying to use Basic.Reject with RabbitMQ.\n\
135
+ [\e[32mVersion 0.7\e[0m] [FEATURE] Fail if an entity is re-declared with different options.\n\
136
+ [\e[32mVersion 0.7\e[0m] [BUG] Don't reconnect if the credentials are wrong.\n\
137
+ [\e[32mVersion 0.7\e[0m] [BUG] Fixed an exception which occurred when Queue#bind was called synchronously with a callback.\n\
138
+ [\e[32mVersion 0.7\e[0m] [DEVELOPMENT] Added a lot of specs (Bacon replaced by rSpec 2).\n"
125
139
  rdoc_options:
126
140
  - --include=examples
127
141
  require_paths:
@@ -137,13 +151,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
137
151
  required_rubygems_version: !ruby/object:Gem::Requirement
138
152
  none: false
139
153
  requirements:
140
- - - ">"
154
+ - - ">="
141
155
  - !ruby/object:Gem::Version
142
156
  segments:
143
- - 1
144
- - 3
145
- - 1
146
- version: 1.3.1
157
+ - 0
158
+ version: "0"
147
159
  requirements: []
148
160
 
149
161
  rubyforge_project: amqp