Class: RSpec::CallerFilter

Inherits:
Object
  • Object
show all
Defined in:
lib/rspec/support/caller_filter.rb

Overview

Consistent implementation for “cleaning” the caller method to strip out non-rspec lines. This enables errors to be reported at the call site in the code using the library, which is far more useful than the particular internal method that raised an error.

Constant Summary collapse

RSPEC_LIBS =
%w[
  core
  mocks
  expectations
  support
  matchers
  rails
]
ADDITIONAL_TOP_LEVEL_FILES =
%w[ autorun ]
LIB_REGEX =
%r{/lib/rspec/(#{(RSPEC_LIBS + ADDITIONAL_TOP_LEVEL_FILES).join('|')})(\.rb|/)}
IGNORE_REGEX =

rubygems/core_ext/kernel_require.rb isn’t actually part of rspec (obviously) but we want it ignored when we are looking for the first meaningful line of the backtrace outside of RSpec. It can show up in the backtrace as the immediate first caller when ‘CallerFilter.first_non_rspec_line` is called from the top level of a required file, but it depends on if rubygems is loaded or not. We don’t want to have to deal with this complexity in our ‘RSpec.deprecate` calls, so we ignore it here.

Regexp.union(LIB_REGEX, "rubygems/core_ext/kernel_require.rb", "<internal:", %r{/lib/ruby/[^/]+/bundled_gems\.rb})

Class Method Summary collapse

Class Method Details

.first_non_rspec_lineObject

Earlier rubies do not support the two argument form of ‘caller`. This fallback is logically the same, but slower.



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
# File 'lib/rspec/support/caller_filter.rb', line 49

def self.first_non_rspec_line(skip_frames=3, increment=5)
  # Why a default `skip_frames` of 3?
  # By the time `caller_locations` is called below, the first 3 frames are:
  #   lib/rspec/support/caller_filter.rb:63:in `block in first_non_rspec_line'
  #   lib/rspec/support/caller_filter.rb:62:in `loop'
  #   lib/rspec/support/caller_filter.rb:62:in `first_non_rspec_line'

  # `caller` is an expensive method that scales linearly with the size of
  # the stack. The performance hit for fetching it in chunks is small,
  # and since the target line is probably near the top of the stack, the
  # overall improvement of a chunked search like this is significant.
  #
  # See benchmarks/caller.rb for measurements.

  # The default increment of 5 for this method are mostly arbitrary, but
  # is chosen to give good performance on the common case of creating a double.

  loop do
    stack = caller_locations(skip_frames, increment)
    raise "No non-lib lines in stack" unless stack

    line = stack.find { |l| l.path !~ IGNORE_REGEX }
    return line.to_s if line

    skip_frames += increment
    increment *= 2 # The choice of two here is arbitrary.
  end
end