

# Redefine MockExpectationError to print out a lot of stuff when it happens
# So that if it's exceptions are blocked by the code you still get a trace

module Spec

  # open the execute method to redefine the example execute in order to enable exposing
  # expectation errors that may have been intercepted by rescue Exception in the code unde test
  module Example
    module ExampleMethods
      alias_method :fansnap_original_execute, :execute
      @@fansnap_expectation_errors = [] unless defined?(@@fansnap_expectation_errors)
      @@fansnap_reraise_enabled = false unless defined?(@@fansnap_reraise_enabled)

      def execute(run_options, instance_variables)
        fansnap_clear_expectation_error_list  #monkey patch
        run_options.reporter.example_started(@_proxy)
        set_instance_variables_from_hash(instance_variables)

        execution_error = nil
        Timeout.timeout(run_options.timeout) do
          begin
            before_each_example
            instance_eval(&@_implementation)
          rescue Exception => e
            execution_error ||= e
          end
          begin
            after_each_example
          rescue Exception => e
            execution_error ||= e
          end
        end

        execution_error ||= fansnap_first_expectation_error #monkey patch

        run_options.reporter.example_finished(@_proxy.update(description), execution_error)
        success = execution_error.nil? || ExamplePendingError === execution_error
      end

      def fansnap_clear_expectation_error_list
        @@fansnap_expectation_errors = []
      end

      def fansnap_first_expectation_error
        @@fansnap_expectation_errors[0] if @@fansnap_reraise_enabled && @@fansnap_expectation_errors.size > 0
      end

      def self.fansnap_add_expectation_error(err)
        @@fansnap_expectation_errors << err
      end

      def self.fansnap_hidden_expectation_errors_enabled?
        @@fansnap_reraise_enabled
      end

      def expose_hidden_expectation_errors
        @original_state_of_expectation_errors = @@fansnap_reraise_enabled
        @@fansnap_reraise_enabled = true
      end

      def do_not_expose_hidden_expectation_errors
        @original_state_of_expectation_errors = @@fansnap_reraise_enabled
        @@fansnap_reraise_enabled = false
      end

      def restore_hidden_expectation_errors_state
        @@fansnap_reraise_enabled = @original_state_of_expectation_errors
      end
    end
  end

  # Open the MockExpectationError to enable the following behaviors:
  #  * Print details of mock expectation errors (useful when they are not exposed)
  #  * Interact with the example to let it know that an expectation error occured, even if this exception
  #    is digested by the code and does not float back up to the example.
  module Mocks
    class MockExpectationError
      alias_method(:fansnap_original_init, :initialize) unless defined?(FANSNAP_ORIGINAL_INIT)
      FANSNAP_ORIGINAL_INIT = "aliased" unless defined?(FANSNAP_ORIGINAL_INIT)

      def initialize(*args)
        fansnap_original_init(*args)
        should_print_env = ENV["PRINT_EXPECTATION_ERRORS"] || "false"
        should_print_details_env = ENV["PRINT_EXPECTATION_ERROR_DETAILS"] || "false"
        should_print_details = (should_print_details_env.strip.downcase=="true")

        if should_print_env.strip.downcase=="true" || should_print_details
          puts "==MockExpectationError==  "*5
          puts self.message

          context_lines = 2
          context_lines = 10 if should_print_details

          puts "\nspec context:"
          callstack.each do
          |frame|
            if frame[0]=~/_spec\.rb/
              puts "  #{frame[0]}:#{frame[1]}"
              puts "FILE: #{frame[0]}"
              begin
                filename = frame[0]
                line_no = frame[1]
                lines = File.readlines(filename)
                if lines
                  start_line = [0,line_no-context_lines-1].max
                  end_line = [lines.size-1, line_no+context_lines].min
                  start_line.upto(line_no-2) {|l| puts "    "+lines[l]}
                  puts "==> "+lines[line_no-1]
                  line_no.upto(end_line) {|l| puts "    "+lines[l]}
                end
              rescue Exception
              end
            end
          end

          if should_print_details
            puts "\n\n====================\nfull stack trace:\n"
            callstack[1..-1].each do
            |frame|
              puts "  #{frame[0]}:#{frame[1]}"
              puts "--"+"^"*(frame[0].length)+"-"*(80-frame[0].length) if frame[0]=~/_spec\.rb/
            end
          end
        end

        Spec::Example::ExampleMethods.fansnap_add_expectation_error(self)
      end
    end
  end
end

