123456789_123456789_123456789_123456789_123456789_

Class: PowerAssert::Context

Relationships & Source Files
Namespace Children
Classes:
Extension / Inclusion / Inheritance Descendants
Subclasses:
Inherits: Object
Defined in: lib/power_assert/context.rb

Class Method Summary

Instance Attribute Summary

Instance Method Summary

Constructor Details

.new(base_caller_length) ⇒ Context

[ GitHub ]

  
# File '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_alias_method = PowerAssert.configuration._trace_alias_method
  @trace_return = TracePoint.new(:return, :c_return) do |tp|
    begin
      unless method_id_set
        next unless Thread.current == @target_thread
        method_id_set = @parser.method_id_set
      end
      method_id = SUPPORT_ALIAS_METHOD                      ? tp.callee_id :
                  trace_alias_method && tp.event == :return ? tp.binding.eval('::Kernel.__callee__') :
                                                              tp.method_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
end

Instance Attribute Details

#fired?Boolean (readonly, private)

[ GitHub ]

  
# File 'lib/power_assert/context.rb', line 59

def fired?
  @fired
end

Instance Method Details

#build_assertion_message(parser, return_values) (private)

[ GitHub ]

  
# File 'lib/power_assert/context.rb', line 63

def build_assertion_message(parser, return_values)
  if PowerAssert.configuration._colorize_message
    line = Pry::Code.new(parser.line).highlighted
  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.to_s.to_sym] = '|' }).chomp
  vals.each do |i|
    inspected_val = SafeInspectable.new(Formatter.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.to_s.to_sym] = [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) (private)

[ GitHub ]

  
# File 'lib/power_assert/context.rb', line 154

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) (private)

[ GitHub ]

  
# File 'lib/power_assert/context.rb', line 109

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) (private)

[ GitHub ]

  
# File 'lib/power_assert/context.rb', line 143

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) (private)

[ GitHub ]

  
# File 'lib/power_assert/context.rb', line 139

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) (private)

[ GitHub ]

  
# File 'lib/power_assert/context.rb', line 129

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

#message

[ GitHub ]

  
# File 'lib/power_assert/context.rb', line 48

def message
  raise 'call #yield or #enable at first' unless fired?
  @message ||= build_assertion_message(@parser, @return_values).freeze
end

#message_proc

[ GitHub ]

  
# File 'lib/power_assert/context.rb', line 53

def message_proc
  #=> { message }
end

#uniq_calls(paths) (private)

[ GitHub ]

  
# File 'lib/power_assert/context.rb', line 124

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