amqp-spec 0.3.3 → 0.3.4
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/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
|