class PowerAssert::Context

Constants

Value

Public Class Methods

new(base_caller_length) click to toggle source
# File power_assert-2.0.1/lib/power_assert/context.rb, line 10
def initialize(base_caller_length)
  @fired = false
  @target_thread = Thread.current
  method_id_set = nil
  @return_values = []
  @trace_return = TracePoint.new(:return, :c_return) do |tp|
    unless method_id_set
      next unless Thread.current == @target_thread
      method_id_set = @parser.method_id_set
    end
    method_id = tp.callee_id
    next if ! method_id_set[method_id]
    next if tp.event == :c_return and
            not (@parser.lineno == tp.lineno and @parser.path == tp.path)
    locs = PowerAssert.app_caller_locations
    diff = locs.length - base_caller_length
    if (tp.event == :c_return && diff == 1 || tp.event == :return && diff <= 2) and Thread.current == @target_thread
      idx = -(base_caller_length + 1)
      if @parser.path == locs[idx].path and @parser.lineno == locs[idx].lineno
        val = PowerAssert.configuration.lazy_inspection ?
          tp.return_value :
          InspectedValue.new(SafeInspectable.new(tp.return_value).inspect)
        @return_values << Value[method_id.to_s, val, locs[idx].lineno, nil]
      end
    end
  rescue Exception => e
    warn "power_assert: [BUG] Failed to trace: #{e.class}: #{e.message}"
    if e.respond_to?(:full_message)
      warn e.full_message.gsub(/^/, 'power_assert:     ')
    end
  end
end

Public Instance Methods

message() click to toggle source
# File power_assert-2.0.1/lib/power_assert/context.rb, line 43
def message
  raise 'call #yield or #enable at first' unless fired?
  @message ||= build_assertion_message(@parser, @return_values).freeze
end
message_proc() click to toggle source
# File power_assert-2.0.1/lib/power_assert/context.rb, line 48
def message_proc
  -> { message }
end

Private Instance Methods

build_assertion_message(parser, return_values) click to toggle source
# File power_assert-2.0.1/lib/power_assert/context.rb, line 58
def build_assertion_message(parser, return_values)
  if PowerAssert.configuration.colorize_message
    line = IRB::Color.colorize_code(parser.line, ignore_error: true)
  else
    line = parser.line
  end

  path = detect_path(parser, return_values)
  return line unless path

  c2d = column2display_offset(parser.line)
  return_values, methods_in_path = find_all_identified_calls(return_values, path)
  return_values.zip(methods_in_path) do |i, j|
    unless i.name == j.name
      warn "power_assert: [BUG] Failed to get column: #{i.name}"
      return line
    end
    i.display_offset = c2d[j.column]
  end
  refs_in_path = path.find_all {|i| i.type == :ref }
  ref_values = refs_in_path.map {|i| Value[i.name, parser.binding.eval(i.name), parser.lineno, i.column, c2d[i.column]] }
  vals = (return_values + ref_values).find_all(&:display_offset).sort_by(&:display_offset).reverse
  return line if vals.empty?

  fmt = (0..vals[0].display_offset).map do |i|
    if vals.find {|v| v.display_offset == i }
      "%<#{i}>s"
    else
      line[i] == "\t" ? "\t" : ' '
    end
  end.join
  lines = []
  lines << line.chomp
  lines << sprintf(fmt, vals.each_with_object({}) {|v, h| h[:"#{v.display_offset}"] = '|' }).chomp
  vals.each do |i|
    inspected_val = SafeInspectable.new(Inspector.new(i.value, i.display_offset)).inspect
    inspected_val.each_line do |l|
      map_to = vals.each_with_object({}) do |j, h|
        h[:"#{j.display_offset}"] = [l, '|', ' '][i.display_offset <=> j.display_offset]
      end
      lines << encoding_safe_rstrip(sprintf(fmt, map_to))
    end
  end
  lines.join("\n")
end
column2display_offset(str) click to toggle source
# File power_assert-2.0.1/lib/power_assert/context.rb, line 149
def column2display_offset(str)
  display_offset = 0
  str.each_char.with_object([]) do |c, r|
    c.bytesize.times do
      r << display_offset
    end
    display_offset += c.ascii_only? ? 1 : 2 # FIXME
  end
end
detect_path(parser, return_values) click to toggle source
# File power_assert-2.0.1/lib/power_assert/context.rb, line 104
def detect_path(parser, return_values)
  return parser.call_paths.flatten.uniq if parser.method_id_set.empty?
  all_paths = parser.call_paths
  return_value_names = return_values.map(&:name)
  uniq_calls = uniq_calls(all_paths)
  uniq_call = return_value_names.find {|i| uniq_calls.include?(i) }
  detected_paths = all_paths.find_all do |path|
    method_names = path.find_all {|ident| ident.type == :method }.map(&:name)
    break [path] if uniq_call and method_names.include?(uniq_call)
    return_value_names == method_names
  end
  return nil unless detected_paths.length == 1
  detected_paths[0]
end
encoding_safe_rstrip(str) click to toggle source
# File power_assert-2.0.1/lib/power_assert/context.rb, line 138
def encoding_safe_rstrip(str)
  str.rstrip
rescue ArgumentError, Encoding::CompatibilityError
  enc = str.encoding
  if enc.ascii_compatible?
    str.b.rstrip.force_encoding(enc)
  else
    str
  end
end
enum_count_by(enum, &blk) click to toggle source
# File power_assert-2.0.1/lib/power_assert/context.rb, line 134
def enum_count_by(enum, &blk)
  Hash[enum.group_by(&blk).map{|k, v| [k, v.length] }]
end
find_all_identified_calls(return_values, path) click to toggle source
# File power_assert-2.0.1/lib/power_assert/context.rb, line 124
def find_all_identified_calls(return_values, path)
  return_value_num_of_calls = enum_count_by(return_values, &:name)
  path_num_of_calls = enum_count_by(path.find_all {|ident| ident.type == :method }, &:name)
  identified_calls = return_value_num_of_calls.find_all {|name, num| path_num_of_calls[name] == num }.map(&:first)
  [
    return_values.find_all {|val| identified_calls.include?(val.name) },
    path.find_all {|ident| ident.type == :method and identified_calls.include?(ident.name)  }
  ]
end
fired?() click to toggle source
# File power_assert-2.0.1/lib/power_assert/context.rb, line 54
def fired?
  @fired
end
uniq_calls(paths) click to toggle source
# File power_assert-2.0.1/lib/power_assert/context.rb, line 119
def uniq_calls(paths)
  all_calls = enum_count_by(paths.map {|path| path.find_all {|ident| ident.type == :method }.map(&:name).uniq }.flatten) {|i| i }
  all_calls.find_all {|_, call_count| call_count == 1 }.map {|name, _| name }
end