amqp-spec 0.0.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/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/HISTORY ADDED
@@ -0,0 +1,19 @@
1
+ == 0.0.0 / 2010-10-13
2
+
3
+ * Birthday!
4
+
5
+ == 0.0.1 / 2010-10-13
6
+
7
+ * Initial code import from EM-Spec and AMQPHelper
8
+
9
+ == 0.0.2 / 2010-10-13
10
+
11
+ * Minimal functionality implemented
12
+
13
+ == 0.0.3 / 2010-10-13
14
+
15
+ * Bunch of non-working specs added
16
+
17
+ == 0.0.4 / 2010-10-14
18
+
19
+ * Problems with default_options resolved
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Arvicco
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,169 @@
1
+ = amqp-spec
2
+ by: Arvicco
3
+ url: http://github.com/arvicco/amqp-spec
4
+
5
+ == Summary
6
+
7
+ Simple API for testing asynchronous EventMachine/AMQP code.
8
+
9
+ == Description
10
+
11
+ EventMachine-based code, including synchronous {AMQP library}[http://github.com/tmm1/amqp] is
12
+ notoriously difficult to test. To the point that many people recommend using either
13
+ Mocks[http://github.com/danielsdeleo/moqueue] or {synchronous libraries}[http://github.com/celldee/bunny]
14
+ instead of EM-based libraries in unit tests. This is not always an option, however - sometimes your code
15
+ is supposed to run inside event loop, and you want to test a real thing, not mocks.
16
+
17
+ EM-Spec[http://github.com/tmm1/em-spec] gem made it easier to write evented specs, but it has several drawbacks.
18
+ First, it is not easy to manage both EM.run and AMQP.start loops at the same time. Second, AMQP is not
19
+ properly stopped and deactivated upon exceptions and timeouts, resulting in AMQP library state leak
20
+ between examples and multiple mystereous failures.
21
+
22
+ AMQP-Spec is built upon EM-Spec code but makes it easier to test AMQP event loops specifically. API is
23
+ very similar to EM-Spec, only a bit extended. The final goal is to make writing AMQP specs reasonably
24
+ pleasant experience and dispel the notion that evented AMQP-based libs are impossible to unit-test.
25
+
26
+ Mind you, you still have to properly manage your AMQP broker in order to prevent broker state from leaking
27
+ between examples. You can try to combine AMQP-Spec and Moqueue[http://github.com/danielsdeleo/moqueue]
28
+ if you want to abstract away actual broker interactions, but still specify some event-based expectations.
29
+
30
+ ==Rspec
31
+ There are several ways to use amqp-spec. To use it as a helper, include AMQP::SpecHelper in your describe block.
32
+ You then use either 'amqp' or 'em' methods to wrap your evented test code. Inside the amqp/em block, you must call
33
+ #done after your expectations. Everything works normally otherwise. You can use default_timeout and default_options
34
+ macros to avoid manually setting AMQP options in each example. However, if you DO manually set options inside
35
+ the example, they override the defaults. Only one set of default options and default timeout is used across groups,
36
+ it is not possible to have separate defaults for separate groups.
37
+
38
+ require "amqp-spec/rspec"
39
+ describe AMQP do
40
+ include AMQP::SpecHelper
41
+
42
+ before(:each) do
43
+ puts EM.reactor_running?
44
+ end
45
+
46
+ it "works normally when not using #amqp or #em" do
47
+ 1.should == 1
48
+ end
49
+
50
+ it "makes testing evented code easy with #em" do
51
+ em do
52
+ start = Time.now
53
+
54
+ EM.add_timer(0.5){
55
+ (Time.now-start).should be_close( 0.5, 0.1 )
56
+ done
57
+ }
58
+ end
59
+ end
60
+
61
+ it "runs AMQP.start loop with options given to #amqp" do
62
+ amqp(:host => 'my.amqp.broker.org', :port => '21118')do
63
+ AMQP.conn.should be_connected
64
+ done
65
+ end
66
+ end
67
+
68
+ it "optionally raises timeout exception if your loop hangs for some reason" do
69
+ proc {
70
+ amqp(:spec_timeout => 3){}
71
+ }.should raise_error SpecTimeoutExceededError
72
+ end
73
+
74
+ default_timeout 1 # Can be used to set default :spec_timeout for all your amqp-based specs
75
+ end
76
+
77
+ Another option is to include AMQP::Spec in your describe block. This will patch Rspec so that all of your
78
+ examples run inside an amqp block automatically. A word of caution about before{} and after{} hooks in your example
79
+ groups including AMQP::Spec. Each of these hooks will run in its separate EM loop that you'll need to shut down
80
+ either manually (done) or via default_timeout. Essentially, this means that any EM-related state that you'd like
81
+ to setup/teardown using these hooks will be lost as each example will run in a separate thread. In order to
82
+ run setup/teardown hooks inside the EM loop, you'll need to use before_amqp{} and after_amqp{} hooks that run
83
+ inside the EM loop but before/after AMQP loop (these hooks are currently not implemented)
84
+
85
+ require "amqp-spec/rspec"
86
+ describe AMQP do
87
+ include AMQP::Spec
88
+
89
+ before(:each) do
90
+ puts EM.reactor_running?
91
+ done
92
+ end
93
+
94
+ default_options :host => 'my.amqp.broker.org', :port => '21118'
95
+ # Can be used to set default options for all your (implied) amqp{} event loops
96
+
97
+ it "requires a call to #done in every example" do
98
+ 1.should == 1
99
+ done
100
+ end
101
+
102
+ it "runs test code in an amqp block automatically" do
103
+ start = Time.now
104
+
105
+ EM.add_timer(0.5){
106
+ (Time.now-start).should be_close( 0.5, 0.1 )
107
+ done
108
+ }
109
+ end
110
+
111
+ it "runs AMQP.start loop with default_options" do
112
+ AMQP.conn.should be_connected
113
+ done
114
+ end
115
+
116
+ it "raises timeout exception ONLY if default_timeout was set" do
117
+ proc{}.should raise_error SpecTimeoutExceededError
118
+ end
119
+ end
120
+
121
+ Finally, you can include AMQP::EMSpec in your describe block. This will run all the group examples
122
+ inside em block instead of amqp. before{} and after{} hooks should be finished with 'done', same as
123
+ when including AMQP::Spec, and same caution about using them applies.
124
+
125
+ require "amqp-spec/rspec"
126
+ describe AMQP do
127
+ include AMQP::EMSpec
128
+
129
+ it "requires a call to #done in every example" do
130
+ 1.should == 1
131
+ done
132
+ end
133
+
134
+ it "runs test code in an amqp block automatically" do
135
+ start = Time.now
136
+
137
+ EM.add_timer(0.5){
138
+ (Time.now-start).should be_close( 0.5, 0.1 )
139
+ done
140
+ }
141
+ end
142
+
143
+ default_timeout 1
144
+ # Default spec timeouts can be used same as with AMQP::Spec, default_options (if defined) are not used
145
+ end
146
+
147
+
148
+ ==Bacon
149
+
150
+ ...
151
+
152
+ ==Test::Unit
153
+
154
+ ...
155
+
156
+ ==Limitations
157
+
158
+ AMQP-Spec can be currently used with rspec only. I suppose, there is nothing special in extending EM-Spec's
159
+ test unit and bacon support, I just do not have experience dealing with these platforms. Another limitation,
160
+ it uses native Fibers and therefore not compatible with Ruby 1.8. Again, it seems possible to rewrite it in
161
+ 1.8-compatible style, with string evals and Fiber backport, but I'd rather leave this work to someone else.
162
+
163
+ Any help improving this library is greatly appreciated...
164
+
165
+ == LICENSE:
166
+ Copyright (c) 2010 Arvicco.
167
+ Original EM-Spec code copyright (c) 2008 Aman Gupta (tmm1)
168
+
169
+ See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,24 @@
1
+ require 'pathname'
2
+ NAME = 'amqp-spec'
3
+ BASE_PATH = Pathname.new(__FILE__).dirname
4
+ LIB_PATH = BASE_PATH + 'lib'
5
+ PKG_PATH = BASE_PATH + 'pkg'
6
+ DOC_PATH = BASE_PATH + 'rdoc'
7
+
8
+ $LOAD_PATH.unshift LIB_PATH.to_s
9
+ require 'version'
10
+
11
+ CLASS_NAME = AMQP::Spec
12
+ VERSION = CLASS_NAME::VERSION
13
+
14
+ begin
15
+ require 'rake'
16
+ rescue LoadError
17
+ require 'rubygems'
18
+ gem 'rake', '~> 0.8.3.1'
19
+ require 'rake'
20
+ end
21
+
22
+ # Load rakefile tasks
23
+ Dir['tasks/*.rake'].sort.each { |file| load file }
24
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.4
data/lib/amqp-spec.rb ADDED
@@ -0,0 +1,27 @@
1
+ require 'version'
2
+
3
+ module AMQP
4
+ module Spec
5
+
6
+ # require "bundler"
7
+ # Bundler.setup
8
+
9
+ # Requires ruby source file(s). Accepts either single filename/glob or Array of filenames/globs.
10
+ # Accepts following options:
11
+ # :*file*:: Lib(s) required relative to this file - defaults to __FILE__
12
+ # :*dir*:: Required lib(s) located under this dir name - defaults to gem name
13
+ #
14
+ def self.require_libs(libs, opts={})
15
+ file = Pathname.new(opts[:file] || __FILE__)
16
+ [libs].flatten.each do |lib|
17
+ name = file.dirname + (opts[:dir] || file.basename('.*')) + lib.gsub(/(?<!.rb)$/, '.rb')
18
+ Pathname.glob(name.to_s).sort.each { |rb| require rb }
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ # Require all ruby source files located under directory lib/amqp-spec
25
+ # If you need files in specific order, you should specify it here before the glob
26
+ AMQP::Spec.require_libs %W[**/*]
27
+
@@ -0,0 +1,195 @@
1
+ require 'mq'
2
+ require 'fiber' unless Fiber.respond_to?(:current)
3
+
4
+ # You can include one of the following modules into your example groups:
5
+ # AMQP::SpecHelper
6
+ # AMQP::Spec
7
+ #
8
+ # AMQP::SpecHelper module defines 'ampq' method that can be safely used inside your specs(examples)
9
+ # to test expectations inside running AMQP.start loop. Each loop is running in a separate Fiber,
10
+ # and you can control for timeouts using either :spec_timeout option given to amqp method,
11
+ # or setting default timeout with class method default_timeout(timeout).
12
+ #
13
+ # If you include AMQP::Spec module into your example group, each example of this group will run
14
+ # inside AMQP.start loop without the need to explicitly call 'amqp'. In order to provide options
15
+ # to AMQP loop, default_options class method is defined. Remember, when using AMQP::Specs, you
16
+ # will have a single set of AMQP.start options for all your examples.
17
+ #
18
+ # In order to stop AMQP loop, you should call 'done' AFTER you are sure that your example is finished.
19
+ # For example, if you are using subscribe block that tests expectations on messages, 'done' should be
20
+ # probably called at the end of this block.
21
+ #
22
+ # TODO: Define 'async' method wrapping async requests and returning results... 'async_loop' too for subscribe?
23
+ # TODO: 'evented_before', 'evented_after' that will be run inside EM before the example
24
+ module AMQP
25
+ module SpecHelper
26
+
27
+ SpecTimeoutExceededError = Class.new(RuntimeError)
28
+
29
+ def self.included(example_group)
30
+ ::Spec::Example::ExampleGroup.instance_exec do
31
+ unless defined? default_spec_timeout
32
+
33
+ @@_em_default_options = {}
34
+ @@_em_default_timeout = nil
35
+
36
+ def self.default_spec_timeout(spec_timeout=nil)
37
+ if spec_timeout
38
+ @@_em_default_timeout = spec_timeout
39
+ else
40
+ @@_em_default_timeout
41
+ end
42
+ end
43
+ alias default_timeout default_spec_timeout
44
+
45
+ def self.default_options(opts=nil)
46
+ if opts
47
+ @@_em_default_options = opts
48
+ else
49
+ @@_em_default_options
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ # Yields to given block inside EM.run and AMQP.start loops. This method takes any option that is
57
+ # also accepted by EventMachine::connect. Also, options for AMQP.start include:
58
+ # * :user => String (default ‘guest’) - The username as defined by the AMQP server.
59
+ # * :pass => String (default ‘guest’) - The password for the associated :user as defined by the AMQP server.
60
+ # * :vhost => String (default ’/’) - The virtual host as defined by the AMQP server.
61
+ # * :timeout => Numeric (default nil) - *Connection* timeout, measured in seconds.
62
+ # * :logging => true | false (default false) - Toggle the extremely verbose AMQP logging.
63
+ #
64
+ # In addition to EM and AMQP options, :spec_timeout option (in seconds) is used to force spec to timeout
65
+ # if something goes wrong and EM/AMQP loop hangs for some reason. SpecTimeoutExceededError is raised.
66
+
67
+ def amqp opts={}, &block
68
+ opts = @@_em_default_options.merge opts
69
+ EM.run do
70
+ # begin ?
71
+ @_em_spec_with_amqp = true
72
+ @_em_spec_exception = nil
73
+ spec_timeout = opts.delete(:spec_timeout) || @@_em_default_timeout
74
+ timeout(spec_timeout) if spec_timeout
75
+ @_em_spec_fiber = Fiber.new do
76
+ begin
77
+ amqp_start opts, &block
78
+ rescue Exception => @_em_spec_exception
79
+ p @_em_spec_exception
80
+ done
81
+ end
82
+ Fiber.yield
83
+ end
84
+
85
+ @_em_spec_fiber.resume
86
+ # raise @_em_spec_exception if @_em_spec_exception
87
+ end
88
+ end
89
+
90
+ # Yields to block inside EM loop, :spec_timeout option (in seconds) is used to force spec to timeout
91
+ # if something goes wrong and EM/AMQP loop hangs for some reason. SpecTimeoutExceededError is raised.
92
+ # TODO: accept :spec_timeout =>1 as a Hash for compatibility with amqp interface
93
+ def em(spec_timeout = @@_em_default_timeout, &block)
94
+ EM.run do
95
+ @_em_spec_with_amqp = false
96
+ @_em_spec_exception = nil
97
+ timeout(spec_timeout) if spec_timeout
98
+ @_em_spec_fiber = Fiber.new do
99
+ begin
100
+ block.call
101
+ rescue Exception => @_em_spec_exception
102
+ done
103
+ end
104
+ Fiber.yield
105
+ end
106
+
107
+ @_em_spec_fiber.resume
108
+ end
109
+ end
110
+
111
+ # Sets timeout for current spec
112
+ def timeout(spec_timeout)
113
+ EM.cancel_timer(@_em_timer) if @_em_timer
114
+ @_em_timer = EM.add_timer(spec_timeout) do
115
+ @_em_spec_exception = SpecTimeoutExceededError.new
116
+ done
117
+ end
118
+ end
119
+
120
+ # Stops AMQP and EM event loop
121
+ def done
122
+ EM.next_tick do
123
+ if @_em_spec_with_amqp
124
+ amqp_stop(@_em_spec_exception) do
125
+ finish_em_spec_fiber
126
+ end
127
+ else
128
+ finish_em_spec_fiber
129
+ raise @_em_spec_exception if @_em_spec_exception
130
+ end
131
+ end
132
+ end
133
+
134
+ private
135
+
136
+ def finish_em_spec_fiber
137
+ EM.stop_event_loop if EM.reactor_running?
138
+ # p Thread.current, Thread.current[:mq], __LINE__
139
+ @_em_spec_fiber.resume if @_em_spec_fiber.alive?
140
+ end
141
+
142
+ # Private method that initializes AMQP client/connection without starting another EM loop
143
+ def amqp_start opts={}, &block
144
+ AMQP.instance_exec do
145
+ # p Thread.current, Thread.current[:mq]
146
+ puts "!!!!!!!!! Existing connection: #{@conn}" if @conn
147
+ @conn = connect opts
148
+ # @conn ||= connect opts
149
+ @conn.callback(&block) if block
150
+ end
151
+ end
152
+
153
+ # Private method that closes AMQP connection and raises optional
154
+ # exception AFTER the AMQP connection is 100% closed
155
+ def amqp_stop exception
156
+ if AMQP.conn and not AMQP.closing
157
+ AMQP.instance_exec do #(@_em_spec_exception) do |exception|
158
+ @closing = true
159
+ @conn.close {
160
+ yield if block_given?
161
+ @conn = nil
162
+ @closing = false
163
+ raise exception if exception
164
+ }
165
+ end
166
+ end
167
+ end
168
+ end
169
+
170
+ module Spec
171
+ def self.included(cls)
172
+ cls.send(:include, SpecHelper)
173
+ end
174
+
175
+ def instance_eval(&block)
176
+ amqp do
177
+ super(&block)
178
+ end
179
+ end
180
+ end
181
+
182
+ module EMSpec
183
+ def self.included(cls)
184
+ cls.send(:include, SpecHelper)
185
+ end
186
+
187
+ def instance_eval(&block)
188
+ em do
189
+ super(&block)
190
+ end
191
+ end
192
+ end
193
+ end
194
+
195
+
data/lib/version.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'pathname'
2
+
3
+ module AMQP
4
+ module Spec
5
+
6
+ VERSION_FILE = Pathname.new(__FILE__).dirname + '../VERSION' # :nodoc:
7
+ VERSION = VERSION_FILE.exist? ? VERSION_FILE.read.strip : nil
8
+
9
+ end
10
+ end
data/spec/amqp.yml ADDED
@@ -0,0 +1,28 @@
1
+ # AMQP client configuration file
2
+
3
+ # These values will be used to configure the ampq gem, any values
4
+ # omitted will let the gem use it's own defaults.
5
+
6
+ # The configuration specifies the following keys:
7
+ # * user - Username for the broker
8
+ # * pass - Password for the broker
9
+ # * host - Hostname where the broker is running
10
+ # * vhost - Vhost to connect to
11
+ # * port - Port where the broker is running
12
+ # * ssl - Use ssl or not
13
+ # * timeout - Timeout
14
+
15
+ defaults: &defaults
16
+ user: guest
17
+ pass: guest
18
+ host: 10.211.55.2
19
+ vhost: /
20
+
21
+ development:
22
+ <<: *defaults
23
+
24
+ test:
25
+ <<: *defaults
26
+
27
+ production:
28
+ <<: *defaults
@@ -0,0 +1,62 @@
1
+ require_relative 'spec_helper.rb'
2
+
3
+ context 'Following examples should all be failing:' do
4
+ describe EventMachine, " when running failing examples" do
5
+ include AMQP::EMSpec
6
+
7
+ it "should not bubble failures beyond rspec" do
8
+ EM.add_timer(0.1) do
9
+ :should_not_bubble.should == :failures
10
+ done
11
+ end
12
+ end
13
+
14
+ it "should not block on failure" do
15
+ 1.should == 2
16
+ end
17
+ end
18
+
19
+ describe EventMachine, " when testing with AMQP::EMSpec with a maximum execution time per test" do
20
+
21
+ include AMQP::EMSpec
22
+ it 'should timeout before reaching done' do
23
+ EM.add_timer(2) {
24
+ done
25
+ }
26
+ end
27
+
28
+ it 'should timeout before reaching done' do
29
+ timeout(0.3)
30
+ EM.add_timer(0.6) {
31
+ done
32
+ }
33
+ end
34
+ end
35
+
36
+ describe AMQP, " when testing with AMQP::Spec with a maximum execution time per test" do
37
+
38
+ include AMQP::Spec
39
+
40
+ default_timeout 1
41
+
42
+ it 'should timeout before reaching done' do
43
+ EM.add_timer(2) {
44
+ done
45
+ }
46
+ end
47
+
48
+ it 'should timeout before reaching done' do
49
+ timeout(0.2)
50
+ EM.add_timer(0.5) {
51
+ done
52
+ }
53
+ end
54
+
55
+ it 'should fail due to timeout, not hang up' do
56
+ timeout(0.2)
57
+ end
58
+
59
+ it 'should fail due to default timeout, not hang up' do
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,273 @@
1
+ require_relative 'spec_helper.rb'
2
+
3
+ describe 'Rspec' do
4
+ it 'should work as normal without AMQP-Spec' do
5
+ 1.should == 1
6
+ end
7
+ end
8
+
9
+ context 'Evented AMQP specs' do
10
+ describe AMQP, " when testing with AMQP::SpecHelper" do
11
+ include AMQP::SpecHelper
12
+ after(:each) do
13
+ EM.reactor_running?.should == false
14
+ AMQP.conn.should be_nil
15
+ end
16
+
17
+ default_options AMQP_OPTS if defined? AMQP_OPTS
18
+ default_timeout 1 # Can be used to set default :spec_timeout for all your amqp-based specs
19
+
20
+ p default_timeout, default_options
21
+
22
+ it "should not require a call to done when #em/#amqp is not used" do
23
+ 1.should == 1
24
+ end
25
+
26
+ it "should properly work" do
27
+ amqp do
28
+ done
29
+ end
30
+ end
31
+
32
+ it "should have timers" do
33
+ amqp do
34
+ start = Time.now
35
+
36
+ EM.add_timer(0.5) {
37
+ (Time.now-start).should be_close(0.5, 0.1)
38
+ done
39
+ }
40
+ end
41
+ end
42
+
43
+ it "should run AMQP.start loop with options given to #amqp" do
44
+ amqp(:vhost => '/', :user => 'guest') do
45
+ AMQP.conn.should be_connected
46
+ done
47
+ end
48
+ end
49
+
50
+ it "should properly close AMQP connection if block completes normally" do
51
+ amqp do
52
+ AMQP.conn.should be_connected
53
+ done
54
+ end
55
+ AMQP.conn.should be_nil
56
+ end
57
+
58
+ it 'should have deferrables' do
59
+ amqp do
60
+ defr = EM::DefaultDeferrable.new
61
+ defr.timeout(0.5)
62
+ defr.errback {
63
+ done
64
+ }
65
+ end
66
+ end
67
+
68
+ context 'on exceptions/failures' do
69
+ # default_timeout 1 # Can be used to set default :spec_timeout for all your amqp-based specs
70
+
71
+
72
+ end
73
+ end
74
+
75
+ describe AMQP, " when testing with AMQP::Spec" do
76
+ include AMQP::Spec
77
+ after(:each) do
78
+ EM.reactor_running?.should == true
79
+ done
80
+ end
81
+
82
+ it 'should work' do
83
+ done
84
+ end
85
+
86
+ it 'should have timers' do
87
+ start = Time.now
88
+
89
+ EM.add_timer(0.5) {
90
+ (Time.now-start).should be_close(0.5, 0.1)
91
+ done
92
+ }
93
+ end
94
+
95
+ it 'should have periodic timers' do
96
+ num = 0
97
+ start = Time.now
98
+
99
+ timer = EM.add_periodic_timer(0.25) {
100
+ if (num += 1) == 2
101
+ (Time.now-start).should be_close(0.5, 0.1)
102
+ EM.cancel_timer timer
103
+ done
104
+ end
105
+ }
106
+ end
107
+
108
+ it 'should have deferrables' do
109
+ defr = EM::DefaultDeferrable.new
110
+ defr.timeout(0.5)
111
+ defr.errback {
112
+ done
113
+ }
114
+ end
115
+ end
116
+
117
+ shared_examples_for 'timeout examples' do
118
+ include AMQP::SpecHelper
119
+ before(:each) { @start = Time.now }
120
+
121
+ it 'should timeout before reaching done because of default spec timeout' do
122
+ proc {
123
+ amqp do
124
+ EM.add_timer(2) { done }
125
+ end
126
+ }.should raise_error SpecTimeoutExceededError
127
+ (Time.now-@start).should be_close(1.0, 0.1)
128
+ end
129
+
130
+ it 'should timeout before reaching done because of explicit in-loop timeout' do
131
+ proc {
132
+ amqp do
133
+ timeout(0.2)
134
+ EM.add_timer(0.5) { done }
135
+ end
136
+ }.should raise_error SpecTimeoutExceededError
137
+ (Time.now-@start).should be_close(0.2, 0.1)
138
+ end
139
+
140
+ specify "spec timeout given in amqp options has higher priority than default" do
141
+ proc {
142
+ amqp(:spec_timeout => 0.2) {}
143
+ }.should raise_error SpecTimeoutExceededError
144
+ (Time.now-@start).should be_close(0.2, 0.1)
145
+ end
146
+
147
+ specify "but timeout call inside amqp loop has even higher priority" do
148
+ proc {
149
+ amqp(:spec_timeout => 0.5) { timeout(0.2) }
150
+ }.should raise_error SpecTimeoutExceededError
151
+ (Time.now-@start).should be_close(0.2, 0.1)
152
+ end
153
+
154
+ specify "AMQP connection should not leak between examples" do
155
+ AMQP.conn.should be_nil
156
+ end
157
+ end
158
+
159
+ describe AMQP, " when testing with AMQP::SpecHelper with spec timeouts" do
160
+ it_should_behave_like 'timeout examples'
161
+
162
+ context 'inside embedded context / example group' do
163
+ it_should_behave_like 'timeout examples'
164
+ # it "should properly timeout " do
165
+ # amqp do
166
+ # timeout(0.2)
167
+ # end
168
+ # end
169
+ # it "should properly timeout inside embedded context/describe blocks" do
170
+ # amqp do
171
+ # end
172
+ # end
173
+ end
174
+ end
175
+
176
+ # PROBLEMATIC !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
177
+ describe AMQP, " when testing with AMQP::SpecHelper with spec timeouts" do
178
+ include AMQP::SpecHelper
179
+
180
+ it "should fail to provide context to next spec" do
181
+ begin
182
+ amqp do
183
+ :this.should == :fail
184
+ end
185
+ rescue => e
186
+ p e
187
+ end
188
+ end
189
+
190
+ it "should properly close AMQP connection after Rspec failures" do
191
+ AMQP.conn.should == nil
192
+ end
193
+ end
194
+
195
+ # describe MQ, " when MQ.queue or MQ.fanout etc is trying to access Thread-local mq across examples" do
196
+ # include AMQP::SpecHelper
197
+ #
198
+ # default_timeout 1
199
+ #
200
+ # it 'sends data to queue' do
201
+ # amqp do
202
+ # q = MQ.new.queue("test_sink")
203
+ # q.subscribe do |hdr, data|
204
+ # p hdr, data
205
+ # EM.next_tick {
206
+ # q.unsubscribe; q.delete
207
+ # AMQP.stop { EM.stop_event_loop }
208
+ # }
209
+ # end
210
+ # EM.add_timer(0.2) do
211
+ # p Thread.current, Thread.current[:mq]
212
+ # MQ.queue('test_sink').publish 'data' # MQ.new. !!!!!!!!!!!
213
+ # end
214
+ # end
215
+ # end
216
+ #
217
+ # it 'sends data to queue' do
218
+ # amqp do
219
+ # q = MQ.new.queue("test_sink")
220
+ # q.subscribe do |hdr, data|
221
+ # p hdr, data
222
+ # EM.next_tick {
223
+ # q.unsubscribe; q.delete
224
+ # AMQP.stop { EM.stop_event_loop }
225
+ # }
226
+ # end
227
+ # EM.add_timer(0.2) do
228
+ # p Thread.current, Thread.current[:mq]
229
+ # MQ.queue('test_sink').publish 'data' # MQ.new. !!!!!!!!!!!
230
+ # end
231
+ # end
232
+ # end
233
+ #
234
+ # end
235
+ end
236
+
237
+ context '!!!!!!!!!!! LEAKING !!!!!!!!!!!!!!!!!!' do
238
+ describe EventMachine, " when running failing examples" do
239
+ include AMQP::SpecHelper
240
+
241
+ it "should not bubble failures beyond rspec" do
242
+ amqp do
243
+ EM.add_timer(0.1) do
244
+ :should_not_bubble.should == :failures
245
+ done
246
+ end
247
+ end
248
+ AMQP.conn.should == nil
249
+ end
250
+
251
+ it "should not block on failure" do
252
+ 1.should == 2
253
+ end
254
+ end
255
+
256
+ describe EventMachine, " when testing with AMQP::Spec with a maximum execution time per test" do
257
+
258
+ include AMQP::Spec
259
+
260
+ it 'should timeout before reaching done' do
261
+ EM.add_timer(2) {
262
+ done
263
+ }
264
+ end
265
+ end
266
+ end
267
+
268
+
269
+ describe "Rspec", " when running an example group after another group that uses AMQP-Spec " do
270
+ it "should work normally" do
271
+ :does_not_hang.should_not be_false
272
+ end
273
+ end
@@ -0,0 +1,67 @@
1
+ require_relative 'spec_helper.rb'
2
+
3
+ context 'Plain EM, no AMQP' do
4
+ describe EventMachine, " when testing with AMQP::SpecHelper" do
5
+ include AMQP::SpecHelper
6
+
7
+ it "should not require a call to done when #em is not used" do
8
+ 1.should == 1
9
+ end
10
+
11
+ it "should have timers" do
12
+ em do
13
+ start = Time.now
14
+
15
+ EM.add_timer(0.5) {
16
+ (Time.now-start).should be_close(0.5, 0.1)
17
+ done
18
+ }
19
+ end
20
+ end
21
+ end
22
+
23
+ describe EventMachine, " when testing with AMQP::Spec" do
24
+ include AMQP::EMSpec
25
+
26
+ it 'should work' do
27
+ done
28
+ end
29
+
30
+ it 'should have timers' do
31
+ start = Time.now
32
+
33
+ EM.add_timer(0.5) {
34
+ (Time.now-start).should be_close(0.5, 0.1)
35
+ done
36
+ }
37
+ end
38
+
39
+ it 'should have periodic timers' do
40
+ num = 0
41
+ start = Time.now
42
+
43
+ timer = EM.add_periodic_timer(0.2) {
44
+ if (num += 1) == 2
45
+ (Time.now-start).should be_close(0.4, 0.1)
46
+ EM.__send__ :cancel_timer, timer
47
+ done
48
+ end
49
+ }
50
+ end
51
+
52
+ it 'should have deferrables' do
53
+ defr = EM::DefaultDeferrable.new
54
+ defr.timeout(0.5)
55
+ defr.errback {
56
+ done
57
+ }
58
+ end
59
+
60
+ end
61
+ end
62
+
63
+ describe "Rspec", " when running an example group after groups that uses EM specs " do
64
+ it "should work normally" do
65
+ :does_not_hang.should_not be_false
66
+ end
67
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format nested
@@ -0,0 +1,35 @@
1
+ #$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+
3
+ $LOAD_PATH << "." unless $LOAD_PATH.include? "." # moronic 1.9.2 breaks things bad
4
+
5
+ require 'spec'
6
+ require 'yaml'
7
+
8
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'amqp-spec', 'rspec.rb')
9
+
10
+ amqp_config = File.dirname(__FILE__) + '/amqp.yml'
11
+ if File.exists? amqp_config
12
+
13
+ class Hash
14
+ def symbolize_keys
15
+ self.inject({}) { |result, (key, value)|
16
+ new_key = case key
17
+ when String then
18
+ key.to_sym
19
+ else
20
+ key
21
+ end
22
+ new_value = case value
23
+ when Hash then
24
+ value.symbolize_keys
25
+ else
26
+ value
27
+ end
28
+ result[new_key] = new_value
29
+ result
30
+ }
31
+ end
32
+ end
33
+
34
+ AMQP_OPTS = YAML::load_file(amqp_config).symbolize_keys[:test]
35
+ end
data/tasks/common.rake ADDED
@@ -0,0 +1,18 @@
1
+ #task :default => 'test:run'
2
+ #task 'gem:release' => 'test:run'
3
+
4
+ task :notes do
5
+ puts 'Output annotations (TBD)'
6
+ end
7
+
8
+ #Bundler not ready for prime time just yet
9
+ #desc 'Bundle dependencies'
10
+ #task :bundle do
11
+ # output = `bundle check 2>&1`
12
+ #
13
+ # unless $?.to_i == 0
14
+ # puts output
15
+ # system "bundle install"
16
+ # puts
17
+ # end
18
+ #end
data/tasks/doc.rake ADDED
@@ -0,0 +1,14 @@
1
+ desc 'Alias to doc:rdoc'
2
+ task :doc => 'doc:rdoc'
3
+
4
+ namespace :doc do
5
+ require 'rake/rdoctask'
6
+ Rake::RDocTask.new do |rdoc|
7
+ # Rake::RDocTask.new(:rdoc => "rdoc", :clobber_rdoc => "clobber", :rerdoc => "rerdoc") do |rdoc|
8
+ rdoc.rdoc_dir = DOC_PATH.basename.to_s
9
+ rdoc.title = "#{NAME} #{VERSION} Documentation"
10
+ rdoc.main = "README.doc"
11
+ rdoc.rdoc_files.include('README*')
12
+ rdoc.rdoc_files.include('lib/**/*.rb')
13
+ end
14
+ end
data/tasks/gem.rake ADDED
@@ -0,0 +1,40 @@
1
+ desc "Alias to gem:release"
2
+ task :release => 'gem:release'
3
+
4
+ desc "Alias to gem:install"
5
+ task :install => 'gem:install'
6
+
7
+ desc "Alias to gem:build"
8
+ task :gem => 'gem:build'
9
+
10
+ namespace :gem do
11
+ gem_file = "#{NAME}-#{VERSION}.gem"
12
+
13
+ desc "(Re-)Build gem"
14
+ task :build do
15
+ puts "Remove existing gem package"
16
+ rm_rf PKG_PATH
17
+ puts "Build new gem package"
18
+ system "gem build #{NAME}.gemspec"
19
+ puts "Move built gem to package dir"
20
+ mkdir_p PKG_PATH
21
+ mv gem_file, PKG_PATH
22
+ end
23
+
24
+ desc "Cleanup already installed gem(s)"
25
+ task :cleanup do
26
+ puts "Cleaning up installed gem(s)"
27
+ system "gem cleanup #{NAME}"
28
+ end
29
+
30
+ desc "Build and install gem"
31
+ task :install => :build do
32
+ system "gem install #{PKG_PATH}/#{gem_file}"
33
+ end
34
+
35
+ desc "Build and push gem to Gemcutter"
36
+ task :release => [:build, 'git:tag'] do
37
+ puts "Pushing gem to Gemcutter"
38
+ system "gem push #{PKG_PATH}/#{gem_file}"
39
+ end
40
+ end
data/tasks/git.rake ADDED
@@ -0,0 +1,34 @@
1
+ desc "Alias to git:commit"
2
+ task :git => 'git:commit'
3
+
4
+ namespace :git do
5
+
6
+ desc "Stage and commit your work [with message]"
7
+ task :commit, [:message] do |t, args|
8
+ puts "Staging new (unversioned) files"
9
+ system "git add --all"
10
+ if args.message
11
+ puts "Committing with message: #{args.message}"
12
+ system %Q[git commit -a -m "#{args.message}" --author arvicco]
13
+ else
14
+ puts "Committing"
15
+ system %Q[git commit -a -m "No message" --author arvicco]
16
+ end
17
+ end
18
+
19
+ desc "Push local changes to Github"
20
+ task :push => :commit do
21
+ puts "Pushing local changes to remote"
22
+ system "git push"
23
+ end
24
+
25
+ desc "Create (release) tag on Github"
26
+ task :tag => :push do
27
+ tag = VERSION
28
+ puts "Creating git tag: #{tag}"
29
+ system %Q{git tag -a -m "Release tag #{tag}" #{tag}}
30
+ puts "Pushing #{tag} to remote"
31
+ system "git push origin #{tag}"
32
+ end
33
+
34
+ end
data/tasks/spec.rake ADDED
@@ -0,0 +1,19 @@
1
+ desc 'Alias to spec:spec'
2
+ task :spec => 'spec:spec'
3
+
4
+ namespace :spec do
5
+ require 'spec/rake/spectask'
6
+
7
+ desc "Run all specs"
8
+ Spec::Rake::SpecTask.new(:spec) do |t|
9
+ t.spec_opts = ['--options', %Q{"#{BASE_PATH}/spec/spec.opts"}]
10
+ t.spec_files = FileList['spec/**/*_spec.rb']
11
+ end
12
+
13
+ desc "Run specs with RCov"
14
+ Spec::Rake::SpecTask.new(:rcov) do |t|
15
+ t.spec_files = FileList['spec/**/*_spec.rb']
16
+ t.rcov = true
17
+ t.rcov_opts = ['--exclude', 'spec']
18
+ end
19
+ end
@@ -0,0 +1,71 @@
1
+ class Version
2
+ attr_accessor :major, :minor, :patch, :build
3
+
4
+ def initialize(version_string)
5
+ raise "Invalid version #{version_string}" unless version_string =~ /^(\d+)\.(\d+)\.(\d+)(?:\.(.*?))?$/
6
+ @major = $1.to_i
7
+ @minor = $2.to_i
8
+ @patch = $3.to_i
9
+ @build = $4
10
+ end
11
+
12
+ def bump_major(x)
13
+ @major += x.to_i
14
+ @minor = 0
15
+ @patch = 0
16
+ @build = nil
17
+ end
18
+
19
+ def bump_minor(x)
20
+ @minor += x.to_i
21
+ @patch = 0
22
+ @build = nil
23
+ end
24
+
25
+ def bump_patch(x)
26
+ @patch += x.to_i
27
+ @build = nil
28
+ end
29
+
30
+ def update(major, minor, patch, build=nil)
31
+ @major = major
32
+ @minor = minor
33
+ @patch = patch
34
+ @build = build
35
+ end
36
+
37
+ def write(desc = nil)
38
+ CLASS_NAME::VERSION_FILE.open('w') {|file| file.puts to_s }
39
+ (BASE_PATH + 'HISTORY').open('a') do |file|
40
+ file.puts "\n== #{to_s} / #{Time.now.strftime '%Y-%m-%d'}\n"
41
+ file.puts "\n* #{desc}\n" if desc
42
+ end
43
+ end
44
+
45
+ def to_s
46
+ [major, minor, patch, build].compact.join('.')
47
+ end
48
+ end
49
+
50
+ desc 'Set version: [x.y.z] - explicitly, [1/10/100] - bump major/minor/patch, [.build] - build'
51
+ task :version, [:command, :desc] do |t, args|
52
+ version = Version.new(VERSION)
53
+ case args.command
54
+ when /^(\d+)\.(\d+)\.(\d+)(?:\.(.*?))?$/ # Set version explicitly
55
+ version.update($1, $2, $3, $4)
56
+ when /^\.(.*?)$/ # Set build
57
+ version.build = $1
58
+ when /^(\d{1})$/ # Bump patch
59
+ version.bump_patch $1
60
+ when /^(\d{1})0$/ # Bump minor
61
+ version.bump_minor $1
62
+ when /^(\d{1})00$/ # Bump major
63
+ version.bump_major $1
64
+ else # Unknown command, just display VERSION
65
+ puts "#{NAME} #{version}"
66
+ next
67
+ end
68
+
69
+ puts "Writing version #{version} to VERSION file"
70
+ version.write args.desc
71
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: amqp-spec
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 4
10
+ version: 0.0.4
11
+ platform: ruby
12
+ authors:
13
+ - Arvicco
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-10-14 00:00:00 +04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rspec
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 13
30
+ segments:
31
+ - 1
32
+ - 2
33
+ - 9
34
+ version: 1.2.9
35
+ type: :development
36
+ version_requirements: *id001
37
+ description: Simple API for writing (asynchronous) AMQP specs
38
+ email: arvitallian@gmail.com
39
+ executables: []
40
+
41
+ extensions: []
42
+
43
+ extra_rdoc_files:
44
+ - LICENSE
45
+ - HISTORY
46
+ - README.rdoc
47
+ files:
48
+ - lib/amqp-spec/rspec.rb
49
+ - lib/amqp-spec.rb
50
+ - lib/version.rb
51
+ - spec/amqp.yml
52
+ - spec/failing_rspec_spec.rb
53
+ - spec/rspec_amqp_spec.rb
54
+ - spec/rspec_em_spec.rb
55
+ - spec/spec.opts
56
+ - spec/spec_helper.rb
57
+ - tasks/common.rake
58
+ - tasks/doc.rake
59
+ - tasks/gem.rake
60
+ - tasks/git.rake
61
+ - tasks/spec.rake
62
+ - tasks/version.rake
63
+ - Rakefile
64
+ - README.rdoc
65
+ - LICENSE
66
+ - VERSION
67
+ - HISTORY
68
+ - .gitignore
69
+ has_rdoc: true
70
+ homepage: http://github.com/arvicco/amqp-spec
71
+ licenses: []
72
+
73
+ post_install_message:
74
+ rdoc_options:
75
+ - --charset
76
+ - UTF-8
77
+ - --main
78
+ - README.rdoc
79
+ - --title
80
+ - amqp-spec
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ hash: 3
89
+ segments:
90
+ - 0
91
+ version: "0"
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ hash: 3
98
+ segments:
99
+ - 0
100
+ version: "0"
101
+ requirements: []
102
+
103
+ rubyforge_project:
104
+ rubygems_version: 1.3.7
105
+ signing_key:
106
+ specification_version: 3
107
+ summary: Simple API for writing (asynchronous) AMQP specs
108
+ test_files:
109
+ - spec/amqp.yml
110
+ - spec/failing_rspec_spec.rb
111
+ - spec/rspec_amqp_spec.rb
112
+ - spec/rspec_em_spec.rb
113
+ - spec/spec.opts
114
+ - spec/spec_helper.rb