class Lopata::Observers::BacktraceFormatter

@private Based on RSpec::Core::BacktraceFormatter

Provides ability to format backtrace and find source code by file and line number.

Attributes

exclusion_patterns[RW]

@private

inclusion_patterns[RW]

@private

Public Class Methods

new() click to toggle source
# File lib/lopata/observers/backtrace_formatter.rb, line 11
def initialize
  patterns = %w[ /lib\d*/ruby/ bin/ exe/lopata /lib/bundler/ /exe/bundle /\.rvm/ /rvm/ ]
  patterns.map! { |s| Regexp.new(s.gsub("/", File::SEPARATOR)) }

  @exclusion_patterns = [Regexp.union(*patterns)]
  @inclusion_patterns = []

  inclusion_patterns << Regexp.new(Dir.getwd)
end

Public Instance Methods

backtrace_line(line) click to toggle source
# File lib/lopata/observers/backtrace_formatter.rb, line 65
def backtrace_line(line)
  relative_path(line) unless exclude?(line)
end
error_message(exception, include_backtrace: false) click to toggle source

Extracts error message from excetion

@param exception [Exception] @param include_backtrace [Boolean] whether to add formatted backtrace to output @return [String] error message from excetion, incuding source code line.

# File lib/lopata/observers/backtrace_formatter.rb, line 45
def error_message(exception, include_backtrace: false)
  backtrace = format(exception.backtrace)
  source_line = extract_source_line(backtrace.first)
  msg = ''
  msg << "\n#{source_line}\n" if source_line
  msg << "#{exception.class.name}: " unless exception.class.name =~ /RSpec/
  msg << exception.message if exception.message
  msg << "\n#{backtrace.join("\n")}\n" if include_backtrace
  msg
end
exclude?(line) click to toggle source
# File lib/lopata/observers/backtrace_formatter.rb, line 69
def exclude?(line)
  matches?(exclusion_patterns, line) && !matches?(inclusion_patterns, line)
end
extract_source_line(backtrace_line) click to toggle source
# File lib/lopata/observers/backtrace_formatter.rb, line 56
def extract_source_line(backtrace_line)
  file_and_line_number = backtrace_line.match(/(.+?):(\d+)(|:\d+)/)
  return nil unless file_and_line_number
  file_path, line_number = file_and_line_number[1..2]
  return nil unless File.exist?(file_path)
  lines = File.read(file_path).split("\n")
  lines[line_number.to_i - 1]
end
format(backtrace) click to toggle source

Filter backtrace.

@param backtrace [Array<String>] exception backtrace @return [Array<String>] backtrace lines except ruby libraries, gems and executable files.

# File lib/lopata/observers/backtrace_formatter.rb, line 25
def format(backtrace)
  return [] unless backtrace
  return backtrace if backtrace.empty?

  backtrace.map { |l| backtrace_line(l) }.compact.
    tap do |filtered|
      if filtered.empty?
        filtered.concat backtrace
        filtered << ""
        filtered << "  Showing full backtrace because every line was filtered out."
      end
    end
end

Private Instance Methods

matches?(patterns, line) click to toggle source
# File lib/lopata/observers/backtrace_formatter.rb, line 75
def matches?(patterns, line)
  patterns.any? { |p| line =~ p }
end
relative_path(line) click to toggle source

@param line [String] current code line @return [String] relative path to line

# File lib/lopata/observers/backtrace_formatter.rb, line 93
def relative_path(line)
  line = line.sub(relative_path_regex, "\\1.\\2".freeze)
  line = line.sub(/\A([^:]+:\d+)$/, '\\1'.freeze)
  return nil if line == '-e:1'.freeze
  line
rescue SecurityError
  nil
end
relative_path_regex() click to toggle source

Matches strings either at the beginning of the input or prefixed with a whitespace, containing the current path, either postfixed with the separator, or at the end of the string. Match groups are the character before and the character after the string if any.

rubular.com/r/fT0gmX6VJX rubular.com/r/duOrD4i3wb rubular.com/r/sbAMHFrOx1

# File lib/lopata/observers/backtrace_formatter.rb, line 87
def relative_path_regex
  @relative_path_regex ||= /(\A|\s)#{File.expand_path('.')}(#{File::SEPARATOR}|\s|\Z)/
end