amqp-spec 0.3.3 → 0.3.4
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY +4 -0
- data/README.rdoc +35 -23
- data/VERSION +1 -1
- data/lib/amqp-spec/evented_example.rb +10 -5
- data/lib/amqp-spec/rspec.rb +18 -4
- data/spec/em_hooks_spec.rb +58 -24
- metadata +3 -3
data/HISTORY
CHANGED
data/README.rdoc
CHANGED
@@ -10,20 +10,21 @@ Simple API for writing asynchronous EventMachine/AMQP specs. Supports RSpec and
|
|
10
10
|
|
11
11
|
EventMachine-based code, including synchronous {AMQP library}[http://github.com/tmm1/amqp]
|
12
12
|
is notoriously difficult to test. To the point that many people recommend using either
|
13
|
-
Mocks[http://github.com/danielsdeleo/moqueue]
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
Mocks[http://github.com/danielsdeleo/moqueue]
|
14
|
+
or {synchronous libraries}[http://github.com/celldee/bunny]
|
15
|
+
instead of EM-based libraries in unit tests. This is not always an option, however -
|
16
|
+
sometimes your code just has to run inside the event loop, and you want to test a real
|
17
|
+
thing, not just mocks.
|
17
18
|
|
18
19
|
EM-Spec[http://github.com/tmm1/em-spec] gem made it easier to write evented specs, but it
|
19
20
|
has several drawbacks. First, it is not easy to manage both EM.run and AMQP.start loops
|
20
21
|
at the same time. Second, AMQP is not properly stopped and deactivated upon exceptions and
|
21
22
|
timeouts, resulting in state leak between examples and multiple mystereous failures.
|
22
23
|
|
23
|
-
AMQP-Spec is based on EM-Spec code but makes it easier to test AMQP event loops
|
24
|
-
API is very similar to EM-Spec's, only a bit extended. The final goal is to
|
25
|
-
specs reasonably pleasant experience and dispel the notion that evented
|
26
|
-
impossible to unit-test.
|
24
|
+
AMQP-Spec is based on EM-Spec code but makes it easier to test AMQP event loops
|
25
|
+
specifically. API is very similar to EM-Spec's, only a bit extended. The final goal is to
|
26
|
+
make writing AMQP specs reasonably pleasant experience and dispel the notion that evented
|
27
|
+
AMQP-based libs are impossible to unit-test.
|
27
28
|
|
28
29
|
Mind you, you still have to properly manage your AMQP broker in order to prevent broker
|
29
30
|
state from leaking between examples. You can try to combine AMQP-Spec and
|
@@ -45,8 +46,15 @@ this is different from EM-Spec where default_timeout is effectively a global set
|
|
45
46
|
|
46
47
|
In order to setup/teardown EM state before/after your examples, you'll need to use
|
47
48
|
*em_before* and *em_after* hooks. These hooks are similar to standard RSpec's
|
48
|
-
*before
|
49
|
-
If you are using
|
49
|
+
*before*/*after* hooks but run inside the EM event loop before/after your example block.
|
50
|
+
If you are using #amqp method, *em_before* hook will run just BEFORE AMQP connection is
|
51
|
+
attempted, and *em_after* is run after AMQP is stopped.
|
52
|
+
|
53
|
+
Sometimes, may want to setup/teardown state inside AMQP connection (inside block given to
|
54
|
+
AMQP.start): for example, to make sure that the connection is established before your
|
55
|
+
example runs, or pre-declare some queues and exchanges common for all examples.
|
56
|
+
In this case, please use *amqp_before* and *amqp_after* hooks. These hooks run inside
|
57
|
+
the AMQP.start block just before/after your example block.
|
50
58
|
|
51
59
|
require "amqp-spec/rspec"
|
52
60
|
|
@@ -96,9 +104,12 @@ will run in its separate EM loop that you'll need to shut down either manually (
|
|
96
104
|
via timeout. Essentially, this means that any EM-related state that you'd like to set up or
|
97
105
|
tear down using these hooks will be lost as example itself will run in a different EM loop.
|
98
106
|
|
99
|
-
In short, you should avoid using *before
|
100
|
-
*em_before
|
101
|
-
|
107
|
+
In short, you should avoid using *before*/*after* if you include AMQP::Spec - instead, use
|
108
|
+
*em_before*/*em_after* or *amqp_before*/*amqp_after* hooks that run inside the EM event
|
109
|
+
loop.
|
110
|
+
|
111
|
+
One more note: you don't need to call #done inside evented hooks, otherwise it'll shut down
|
112
|
+
the EM reactor.
|
102
113
|
|
103
114
|
|
104
115
|
describe AMQP do
|
@@ -133,8 +144,9 @@ to call #done inside *em_before/em_after*, otherwise it'll shut down the reactor
|
|
133
144
|
end
|
134
145
|
|
135
146
|
Finally, you can include AMQP::EMSpec in your describe block. This will run all the group
|
136
|
-
examples inside em block instead of amqp. Non-evented *before
|
137
|
-
with #done, same as when including AMQP::Spec, and same caution about using them
|
147
|
+
examples inside em block instead of amqp. Non-evented *before*/*after* hooks should be
|
148
|
+
finished with #done, same as when including AMQP::Spec, and same caution about using them
|
149
|
+
applies.
|
138
150
|
|
139
151
|
describe AMQP do
|
140
152
|
include AMQP::EMSpec
|
@@ -189,14 +201,14 @@ Take the following spec as an example:
|
|
189
201
|
Seems like a straightforward spec: you subscribe to a message queue, you set expectations
|
190
202
|
inside your subscribe block, then you publish into this queue, then you call done. What may
|
191
203
|
be wrong with it? Well, if you happen to use this spec against live AMQP broker, everything
|
192
|
-
may be wrong. First, communication delays. There is no guarantee that by the time you
|
193
|
-
your message, the queue have been either created or subscribed to. There is also
|
194
|
-
that your subscriber received the message by the time you are unsubscribing
|
195
|
-
queue. Second, sequence of your blocks. Remember
|
196
|
-
assume your previous block is already executed when you start your
|
197
|
-
In this spec, when done is called, it stops everything before your
|
198
|
-
has a chance to fire. As a result, you'll get a PASSING spec even
|
199
|
-
was never executed!
|
204
|
+
may be wrong. First, communication delays. There is no guarantee that by the time you
|
205
|
+
publish your message, the queue have been either created or subscribed to. There is also
|
206
|
+
no guarantee that your subscriber received the message by the time you are unsubscribing
|
207
|
+
and deleting your queue. Second, sequence of your blocks. Remember they are delayed
|
208
|
+
callbacks! Don't just assume your previous block is already executed when you start your
|
209
|
+
new asynchronous action. In this spec, when done is called, it stops everything before your
|
210
|
+
subscribe callback even has a chance to fire. As a result, you'll get a PASSING spec even
|
211
|
+
though your expectation was never executed!
|
200
212
|
|
201
213
|
How to improve this spec? Allow some time for async actions to finish: either use EM timers
|
202
214
|
or pass :nowait=>false to your asynch calls to force them into synchronicity. Keep in mind
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.3.
|
1
|
+
0.3.4
|
@@ -57,7 +57,7 @@ module AMQP
|
|
57
57
|
def run_em_loop
|
58
58
|
begin
|
59
59
|
EM.run do
|
60
|
-
run_em_hooks :
|
60
|
+
run_em_hooks :em_before
|
61
61
|
|
62
62
|
@spec_exception = nil
|
63
63
|
timeout(@opts[:spec_timeout]) if @opts[:spec_timeout]
|
@@ -70,7 +70,7 @@ module AMQP
|
|
70
70
|
end
|
71
71
|
rescue Exception => @spec_exception
|
72
72
|
# p "Outside loop, caught #{@spec_exception}"
|
73
|
-
run_em_hooks :
|
73
|
+
run_em_hooks :em_after # Event loop broken, but we still need to run em_after hooks
|
74
74
|
ensure
|
75
75
|
finish_example
|
76
76
|
end
|
@@ -79,7 +79,7 @@ module AMQP
|
|
79
79
|
# Stops EM event loop. It is called from #done
|
80
80
|
#
|
81
81
|
def finish_em_loop
|
82
|
-
run_em_hooks :
|
82
|
+
run_em_hooks :em_after
|
83
83
|
EM.stop_event_loop if EM.reactor_running?
|
84
84
|
end
|
85
85
|
|
@@ -88,7 +88,6 @@ module AMQP
|
|
88
88
|
# Descendant classes may redefine to clean up type-specific state.
|
89
89
|
#
|
90
90
|
def finish_example
|
91
|
-
# p @spec_exception if @spec_exception
|
92
91
|
raise @spec_exception if @spec_exception
|
93
92
|
end
|
94
93
|
|
@@ -123,7 +122,10 @@ module AMQP
|
|
123
122
|
# Run @block inside the AMQP.start loop
|
124
123
|
def run
|
125
124
|
run_em_loop do
|
126
|
-
AMQP.start_connection @opts,
|
125
|
+
AMQP.start_connection @opts, do
|
126
|
+
run_em_hooks :amqp_before
|
127
|
+
@block.call
|
128
|
+
end
|
127
129
|
end
|
128
130
|
end
|
129
131
|
|
@@ -134,11 +136,14 @@ module AMQP
|
|
134
136
|
super(delay) do
|
135
137
|
yield if block_given?
|
136
138
|
EM.next_tick do
|
139
|
+
run_em_hooks :amqp_after
|
137
140
|
if AMQP.conn and not AMQP.closing
|
138
141
|
AMQP.stop_connection do
|
142
|
+
AMQP.cleanup_state
|
139
143
|
finish_em_loop
|
140
144
|
end
|
141
145
|
else
|
146
|
+
AMQP.cleanup_state
|
142
147
|
finish_em_loop
|
143
148
|
end
|
144
149
|
end
|
data/lib/amqp-spec/rspec.rb
CHANGED
@@ -69,21 +69,35 @@ module AMQP
|
|
69
69
|
# Add before hook that will run inside EM event loop
|
70
70
|
def em_before scope = :each, &block
|
71
71
|
raise ArgumentError, "em_before only supports :each scope" unless :each == scope
|
72
|
-
em_hooks[:
|
72
|
+
em_hooks[:em_before] << block
|
73
73
|
end
|
74
74
|
|
75
75
|
# Add after hook that will run inside EM event loop
|
76
76
|
def em_after scope = :each, &block
|
77
77
|
raise ArgumentError, "em_after only supports :each scope" unless :each == scope
|
78
|
-
em_hooks[:
|
78
|
+
em_hooks[:em_after].unshift block
|
79
|
+
end
|
80
|
+
|
81
|
+
# Add before hook that will run inside AMQP connection (AMQP.start loop)
|
82
|
+
def amqp_before scope = :each, &block
|
83
|
+
raise ArgumentError, "amqp_before only supports :each scope" unless :each == scope
|
84
|
+
em_hooks[:amqp_before] << block
|
85
|
+
end
|
86
|
+
|
87
|
+
# Add after hook that will run inside AMQP connection (AMQP.start loop)
|
88
|
+
def amqp_after scope = :each, &block
|
89
|
+
raise ArgumentError, "amqp_after only supports :each scope" unless :each == scope
|
90
|
+
em_hooks[:amqp_after].unshift block
|
79
91
|
end
|
80
92
|
|
81
93
|
# Collection of evented hooks for THIS example group
|
82
94
|
def em_hooks
|
83
95
|
metadata[:em_hooks] ||= {}
|
84
96
|
metadata[:em_hooks][self] ||=
|
85
|
-
{
|
86
|
-
|
97
|
+
{em_before: (superclass.em_hooks[:em_before].clone rescue []),
|
98
|
+
em_after: (superclass.em_hooks[:em_after].clone rescue []),
|
99
|
+
amqp_before: (superclass.em_hooks[:amqp_before].clone rescue []),
|
100
|
+
amqp_after: (superclass.em_hooks[:amqp_after].clone rescue [])}
|
87
101
|
end
|
88
102
|
end
|
89
103
|
|
data/spec/em_hooks_spec.rb
CHANGED
@@ -1,5 +1,19 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
+
def hook symbol=nil, reactor, connection
|
4
|
+
@hooks_called << symbol.to_sym if symbol
|
5
|
+
if :reactor_running == reactor
|
6
|
+
EM.reactor_running?.should be_true
|
7
|
+
else
|
8
|
+
EM.reactor_running?.should be_false
|
9
|
+
end
|
10
|
+
if :amqp_connected == connection
|
11
|
+
AMQP.conn.should be_connected
|
12
|
+
else
|
13
|
+
AMQP.conn and AMQP.conn.should_not be_connected
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
3
17
|
shared_examples_for 'hooked em specs' do
|
4
18
|
it 'should execute em_before' do
|
5
19
|
em do
|
@@ -34,6 +48,8 @@ shared_examples_for 'hooked amqp specs' do
|
|
34
48
|
amqp do
|
35
49
|
@hooks_called.should include :em_before
|
36
50
|
@hooks_called.should_not include :em_after
|
51
|
+
@hooks_called.should include :amqp_before
|
52
|
+
@hooks_called.should_not include :amqp_after
|
37
53
|
done
|
38
54
|
end
|
39
55
|
end
|
@@ -65,13 +81,14 @@ describe AMQP::SpecHelper, ".em_before/.em_after" do
|
|
65
81
|
include AMQP::SpecHelper
|
66
82
|
default_options AMQP_OPTS if defined? AMQP_OPTS
|
67
83
|
|
68
|
-
before {
|
69
|
-
|
70
|
-
|
71
|
-
em_after { @hooks_called << :em_after }
|
84
|
+
before { hook :before, :reactor_not_running, :amqp_not_connected }
|
85
|
+
em_before { hook :em_before, :reactor_running, :amqp_not_connected }
|
86
|
+
em_after { hook :em_after, :reactor_running, :amqp_not_connected }
|
72
87
|
|
73
88
|
context 'for non-evented specs' do
|
74
|
-
after {
|
89
|
+
after {
|
90
|
+
@hooks_called.should == [:before]
|
91
|
+
hook :reactor_not_running, :amqp_not_connected }
|
75
92
|
|
76
93
|
it 'should NOT execute em_before or em_after' do
|
77
94
|
@hooks_called.should_not include :em_before
|
@@ -90,7 +107,9 @@ describe AMQP::SpecHelper, ".em_before/.em_after" do
|
|
90
107
|
end # context 'for non-evented specs'
|
91
108
|
|
92
109
|
context 'for evented specs' do #, pending: true do
|
93
|
-
after {
|
110
|
+
after {
|
111
|
+
@hooks_called.should include :before, :em_before, :em_after
|
112
|
+
hook :reactor_not_running, :amqp_not_connected }
|
94
113
|
|
95
114
|
context 'with em block' do
|
96
115
|
|
@@ -106,32 +125,35 @@ describe AMQP::SpecHelper, ".em_before/.em_after" do
|
|
106
125
|
it 'should not run hooks from unrelated group' do
|
107
126
|
em do
|
108
127
|
@hooks_called.should_not include :amqp_context_em_before,
|
109
|
-
:amqp_context_before
|
128
|
+
:amqp_context_before,
|
129
|
+
:amqp_before,
|
130
|
+
:context_amqp_before
|
110
131
|
done
|
111
132
|
end
|
112
133
|
end
|
113
134
|
|
114
135
|
context 'inside nested example group' do
|
115
|
-
before {
|
116
|
-
em_before {
|
117
|
-
em_after {
|
136
|
+
before { hook :context_before, :reactor_not_running, :amqp_not_connected }
|
137
|
+
em_before { hook :context_em_before, :reactor_running, :amqp_not_connected }
|
138
|
+
em_after { hook :context_em_after, :reactor_running, :amqp_not_connected }
|
118
139
|
|
119
140
|
after { @hooks_called.should include :before,
|
120
141
|
:context_before,
|
121
142
|
:em_before,
|
122
143
|
:context_em_before,
|
123
144
|
:context_em_after,
|
124
|
-
:em_after
|
145
|
+
:em_after
|
146
|
+
hook :reactor_not_running, :amqp_not_connected
|
147
|
+
}
|
125
148
|
|
126
149
|
it_should_behave_like 'hooked em specs'
|
127
150
|
|
128
|
-
it 'should fire
|
151
|
+
it 'should fire all nested :before hooks, but no :after hooks' do
|
129
152
|
em do
|
130
|
-
@hooks_called.should
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
@hooks_called.should_not include :em_after, :context_em_after
|
153
|
+
@hooks_called.should == [:before,
|
154
|
+
:context_before,
|
155
|
+
:em_before,
|
156
|
+
:context_em_before]
|
135
157
|
done
|
136
158
|
end
|
137
159
|
end
|
@@ -140,13 +162,16 @@ describe AMQP::SpecHelper, ".em_before/.em_after" do
|
|
140
162
|
end # context 'with em block'
|
141
163
|
|
142
164
|
context 'with amqp block' do
|
165
|
+
amqp_before { hook :amqp_before, :reactor_running, :amqp_connected }
|
166
|
+
amqp_after { hook :amqp_after, :reactor_running, :amqp_connected }
|
143
167
|
|
144
168
|
it_should_behave_like 'hooked amqp specs'
|
145
169
|
|
146
170
|
it 'should not run nested em hooks' do
|
147
171
|
amqp do
|
148
|
-
@hooks_called.should_not include :
|
149
|
-
:
|
172
|
+
@hooks_called.should_not include :amqp_context_before,
|
173
|
+
:amqp_context_em_before,
|
174
|
+
:context_amqp_before
|
150
175
|
done
|
151
176
|
end
|
152
177
|
end
|
@@ -159,16 +184,23 @@ describe AMQP::SpecHelper, ".em_before/.em_after" do
|
|
159
184
|
end
|
160
185
|
|
161
186
|
context 'inside nested example group' do
|
162
|
-
before {
|
163
|
-
em_before {
|
164
|
-
em_after {
|
187
|
+
before { hook :amqp_context_before, :reactor_not_running, :amqp_not_connected }
|
188
|
+
em_before { hook :amqp_context_em_before, :reactor_running, :amqp_not_connected }
|
189
|
+
em_after { hook :amqp_context_em_after, :reactor_running, :amqp_not_connected }
|
190
|
+
amqp_before { hook :context_amqp_before, :reactor_running, :amqp_connected }
|
191
|
+
amqp_after { hook :context_amqp_after, :reactor_running, :amqp_connected }
|
165
192
|
|
166
193
|
after { @hooks_called.should == [:before,
|
167
194
|
:amqp_context_before,
|
168
195
|
:em_before,
|
169
196
|
:amqp_context_em_before,
|
197
|
+
:amqp_before,
|
198
|
+
:context_amqp_before,
|
199
|
+
:context_amqp_after,
|
200
|
+
:amqp_after,
|
170
201
|
:amqp_context_em_after,
|
171
|
-
:em_after]
|
202
|
+
:em_after]
|
203
|
+
hook :reactor_not_running, :amqp_not_connected }
|
172
204
|
|
173
205
|
it_should_behave_like 'hooked amqp specs'
|
174
206
|
|
@@ -177,7 +209,9 @@ describe AMQP::SpecHelper, ".em_before/.em_after" do
|
|
177
209
|
@hooks_called.should == [:before,
|
178
210
|
:amqp_context_before,
|
179
211
|
:em_before,
|
180
|
-
:amqp_context_em_before
|
212
|
+
:amqp_context_em_before,
|
213
|
+
:amqp_before,
|
214
|
+
:context_amqp_before]
|
181
215
|
done
|
182
216
|
end
|
183
217
|
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 3
|
8
|
-
-
|
9
|
-
version: 0.3.
|
8
|
+
- 4
|
9
|
+
version: 0.3.4
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Arvicco
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-11-
|
17
|
+
date: 2010-11-26 00:00:00 +03:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|