Skip to content
This repository
tree: 34cb8f9d85
Fetching contributors…

Cannot retrieve contributors at this time

file 251 lines (217 sloc) 8.12 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
module RSpec
  module Core
    # Wrapper for an instance of a subclass of [ExampleGroup](ExampleGroup). An
    # instance of `Example` is returned by the
    # [example](ExampleGroup#example-instance_method) method available in
    # examples, [before](Hooks#before-instance_method) and
    # [after](Hooks#after-instance_method) hooks, and yielded to
    # [around](Hooks#around-instance_method) hooks.
    # @see ExampleGroup
    class Example
      # @private
      #
      # Used to define methods that delegate to this example's metadata
      def self.delegate_to_metadata(*keys)
        keys.each do |key|
          define_method(key) {@metadata[key]}
        end
      end

      delegate_to_metadata :full_description, :execution_result, :file_path, :pending, :location

      # Returns the string submitted to `example` or its aliases (e.g.
      # `specify`, `it`, etc). If no string is submitted (e.g. `it { should
      # do_something }`) it returns the message generated by the matcher if
      # there is one, otherwise returns a message including the location of the
      # example.
      def description
        metadata[:description].to_s.empty? ? "example at #{location}" : metadata[:description]
      end

      # @attr_reader
      #
      # Returns the first exception raised in the context of running this
      # example (nil if no exception is raised)
      attr_reader :exception

      # @attr_reader
      #
      # Returns the metadata object associated with this example.
      attr_reader :metadata

      # @attr_reader
      # @private
      #
      # Returns the example_group_instance that provides the context for
      # running this example.
      attr_reader :example_group_instance

      # Creates a new instance of Example.
      # @param example_group_class the subclass of ExampleGroup in which this Example is declared
      # @param description the String passed to the `it` method (or alias)
      # @param metadata additional args passed to `it` to be used as metadata
      # @param example_block the block of code that represents the example
      def initialize(example_group_class, description, metadata, example_block=nil)
        @example_group_class, @options, @example_block = example_group_class, metadata, example_block
        @metadata = @example_group_class.metadata.for_example(description, metadata)
        @exception = nil
        @pending_declared_in_example = false
      end

      # @deprecated access options via metadata instead
      def options
        @options
      end

      # Returns the example group class that provides the context for running
      # this example.
      def example_group
        @example_group_class
      end

      alias_method :pending?, :pending

      # @api private
      # @param example_group_instance the instance of an ExampleGroup subclass
      # instance_evals the block submitted to the constructor in the
      # context of the instance of ExampleGroup
      def run(example_group_instance, reporter)
        @example_group_instance = example_group_instance
        @example_group_instance.example = self

        start(reporter)

        begin
          unless pending
            with_around_hooks do
              begin
                run_before_each
                @example_group_instance.instance_eval(&@example_block)
              rescue Pending::PendingDeclaredInExample => e
                @pending_declared_in_example = e.message
              rescue Exception => e
                set_exception(e)
              ensure
                run_after_each
              end
            end
          end
        rescue Exception => e
          set_exception(e)
        ensure
          @example_group_instance.instance_variables.each do |ivar|
            @example_group_instance.instance_variable_set(ivar, nil)
          end
          @example_group_instance = nil

          begin
            assign_auto_description
          rescue Exception => e
            set_exception(e)
          end
        end

        finish(reporter)
      end

      # @private
      #
      # Wraps the example block in a Proc so it can invoked using `run` or
      # `call` in [around](../Hooks#around-instance_method) hooks.
      def self.procsy(metadata, &proc)
        Proc.new(&proc).extend(Procsy).with(metadata)
      end

      # @private
      module Procsy
        attr_reader :metadata

        # @private
        # @param [Proc]
        # Adds a `run` method to the extended Proc, allowing it to be invoked
        # in an [around](../Hooks#around-instance_method) hook using either
        # `run` or `call`.
        def self.extended(object)
          def object.run; call; end
        end

        # @private
        def with(metadata)
          @metadata = metadata
          self
        end
      end

      # @private
      def any_apply?(filters)
        metadata.any_apply?(filters)
      end

      # @private
      def all_apply?(filters)
        @metadata.all_apply?(filters) || @example_group_class.all_apply?(filters)
      end

      # @private
      def around_hooks
        @around_hooks ||= example_group.around_hooks_for(self)
      end

      # @private
      #
      # Used internally to set an exception in an after hook, which
      # captures the exception but doesn't raise it.
      def set_exception(exception)
        @exception ||= exception
      end

      # @private
      #
      # Used internally to set an exception and fail without actually executing
      # the example when an exception is raised in before(:all).
      def fail_with_exception(reporter, exception)
        start(reporter)
        set_exception(exception)
        finish(reporter)
      end

    private

      def with_around_hooks(&block)
        if around_hooks.empty?
          yield
        else
          @example_group_class.run_around_each_hooks(self, Example.procsy(metadata, &block)).call
        end
      end

      def start(reporter)
        reporter.example_started(self)
        record :started_at => Time.now
      end

      # @private
      module NotPendingExampleFixed
        def pending_fixed?; false; end
      end

      def finish(reporter)
        if @exception
          @exception.extend(NotPendingExampleFixed) unless @exception.respond_to?(:pending_fixed?)
          record_finished 'failed', :exception => @exception
          reporter.example_failed self
          false
        elsif @pending_declared_in_example
          record_finished 'pending', :pending_message => @pending_declared_in_example
          reporter.example_pending self
          true
        elsif pending
          record_finished 'pending', :pending_message => String === pending ? pending : Pending::NO_REASON_GIVEN
          reporter.example_pending self
          true
        else
          record_finished 'passed'
          reporter.example_passed self
          true
        end
      end

      def record_finished(status, results={})
        finished_at = Time.now
        record results.merge(:status => status, :finished_at => finished_at, :run_time => (finished_at - execution_result[:started_at]))
      end

      def run_before_each
        @example_group_instance.setup_mocks_for_rspec if @example_group_instance.respond_to?(:setup_mocks_for_rspec)
        @example_group_class.run_before_each_hooks(self)
      end

      def run_after_each
        @example_group_class.run_after_each_hooks(self)
        @example_group_instance.verify_mocks_for_rspec if @example_group_instance.respond_to?(:verify_mocks_for_rspec)
      ensure
        @example_group_instance.teardown_mocks_for_rspec if @example_group_instance.respond_to?(:teardown_mocks_for_rspec)
      end

      def assign_auto_description
        if metadata[:description].empty? and !pending?
          if RSpec.configuration.expecting_with_rspec?
            metadata[:description] = RSpec::Matchers.generated_description
          end
        end
        RSpec::Matchers.clear_generated_description
      end

      def record(results={})
        execution_result.update(results)
      end

    end
  end
end
Something went wrong with that request. Please try again.