evented-spec 0.4.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. data/HISTORY +8 -1
  2. data/README.textile +51 -9
  3. data/Rakefile +3 -4
  4. data/VERSION +1 -1
  5. data/lib/evented-spec.rb +22 -1
  6. data/lib/evented-spec/amqp_spec.rb +27 -0
  7. data/lib/evented-spec/coolio_spec.rb +26 -0
  8. data/lib/evented-spec/em_spec.rb +26 -0
  9. data/lib/evented-spec/evented_example.rb +13 -5
  10. data/lib/evented-spec/evented_example/amqp_example.rb +8 -7
  11. data/lib/evented-spec/evented_example/coolio_example.rb +4 -18
  12. data/lib/evented-spec/evented_example/em_example.rb +6 -12
  13. data/lib/evented-spec/{amqp.rb → ext/amqp.rb} +31 -2
  14. data/lib/evented-spec/ext/coolio.rb +14 -0
  15. data/lib/evented-spec/spec_helper.rb +141 -0
  16. data/lib/evented-spec/spec_helper/amqp_helpers.rb +55 -0
  17. data/lib/evented-spec/spec_helper/coolio_helpers.rb +49 -0
  18. data/lib/evented-spec/spec_helper/event_machine_helpers.rb +52 -0
  19. data/spec/em_hooks_spec.rb +3 -3
  20. data/spec/evented-spec/adapters/adapter_seg.rb +156 -0
  21. data/spec/evented-spec/adapters/amqp_spec.rb +58 -0
  22. data/spec/evented-spec/adapters/cool_io_spec.rb +30 -0
  23. data/spec/evented-spec/adapters/em_spec.rb +29 -0
  24. data/spec/{em_defaults_spec.rb → evented-spec/defaults_options_spec.rb} +0 -0
  25. data/spec/{em_metadata_spec.rb → evented-spec/evented_spec_metadata_spec.rb} +0 -0
  26. data/spec/{util_spec.rb → evented-spec/util_spec.rb} +0 -0
  27. data/spec/{failing_rspec_spec.rb → integration/failing_rspec_spec.rb} +1 -1
  28. data/spec/spec_helper.rb +18 -9
  29. data/tasks/spec.rake +7 -2
  30. metadata +44 -24
  31. data/lib/evented-spec/rspec.rb +0 -267
  32. data/spec/cool_io_spec.rb +0 -72
  33. data/spec/rspec_amqp_spec.rb +0 -116
  34. data/spec/rspec_em_spec.rb +0 -53
  35. data/spec/shared_examples.rb +0 -194
@@ -0,0 +1,14 @@
1
+ # Monkey patching some methods into Cool.io to make it more testable
2
+ module Coolio
3
+ class Loop
4
+ # Cool.io provides no means to change the default loop which makes sense in
5
+ # most situations, but not ours.
6
+ def self.default_loop=(event_loop)
7
+ if RUBY_VERSION >= "1.9.0"
8
+ Thread.current.instance_variable_set :@_coolio_loop, event_loop
9
+ else
10
+ @@_coolio_loop = event_loop
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,141 @@
1
+ # You can include one of the following modules into your example groups:
2
+ # EventedSpec::SpecHelper,
3
+ # EventedSpec::AMQPSpec,
4
+ # EventedSpec::EMSpec.
5
+ #
6
+ # EventedSpec::SpecHelper module defines #ampq and #em methods that can be safely used inside
7
+ # your specs (examples) to test code running inside AMQP.start or EM.run loop
8
+ # respectively. Each example is running in a separate event loop,you can control
9
+ # for timeouts either with :spec_timeout option given to #amqp/#em method or setting
10
+ # a default timeout using default_timeout(timeout) macro inside describe/context block.
11
+ #
12
+ # If you include EventedSpec::Spec module into your example group, each example of this group
13
+ # will run inside AMQP.start loop without the need to explicitly call 'amqp'. In order to
14
+ # provide options to AMQP loop, default_options({opts}) macro is defined.
15
+ #
16
+ # Including EventedSpec::EMSpec module into your example group, each example of this group will
17
+ # run inside EM.run loop without the need to explicitly call 'em'.
18
+ #
19
+ # In order to stop AMQP/EM loop, you should call 'done' AFTER you are sure that your
20
+ # example is finished and your expectations executed. For example if you are using
21
+ # subscribe block that tests expectations on messages, 'done' should be probably called
22
+ # at the end of this block.
23
+ #
24
+ module EventedSpec
25
+
26
+ # EventedSpec::SpecHelper module defines #ampq and #em methods that can be safely used inside
27
+ # your specs (examples) to test code running inside AMQP.start or EM.run loop
28
+ # respectively. Each example is running in a separate event loop, you can control
29
+ # for timeouts either with :spec_timeout option given to #amqp/#em/#coolio method or setting
30
+ # a default timeout using default_timeout(timeout) macro inside describe/context block.
31
+ module SpecHelper
32
+ # Error which shows in RSpec log when example does not call #done inside
33
+ # of event loop.
34
+ SpecTimeoutExceededError = Class.new(RuntimeError)
35
+
36
+ # Class methods (macros) for any example groups that includes SpecHelper.
37
+ # You can use these methods as macros inside describe/context block.
38
+ module GroupMethods
39
+ # Returns evented-spec related metadata for particular example group.
40
+ # Metadata is cloned from parent to children, so that children inherit
41
+ # all the options and hooks set in parent example groups
42
+ #
43
+ # @return [Hash] hash with example group metadata
44
+ def evented_spec_metadata
45
+ if @evented_spec_metadata
46
+ @evented_spec_metadata
47
+ else
48
+ @evented_spec_metadata = superclass.evented_spec_metadata rescue {}
49
+ @evented_spec_metadata = EventedSpec::Util.deep_clone(@evented_spec_metadata)
50
+ end
51
+ end # evented_spec_metadata
52
+
53
+ # Sets/retrieves default timeout for running evented specs for this
54
+ # example group and its nested groups.
55
+ #
56
+ # @param [Float] desired timeout for the example group
57
+ # @return [Float]
58
+ def default_timeout(spec_timeout = nil)
59
+ if spec_timeout
60
+ default_options[:spec_timeout] = spec_timeout
61
+ else
62
+ default_options[:spec_timeout] || self.superclass.default_timeout
63
+ end
64
+ end
65
+
66
+ # Sets/retrieves default AMQP.start options for this example group
67
+ # and its nested groups.
68
+ #
69
+ # @param [Hash] context-specific options for helper methods like #amqp, #em, #coolio
70
+ # @return [Hash]
71
+ def default_options(opts = nil)
72
+ evented_spec_metadata[:default_options] ||= {}
73
+ if opts
74
+ evented_spec_metadata[:default_options].merge!(opts)
75
+ else
76
+ evented_spec_metadata[:default_options]
77
+ end
78
+ end
79
+
80
+ # Collection of evented hooks for current example group
81
+ #
82
+ # @return [Hash] hash with hooks
83
+ def evented_spec_hooks
84
+ evented_spec_metadata[:es_hooks] ||= Hash.new
85
+ end
86
+
87
+ # Collection of evented hooks of predefined type for current example group
88
+ #
89
+ # @param [Symbol] hook type
90
+ # @return [Array] hooks
91
+ def evented_spec_hooks_for(type)
92
+ evented_spec_hooks[type] ||= []
93
+ end # evented_spec_hooks_for
94
+ end
95
+
96
+ def self.included(example_group)
97
+ unless example_group.respond_to? :default_timeout
98
+ example_group.extend GroupMethods
99
+ end
100
+ end
101
+
102
+ # Retrieves default options passed in from enclosing example groups
103
+ #
104
+ # @return [Hash] default option for currently running example
105
+ def default_options
106
+ @default_options ||= self.class.default_options.dup rescue {}
107
+ end
108
+
109
+ # Executes an operation after certain delay
110
+ #
111
+ # @param [Float] time to wait before operation
112
+ def delayed(time, &block)
113
+ @evented_example.delayed(time) do
114
+ @example_group_instance.instance_eval(&block)
115
+ end
116
+ end # delayed
117
+
118
+ # Breaks the event loop and finishes the spec. This should be called after
119
+ # you are reasonably sure that your expectations succeeded.
120
+ # Done yields to any given block first, then stops EM event loop.
121
+ # For amqp specs, stops AMQP and cleans up AMQP state.
122
+ #
123
+ # You may pass delay (in seconds) to done. If you do so, please keep in mind
124
+ # that your (default or explicit) spec timeout may fire before your delayed done
125
+ # callback is due, leading to SpecTimeoutExceededError
126
+ #
127
+ # @param [Float] Delay before event loop is stopped
128
+ def done(*args, &block)
129
+ @evented_example.done *args, &block if @evented_example
130
+ end
131
+
132
+ # Manually sets timeout for currently running example. If spec doesn't call
133
+ # #done before timeout, it is marked as failed on timeout.
134
+ #
135
+ # @param [Float] Delay before event loop is stopped with error
136
+ def timeout(*args)
137
+ @evented_example.timeout *args if @evented_example
138
+ end
139
+
140
+ end # module SpecHelper
141
+ end # EventedSpec
@@ -0,0 +1,55 @@
1
+ module EventedSpec
2
+ module SpecHelper
3
+ module AMQPHelpers # naming is v
4
+ module GroupMethods
5
+ # Adds before hook that will run inside AMQP connection (AMQP.start loop)
6
+ # before example starts
7
+ #
8
+ # @param [Symbol] scope for hook (only :each is supported currently)
9
+ # @yield hook block
10
+ def amqp_before(scope = :each, &block)
11
+ raise ArgumentError, "amqp_before only supports :each scope" unless :each == scope
12
+ evented_spec_hooks_for(:amqp_before) << block
13
+ end
14
+
15
+ # Adds after hook that will run inside AMQP connection (AMQP.start loop)
16
+ # after example finishes
17
+ #
18
+ # @param [Symbol] scope for hook (only :each is supported currently)
19
+ # @yield hook block
20
+ def amqp_after(scope = :each, &block)
21
+ raise ArgumentError, "amqp_after only supports :each scope" unless :each == scope
22
+ evented_spec_hooks_for(:amqp_after).unshift block
23
+ end
24
+ end # module GroupMethods
25
+
26
+ module ExampleMethods
27
+ # Yields to a given block inside EM.run and AMQP.start loops.
28
+ #
29
+ # @param [Hash] options for amqp connection initialization
30
+ # @option opts [String] :user ('guest') Username as defined by the AMQP server.
31
+ # @option opts [String] :pass ('guest') Password as defined by the AMQP server.
32
+ # @option opts [String] :vhost ('/') Virtual host as defined by the AMQP server.
33
+ # @option opts [Numeric] :timeout (nil) *Connection* timeout, measured in seconds.
34
+ # @option opts [Boolean] :logging (false) Toggle the extremely verbose AMQP logging.
35
+ # @option opts [Numeric] :spec_timeout (nil) Amount of time before spec is stopped by timeout
36
+ # @yield block to execute after amqp connects
37
+ def amqp(opts = {}, &block)
38
+ opts = default_options.merge opts
39
+ @evented_example = AMQPExample.new(opts, self, &block)
40
+ @evented_example.run
41
+ end
42
+ end # module ExampleMethods
43
+ end # module AMQP
44
+ end # module SpecHelper
45
+ end # module EventedSpec
46
+
47
+ module EventedSpec
48
+ module SpecHelper
49
+ module GroupMethods
50
+ include EventedSpec::SpecHelper::AMQPHelpers::GroupMethods
51
+ end # module GroupMethods
52
+
53
+ include EventedSpec::SpecHelper::AMQPHelpers::ExampleMethods
54
+ end # module SpecHelper
55
+ end # module EventedSpec
@@ -0,0 +1,49 @@
1
+ module EventedSpec
2
+ module SpecHelper
3
+ module CoolioHelpers
4
+ module GroupHelpers
5
+ # Adds before hook that will run inside coolio event loop before example starts.
6
+ #
7
+ # @param [Symbol] scope for hook (only :each is supported currently)
8
+ # @yield hook block
9
+ def coolio_before(scope = :each, &block)
10
+ raise ArgumentError, "coolio_before only supports :each scope" unless :each == scope
11
+ evented_spec_hooks_for(:coolio_before) << block
12
+ end
13
+
14
+ # Adds after hook that will run inside coolio event loop after example finishes.
15
+ #
16
+ # @param [Symbol] scope for hook (only :each is supported currently)
17
+ # @yield hook block
18
+ def coolio_after(scope = :each, &block)
19
+ raise ArgumentError, "coolio_after only supports :each scope" unless :each == scope
20
+ evented_spec_hooks_for(:coolio_after).unshift block
21
+ end
22
+ end # module GroupHelpers
23
+
24
+ module ExampleHelpers
25
+ # Yields to block inside cool.io loop, :spec_timeout option (in seconds) is used to
26
+ # force spec to timeout if something goes wrong and EM/AMQP loop hangs for some
27
+ # reason.
28
+ #
29
+ # @param [Hash] options for cool.io
30
+ # @param opts [Numeric] :spec_timeout (nil) Amount of time before spec is stopped by timeout
31
+ # @yield block to execute after cool.io loop starts
32
+ def coolio(opts = {}, &block)
33
+ opts = default_options.merge opts
34
+ @evented_example = CoolioExample.new(opts, self, &block)
35
+ @evented_example.run
36
+ end
37
+ end # module ExampleHelpers
38
+ end # module CoolioHelpers
39
+ end # module SpecHelper
40
+ end # module EventedSpec
41
+
42
+ module EventedSpec
43
+ module SpecHelper
44
+ module GroupMethods
45
+ include EventedSpec::SpecHelper::CoolioHelpers::GroupHelpers
46
+ end # module GroupHelpers
47
+ include EventedSpec::SpecHelper::CoolioHelpers::ExampleHelpers
48
+ end # module SpecHelper
49
+ end # module EventedSpec
@@ -0,0 +1,52 @@
1
+ module EventedSpec
2
+ module SpecHelper
3
+ module EventMachineHelpers
4
+ module GroupMethods
5
+ # Adds before hook that will run inside EM event loop before example starts.
6
+ #
7
+ # @param [Symbol] scope for hook (only :each is supported currently)
8
+ # @yield hook block
9
+ def em_before(scope = :each, &block)
10
+ raise ArgumentError, "em_before only supports :each scope" unless :each == scope
11
+ evented_spec_hooks_for(:em_before) << block
12
+ end
13
+
14
+ # Adds after hook that will run inside EM event loop after example finishes.
15
+ #
16
+ # @param [Symbol] scope for hook (only :each is supported currently)
17
+ # @yield hook block
18
+ def em_after(scope = :each, &block)
19
+ raise ArgumentError, "em_after only supports :each scope" unless :each == scope
20
+ evented_spec_hooks_for(:em_after).unshift block
21
+ end
22
+ end # module GroupMethods
23
+
24
+ module ExampleMethods
25
+ # Yields to block inside EM loop, :spec_timeout option (in seconds) is used to
26
+ # force spec to timeout if something goes wrong and EM/AMQP loop hangs for some
27
+ # reason.
28
+ #
29
+ # For compatibility with EM-Spec API, em method accepts either options Hash
30
+ # or numeric timeout in seconds.
31
+ #
32
+ # @param [Hash] options for eventmachine
33
+ # @param opts [Numeric] :spec_timeout (nil) Amount of time before spec is stopped by timeout
34
+ # @yield block to execute after eventmachine loop starts
35
+ def em(opts = {}, &block)
36
+ opts = default_options.merge(opts.is_a?(Hash) ? opts : { :spec_timeout => opts })
37
+ @evented_example = EMExample.new(opts, self, &block)
38
+ @evented_example.run
39
+ end
40
+ end # module ExampleMethods
41
+ end # module EventMachine
42
+ end # module SpecHelper
43
+ end # module EventedSpec
44
+
45
+ module EventedSpec
46
+ module SpecHelper
47
+ module GroupMethods
48
+ include EventedSpec::SpecHelper::EventMachineHelpers::GroupMethods
49
+ end # module GroupMethods
50
+ include EventedSpec::SpecHelper::EventMachineHelpers::ExampleMethods
51
+ end # module SpecHelper
52
+ end # module EventedSpec
@@ -43,7 +43,7 @@ shared_examples_for 'hooked em specs' do
43
43
  # Expectation is set in after{} hook
44
44
  em do
45
45
  expect { :this.should == :fail
46
- }.to raise_error RSPEC::Expectations::ExpectationNotMetError
46
+ }.to raise_error RSpec::Expectations::ExpectationNotMetError
47
47
  done
48
48
  end
49
49
  end
@@ -74,7 +74,7 @@ shared_examples_for 'hooked amqp specs' do
74
74
  # Expectation is set in after{} hook
75
75
  amqp do
76
76
  expect { :this.should == :fail
77
- }.to raise_error RSPEC::Expectations::ExpectationNotMetError
77
+ }.to raise_error RSpec::Expectations::ExpectationNotMetError
78
78
  done
79
79
  end
80
80
  end
@@ -108,7 +108,7 @@ describe EventedSpec::SpecHelper, ".em_before/.em_after" do
108
108
 
109
109
  it 'should execute em_after if RSpec expectation fails' do
110
110
  expect { :this.should == :fail
111
- }.to raise_error RSPEC::Expectations::ExpectationNotMetError
111
+ }.to raise_error RSpec::Expectations::ExpectationNotMetError
112
112
  end
113
113
  end # context 'for non-evented specs'
114
114
 
@@ -0,0 +1,156 @@
1
+ # Our assumptions with this seg:
2
+ # - let(:prefix) is defined (e.g. 'coolio_')
3
+ # - let(:method_name) is defined (e.g. 'coolio')
4
+ # - #{prefix}running? method is defined in example group, it is true inside
5
+ # #{method_name} call block, and false outside of it
6
+ #
7
+ shared_examples_for "EventedSpec adapter" do
8
+ # Unfortunately, I know no other way to extract variables from let(...)
9
+ prefix = self.new.prefix
10
+ method_name = self.new.method_name
11
+
12
+ def loop_running?
13
+ send("#{prefix}running?")
14
+ end
15
+
16
+ def loop(*args)
17
+ send(method_name, *args) do
18
+ yield
19
+ end
20
+ end
21
+
22
+ before(:each) { loop_running?.should be_false }
23
+ after(:each) { loop_running?.should be_false }
24
+
25
+ describe "sanity check:" do
26
+ it "we should not be in #{method_name} loop unless explicitly asked" do
27
+ loop_running?.should be_false
28
+ end
29
+ end
30
+
31
+ describe "#{method_name}" do
32
+ it "should execute given block in the right scope" do
33
+ @variable = 1
34
+ loop do
35
+ @variable.should == 1
36
+ @variable = true
37
+ done
38
+ end
39
+ @variable.should == true
40
+ end
41
+
42
+ it "should start default event loop and give control" do
43
+ loop do
44
+ loop_running?.should be_true
45
+ done
46
+ end
47
+ end
48
+
49
+ it "should stop the event loop afterwards" do
50
+ loop do
51
+ done
52
+ end
53
+ loop_running?.should be_false
54
+ end
55
+
56
+ it "should raise SpecTimeoutExceededError when #done is not issued" do
57
+ expect {
58
+ loop do
59
+ end
60
+ }.to raise_error(EventedSpec::SpecHelper::SpecTimeoutExceededError)
61
+ end
62
+
63
+ it "should propagate mismatched rspec expectations" do
64
+ expect {
65
+ loop do
66
+ :fail.should == :win
67
+ end
68
+ }.to raise_error(RSpec::Expectations::ExpectationNotMetError)
69
+ end
70
+ end
71
+
72
+
73
+ describe "#done" do
74
+ it "should execute given block" do
75
+ loop do
76
+ done(0.05) do
77
+ @variable = true
78
+ end
79
+ end
80
+ @variable.should be_true
81
+ end
82
+
83
+ it "should cancel timeout" do
84
+ expect {
85
+ loop do
86
+ done(0.2)
87
+ end
88
+ }.to_not raise_error
89
+ end
90
+ end
91
+
92
+ describe "hooks" do
93
+ context "before" do
94
+ send("#{prefix}before") do
95
+ @called_back = true
96
+ loop_running?.should be_true
97
+ end
98
+
99
+ it "should run before example starts" do
100
+ loop do
101
+ @called_back.should be_true
102
+ done
103
+ end
104
+ end
105
+ end
106
+
107
+ context "after" do
108
+ send("#{prefix}after") do
109
+ @called_back = true
110
+ loop_running?.should be_true
111
+ end
112
+
113
+ it "should run after example finishes" do
114
+ loop do
115
+ @called_back.should be_false
116
+ done
117
+ end
118
+ @called_back.should be_true
119
+ end
120
+ end
121
+ end
122
+
123
+ describe "#delayed" do
124
+ it "should run an operation after certain amount of time" do
125
+ loop(:spec_timeout => 3) do
126
+ time = Time.now
127
+ delayed(0.5) do
128
+ (Time.now - time).should be_within(0.3).of(0.5)
129
+ done
130
+ end
131
+ end
132
+ end
133
+
134
+ it "should preserve context" do
135
+ loop(:spec_timeout => 3) do
136
+ @instance_var = true
137
+ delayed(0.1) do
138
+ @instance_var.should be_true
139
+ done
140
+ end
141
+ end
142
+ end
143
+ end
144
+
145
+
146
+ describe "error handling" do
147
+ it "bubbles failing expectations up to Rspec" do
148
+ expect {
149
+ loop do
150
+ :this.should == :fail
151
+ end
152
+ }.to raise_error(RSpec::Expectations::ExpectationNotMetError)
153
+ loop_running?.should be_false
154
+ end
155
+ end
156
+ end