lib/amqp-spec/rspec.rb in amqp-spec-0.2.1 vs lib/amqp-spec/rspec.rb in amqp-spec-0.2.3

- old
+ new

@@ -1,97 +1,85 @@ require 'fiber' unless Fiber.respond_to?(:current) -require 'mq' +require 'amqp-spec/amqp' # You can include one of the following modules into your example groups: -# AMQP::SpecHelper -# AMQP::Spec +# AMQP::SpecHelper, +# AMQP::Spec, +# AMQP::EMSpec. # -# AMQP::SpecHelper module defines 'ampq' method that can be safely used inside your specs(examples) +# 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). +# 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. # # 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. # -# In order to stop AMQP loop, you should call 'done' AFTER you are sure that your example is finished. +# 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. # -# 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 module AMQP - - # Initializes new AMQP client/connection without starting another EM loop - def self.start_connection(opts={}, &block) -# puts "!!!!!!!!! Existing connection: #{@conn}" if @conn - @conn = connect opts - @conn.callback(&block) if block - end - - # Closes AMQP connection and raises optional exception AFTER the AMQP connection is 100% closed - def self.stop_connection - if AMQP.conn and not AMQP.closing -# MQ.reset ? - @closing = true - @conn.close { - yield if block_given? - cleanup_state - } - end - end - - def self.cleanup_state -# MQ.reset ? - Thread.current[:mq] = nil - Thread.current[:mq_id] = nil - @conn = nil - @closing = false - end - + # 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. + # + # 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) - def self.included(example_group) + # Class methods (macros) for example group that includes SpecHelper + # + 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. + # + def metadata + @metadata ||= superclass.metadata.dup rescue {} + end + end - extended_class = defined?(RSpec) ? example_group : ::Spec::Example::ExampleGroup - unless extended_class.respond_to? :default_timeout - extended_class.instance_exec do - if defined?(RSpec) - metadata[:em_default_options] = {} - metadata[:em_default_timeout] = nil + # 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] + end - def self.default_timeout(spec_timeout=nil) - metadata[:em_default_timeout] = spec_timeout if spec_timeout - metadata[:em_default_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] + end + end - def self.default_options(opts=nil) - metadata[:em_default_options] = opts if opts - metadata[:em_default_options] - end - else - @@_em_default_options = {} - @@_em_default_timeout = nil - - def self.default_timeout(spec_timeout=nil) - @@_em_default_timeout = spec_timeout if spec_timeout - @@_em_default_timeout - end - - def self.default_options(opts=nil) - @@_em_default_options = opts if opts - @@_em_default_options - end - end - end + 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 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. @@ -105,41 +93,40 @@ 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 + spec_timeout = opts.delete(:spec_timeout) || self.class.default_timeout timeout(spec_timeout) if spec_timeout - @_em_spec_fiber = Fiber.new do + @_em_spec_fiber = Fiber.new do begin AMQP.start_connection opts, &block rescue Exception => @_em_spec_exception -# p "inner", @_em_spec_exception done end Fiber.yield end @_em_spec_fiber.resume end rescue Exception => outer_spec_exception -# p "outer", outer_spec_exception unless outer_spec_exception.is_a? SpecTimeoutExceededError # Make sure AMQP state is cleaned even after Rspec failures AMQP.cleanup_state raise outer_spec_exception end 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. + # 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 + @_em_spec_fiber = Fiber.new do begin block.call rescue Exception => @_em_spec_exception done end @@ -148,11 +135,12 @@ @_em_spec_fiber.resume end end - # Sets timeout for current spec + # 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 @@ -165,10 +153,11 @@ # 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 @@ -189,35 +178,50 @@ else done_proc.call end end + # Retrieves metadata passed in from enclosing example groups + # + def metadata + @metadata ||= self.class.metadata.dup rescue {} + end + private # 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. + # module Spec - def self.included(cls) - cls.send(:include, SpecHelper) + def self.included(example_group) + example_group.send(:include, SpecHelper) end 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'. + # module EMSpec - def self.included(cls) - cls.send(:include, SpecHelper) + def self.included(example_group) + example_group.send(:include, SpecHelper) end def instance_eval(&block) em do super(&block)