class DeepCover::CoveredCode

Attributes

buffer[RW]
local_var[RW]
path[RW]

Public Class Methods

new( path: nil, source: nil, lineno: 1, local_var: '_temp', tracker_hits: nil ) click to toggle source
# File lib/deep_cover/covered_code.rb, line 10
def initialize(
  path: nil,
  source: nil,
  lineno: 1,
  local_var: '_temp',
  tracker_hits: nil
)
  raise 'Must provide either path or source' unless path || source

  @path = path &&= Pathname(path)
  @buffer = Parser::Source::Buffer.new('', lineno)
  @buffer.source = source || path.read
  @index = nil # Set in #instrument_source
  @local_var = local_var
  @covered_source = nil # Set in #covered_source
  @tracker_hits = tracker_hits # Loaded from global in #tracker_hits, or when received right away when loading data
  @nb_allocated_trackers = 0
  # We parse the code now so that problems happen early
  covered_ast
  @tracker_hits = Array.new(@nb_allocated_trackers, 0) if @tracker_hits == :zeroes
end
next_global_index() click to toggle source
# File lib/deep_cover/covered_code.rb, line 168
def self.next_global_index
  @last_allocated_global_index ||= -1
  @last_allocated_global_index += 1
end

Public Instance Methods

allocate_trackers(nb_needed) click to toggle source
# File lib/deep_cover/covered_code.rb, line 94
def allocate_trackers(nb_needed)
  return @nb_allocated_trackers...@nb_allocated_trackers if nb_needed == 0
  prev = @nb_allocated_trackers
  @nb_allocated_trackers += nb_needed
  prev...@nb_allocated_trackers
end
char_cover(**options) click to toggle source
# File lib/deep_cover/covered_code.rb, line 60
def char_cover(**options)
  Analyser::PerChar.new(self, **options).results
end
comments() click to toggle source
# File lib/deep_cover/covered_code.rb, line 68
def comments
  root
  @comments
end
compile() click to toggle source
# File lib/deep_cover/covered_code.rb, line 133
def compile
  RubyVM::InstructionSequence.compile(covered_source, path.to_s, path.to_s)
end
compile_or_warn() click to toggle source
# File lib/deep_cover/covered_code.rb, line 137
def compile_or_warn
  warn_instead_of_syntax_error do
    compile
  end
end
covered_ast() click to toggle source
# File lib/deep_cover/covered_code.rb, line 64
def covered_ast
  root.main
end
covered_source() click to toggle source
# File lib/deep_cover/covered_code.rb, line 114
def covered_source
  @covered_source ||= instrument_source
end
each_node(*args, &block) click to toggle source
# File lib/deep_cover/covered_code.rb, line 80
def each_node(*args, &block)
  covered_ast.each_node(*args, &block)
end
execute_code(binding: DeepCover::GLOBAL_BINDING.dup) click to toggle source
# File lib/deep_cover/covered_code.rb, line 45
def execute_code(binding: DeepCover::GLOBAL_BINDING.dup)
  eval(covered_source, binding, (@path || '<raw_code>').to_s, lineno) # rubocop:disable Security/Eval
  self
end
execute_code_or_warn(*args) click to toggle source
# File lib/deep_cover/covered_code.rb, line 50
def execute_code_or_warn(*args)
  warn_instead_of_syntax_error do
    execute_code(*args)
  end
end
freeze() click to toggle source
Calls superclass method
# File lib/deep_cover/covered_code.rb, line 143
def freeze
  unless frozen? # Guard against reentrance
    tracker_hits
    super
    root.each_node(&:freeze)
  end
  self
end
increment_tracker_source(tracker_id) click to toggle source
# File lib/deep_cover/covered_code.rb, line 90
def increment_tracker_source(tracker_id)
  "#{DeepCover.config.tracker_global}[#{@index}][#{tracker_id}]+=1"
end
inspect() click to toggle source
# File lib/deep_cover/covered_code.rb, line 152
def inspect
  %{#<DeepCover::CoveredCode "#{path}">}
end
Also aliased as: to_s
instrument_source() { |prefix, node, begin, :prefix| ... } click to toggle source
# File lib/deep_cover/covered_code.rb, line 118
def instrument_source
  @index ||= self.class.next_global_index

  rewriter = Parser::Source::TreeRewriter.new(@buffer)
  covered_ast.each_node do |node|
    node.rewriting_rules.each do |range, rule|
      prefix, _node, suffix = rule.partition('%{node}')
      prefix = yield prefix, node, range.begin, :prefix if block_given? && !prefix.empty?
      suffix = yield suffix, node, range.end, :suffix if block_given? && !suffix.empty?
      rewriter.wrap(range, prefix, suffix)
    end
  end
  rewriter.process
end
line_coverage(**options) click to toggle source
# File lib/deep_cover/covered_code.rb, line 56
def line_coverage(**options)
  Analyser::PerLine.new(self, **options).results
end
lineno() click to toggle source
# File lib/deep_cover/covered_code.rb, line 32
def lineno
  @buffer.first_line
end
nb_lines() click to toggle source
# File lib/deep_cover/covered_code.rb, line 36
def nb_lines
  lines = buffer.source_lines
  if lines.empty?
    0
  else
    lines.size - (lines.last.empty? ? 1 : 0)
  end
end
root() click to toggle source
# File lib/deep_cover/covered_code.rb, line 73
def root
  @root ||= begin
    ast, @comments = parser.parse_with_comments(@buffer)
    Node::Root.new(ast, self)
  end
end
setup_tracking_source() click to toggle source
# File lib/deep_cover/covered_code.rb, line 84
def setup_tracking_source
  src = "(#{DeepCover.config.tracker_global}||={})[#{@index}]||=Array.new(#{@nb_allocated_trackers},0)"
  src += ";(#{DeepCover.config.tracker_global}_p||={})[#{@index}]=#{path.to_s.inspect}" if path
  src
end
to_s()
Alias for: inspect
tracker_hits() click to toggle source
# File lib/deep_cover/covered_code.rb, line 101
def tracker_hits
  return @tracker_hits if @tracker_hits
  global_trackers = DeepCover::GlobalVariables.trackers[@index]

  return unless global_trackers

  if global_trackers.size != @nb_allocated_trackers
    raise "Cannot sync path: #{path.inspect}, global[#{@index}] is of size #{global_trackers.size} instead of expected #{@nb_allocated_trackers}"
  end

  @tracker_hits = global_trackers
end
warn_instead_of_syntax_error() { || ... } click to toggle source
# File lib/deep_cover/covered_code.rb, line 157
    def warn_instead_of_syntax_error # &block
      yield
    rescue ::SyntaxError => e
      warn Tools.strip_heredoc(<<-MSG)
          DeepCover is getting confused with the file #{path} and it won't be instrumented.
          Please report this error and provide the source code around the following lines:
          #{e}
      MSG
      nil
    end

Private Instance Methods

parser() click to toggle source
# File lib/deep_cover/covered_code.rb, line 175
def parser
  Parser::CurrentRuby.new.tap do |parser|
    parser.diagnostics.all_errors_are_fatal = true
    parser.diagnostics.ignore_warnings      = true
  end
end