lib/amqp-spec/rspec.rb in amqp-spec-0.2.7 vs lib/amqp-spec/rspec.rb in amqp-spec-0.3.0

- old
+ new

@@ -1,232 +1,185 @@ -require 'fiber' unless Fiber.respond_to?(:current) require 'amqp-spec/amqp' +require 'amqp-spec/evented_example' # You can include one of the following modules into your example groups: # AMQP::SpecHelper, # AMQP::Spec, # AMQP::EMSpec. # -# AMQP::SpecHelper module defines #ampq method that can be safely used inside your specs(examples) -# to test expectations inside running AMQP.start loop. Each loop is running in a separate Fiber, -# and you can control for timeouts using either :spec_timeout option given to #amqp method, -# or setting default timeout with class method default_timeout(timeout). In addition to #amqp -# method, you can use #em method - it creates plain EM.run loop without starting AMQP. +# AMQP::SpecHelper module defines #ampq and #em methods that can be safely used inside +# your specs (examples) to test code running inside AMQP.start or EM.run loop +# respectively. Each example is running in a separate event loop,you can control +# for timeouts either with :spec_timeout option given to #amqp/#em method or setting +# a default timeout using default_timeout(timeout) macro inside describe/context block. # -# If you include AMQP::Spec module into your example group, each example of this group will run -# inside AMQP.start loop without the need to explicitly call 'amqp'. In order to provide options -# to AMQP loop, default_options class method is defined. Remember, when using AMQP::Specs, you -# will have a single set of AMQP.start options for all your examples. +# If you include AMQP::Spec module into your example group, each example of this group +# will run inside AMQP.start loop without the need to explicitly call 'amqp'. In order to +# provide options to AMQP loop, default_options({opts}) macro is defined. # # Including AMQP::EMSpec module into your example group, each example of this group will run # inside EM.run loop without the need to explicitly call 'em'. # -# In order to stop AMQP/EM loop, you should call 'done' AFTER you are sure that your example is finished. -# For example, if you are using subscribe block that tests expectations on messages, 'done' should be -# probably called at the end of this block. +# In order to stop AMQP/EM loop, you should call 'done' AFTER you are sure that your +# example is finished and your expectations executed. For example if you are using +# subscribe block that tests expectations on messages, 'done' should be probably called +# at the end of this block. # module AMQP - # AMQP::SpecHelper module defines #ampq method that can be safely used inside your specs(examples) - # to test expectations inside running AMQP.start loop. Each loop is running in a separate Fiber, - # and you can control for timeouts using either :spec_timeout option given to #amqp method, - # or setting default timeout with class method default_timeout(timeout). In addition to #amqp - # method, you can use #em method - it creates plain EM.run loop without starting AMQP. + + # AMQP::SpecHelper module defines #ampq and #em methods that can be safely used inside + # your specs (examples) to test code running inside AMQP.start or EM.run loop + # respectively. Each example is running in a separate event loop,you can control + # for timeouts either with :spec_timeout option given to #amqp/#em method or setting + # a default timeout using default_timeout(timeout) macro inside describe/context block. # - # TODO: Define 'async' method wrapping async requests and returning results... 'async_loop' too for subscribe block? - # TODO: 'evented_before', 'evented_after' that will be run inside EM before the example - # # noinspection RubyArgCount module SpecHelper SpecTimeoutExceededError = Class.new(RuntimeError) - # Class methods (macros) for example group that includes SpecHelper + # Class methods (macros) for any example groups that includes SpecHelper. + # You can use these methods as macros inside describe/context block. # module GroupMethods - unless respond_to?(:metadata) - # Hacking in metadata into RSpec1 to imitate Rspec2's metadata. - # You can add to metadata Hash to pass options into examples and - # nested groups. + unless respond_to? :metadata + # Hacking in metadata into RSpec1 to imitate Rspec2's metadata. Now you can add + # anything to metadata Hash to pass options into examples and nested groups. # def metadata @metadata ||= superclass.metadata.dup rescue {} end end # Sets/retrieves default timeout for running evented specs for this # example group and its nested groups. # - def default_timeout(spec_timeout=nil) - metadata[:em_default_timeout] = spec_timeout if spec_timeout - metadata[:em_default_timeout] + def default_timeout spec_timeout=nil + metadata[:em_timeout] = spec_timeout if spec_timeout + metadata[:em_timeout] end # Sets/retrieves default AMQP.start options for this example group # and its nested groups. # - def default_options(opts=nil) - metadata[:em_default_options] = opts if opts - metadata[:em_default_options] + def default_options opts=nil + metadata[:em_defaults] = opts if opts + metadata[:em_defaults] end + + # Add before hook that will run inside EM event loop + def em_before scope = :each, &block + raise ArgumentError, "em_before only supports :each scope" unless :each == scope + em_hooks[:before] << block + end + + # Add after hook that will run inside EM event loop + def em_after scope = :each, &block + raise ArgumentError, "em_after only supports :each scope" unless :each == scope + em_hooks[:after] << block + end + + # Collection of evented hooks + def em_hooks + metadata[:em_hooks] ||= {:before => [], :after => []} + end end - def self.included(example_group) + def self.included example_group unless example_group.respond_to? :default_timeout - example_group.extend(GroupMethods) - example_group.metadata[:em_default_options] = {} - example_group.metadata[:em_default_timeout] = nil + example_group.extend GroupMethods + example_group.metadata[:em_defaults] = {} + example_group.metadata[:em_timeout] = nil end end - - # Yields to given block inside EM.run and AMQP.start loops. This method takes any option that is - # also accepted by EventMachine::connect. Also, options for AMQP.start include: - # * :user => String (default ‘guest’) - The username as defined by the AMQP server. - # * :pass => String (default ‘guest’) - The password for the associated :user as defined by the AMQP server. - # * :vhost => String (default ’/’) - The virtual host as defined by the AMQP server. + # Yields to a given block inside EM.run and AMQP.start loops. This method takes + # any option that is accepted by EventMachine::connect. Options for AMQP.start include: + # * :user => String (default ‘guest’) - Username as defined by the AMQP server. + # * :pass => String (default ‘guest’) - Password as defined by the AMQP server. + # * :vhost => String (default ’/’) - Virtual host as defined by the AMQP server. # * :timeout => Numeric (default nil) - *Connection* timeout, measured in seconds. - # * :logging => true | false (default false) - Toggle the extremely verbose AMQP logging. + # * :logging => Bool (default false) - Toggle the extremely verbose AMQP logging. # - # In addition to EM and AMQP options, :spec_timeout option (in seconds) is used to force spec to timeout - # if something goes wrong and EM/AMQP loop hangs for some reason. SpecTimeoutExceededError is raised. + # In addition to EM and AMQP options, :spec_timeout option (in seconds) is used + # to force spec to timeout if something goes wrong and EM/AMQP loop hangs for some + # reason. SpecTimeoutExceededError is raised if it happens. # def amqp opts={}, &block opts = self.class.default_options.merge opts - begin - EM.run do - @_em_spec_with_amqp = true - @_em_spec_exception = nil - spec_timeout = opts.delete(:spec_timeout) || self.class.default_timeout - timeout(spec_timeout) if spec_timeout - @_em_spec_fiber = Fiber.new do - begin - AMQP.start_connection opts, &block - rescue Exception => @_em_spec_exception - done - end - Fiber.yield - end - - @_em_spec_fiber.resume - end - rescue Exception => outer_spec_exception - # Make sure AMQP state is cleaned even after Rspec failures - AMQP.cleanup_state - raise outer_spec_exception - end + opts[:spec_timeout] ||= self.class.default_timeout + @evented_example = AMQPExample.new(opts, self, &block) + @evented_example.run end - # Yields to block inside EM loop, :spec_timeout option (in seconds) is used to force spec to timeout - # if something goes wrong and EM/AMQP loop hangs for some reason. SpecTimeoutExceededError is raised. + # Yields to block inside EM loop, :spec_timeout option (in seconds) is used to + # force spec to timeout if something goes wrong and EM/AMQP loop hangs for some + # reason. SpecTimeoutExceededError is raised if it happens. # - def em(spec_timeout = self.class.default_timeout, &block) - spec_timeout = spec_timeout[:spec_timeout] || self.class.default_timeout if spec_timeout.is_a?(Hash) - EM.run do - @_em_spec_with_amqp = false - @_em_spec_exception = nil - timeout(spec_timeout) if spec_timeout - @_em_spec_fiber = Fiber.new do - begin - block.call - rescue Exception => @_em_spec_exception - done - end - Fiber.yield - end - - @_em_spec_fiber.resume + def em opts = {}, &block + if opts.is_a? Hash + opts[:spec_timeout] ||= self.class.default_timeout + else + opts = {spec_timeout: opts} end + @evented_example = EMExample.new(opts, self, &block) + @evented_example.run end - # Sets timeout for current running example - # - def timeout(spec_timeout) - EM.cancel_timer(@_em_timer) if @_em_timer - @_em_timer = EM.add_timer(spec_timeout) do - @_em_spec_exception = SpecTimeoutExceededError.new - done - end - end - # Breaks the event loop and finishes the spec. This should be called after # you are reasonably sure that your expectations either succeeded or failed. # Done yields to any given block first, then stops EM event loop. # For amqp specs, stops AMQP and cleans up AMQP state. # # You may pass delay (in seconds) to done. If you do so, please keep in mind # that your (default or explicit) spec timeout may fire before your delayed done # callback is due, leading to SpecTimeoutExceededError # - def done(delay=nil) - done_proc = proc do - yield if block_given? - EM.next_tick do - if @_em_spec_with_amqp - if AMQP.conn and not AMQP.closing - AMQP.stop_connection do - finish_em_spec_fiber { AMQP.cleanup_state } - end - else - finish_em_spec_fiber { AMQP.cleanup_state } - end - else - finish_em_spec_fiber - end - end - end - if delay - EM.add_timer delay, &done_proc - else - done_proc.call - end + def done *args, &block + @evented_example.done *args, &block end - # Retrieves metadata passed in from enclosing example groups + # Manually sets timeout for currently running example # - def metadata - @metadata ||= self.class.metadata.dup rescue {} + def timeout *args + @evented_example.timeout *args end - private + end # module SpecHelper - # Stops EM loop, executes optional block, finishes off fiber and raises exception if any - # - def finish_em_spec_fiber - EM.stop_event_loop if EM.reactor_running? - yield if block_given? - @_em_spec_fiber.resume if @_em_spec_fiber.alive? - raise @_em_spec_exception if @_em_spec_exception - end - end - - # If you include AMQP::Spec module into your example group, each example of this group will run - # inside AMQP.start loop without the need to explicitly call 'amqp'. In order to provide options - # to AMQP loop, default_options class method is defined. Remember, when using AMQP::Specs, you - # will have a single set of AMQP.start options for all your examples. + # If you include AMQP::Spec module into your example group, each example of this group + # will run inside AMQP.start loop without the need to explicitly call 'amqp'. In order + # to provide options to AMQP loop, default_options class method is defined. Remember, + # when using AMQP::Specs, you'll have a single set of AMQP.start options for all your + # examples. # module Spec def self.included(example_group) example_group.send(:include, SpecHelper) end + alias instance_eval_without_event_loop instance_eval + def instance_eval(&block) amqp do super(&block) end end end - # Including AMQP::EMSpec module into your example group, each example of this group will run - # inside EM.run loop without the need to explicitly call 'em'. + # Including AMQP::EMSpec module into your example group, each example of this group + # will run inside EM.run loop without the need to explicitly call 'em'. # module EMSpec def self.included(example_group) example_group.send(:include, SpecHelper) end + alias instance_eval_without_event_loop instance_eval + def instance_eval(&block) em do super(&block) end end end -end +end \ No newline at end of file