# rcov Copyright (c) 2004-2006 Mauricio Fernandez <mfp@acm.org>
#
# See LEGAL and LICENSE for licensing information.
require 'rcov/version'
module Rcov
# RCOV__ performs the low-level tracing of the execution, gathering code
# coverage information in the process. The C core made available through the
# rcovrt extension will be used if possible. Otherwise the functionality
# will be emulated using set_trace_func, but this is very expensive and
# will fail if other libraries (e.g. breakpoint) change the trace_func.
#
# Do not use this module; it is very low-level and subject to frequent
# changes. Rcov::CodeCoverageAnalyzer offers a much more convenient and
# stable interface.
module RCOV__
COVER = {}
CALLSITES = {}
DEFSITES = {}
pure_ruby_impl_needed = true
unless defined? $rcov_do_not_use_rcovrt
begin
require 'rcovrt'
abi = [0,0,0]
begin
abi = RCOV__.ABI
raise if abi[0] != RCOVRT_ABI[0] || abi[1] < RCOVRT_ABI[1]
pure_ruby_impl_needed = false
rescue
$stderr.puts <<-EOF
The rcovrt extension I found was built for a different version of rcov.
The required ABI is: #{RCOVRT_ABI.join(".")}
Your current rcovrt extension is: #{abi.join(".")}
Please delete rcovrt.{so,bundle,dll,...} and install the required one.
EOF
raise LoadError
end
rescue LoadError
$stderr.puts <<-EOF
Since the rcovrt extension couldn't be loaded, rcov will run in pure-Ruby
mode, which is about two orders of magnitude slower.
If you're on win32, you can find a pre-built extension (usable with recent
One Click Installer and mswin32 builds) at http://eigenclass.org/hiki.rb?rcov .
EOF
end
end
if pure_ruby_impl_needed
methods = %w[install_coverage_hook remove_coverage_hook reset_coverage
install_callsite_hook remove_callsite_hook reset_callsite
generate_coverage_info generate_callsite_info]
sklass = class << self; self end
(methods & sklass.instance_methods).each do |meth|
sklass.class_eval{ remove_method meth }
end
@coverage_hook_activated = @callsite_hook_activated = false
def self.install_coverage_hook # :nodoc:
install_common_hook
@coverage_hook_activated = true
end
def self.install_callsite_hook # :nodoc:
install_common_hook
@callsite_hook_activated = true
end
def self.install_common_hook # :nodoc:
set_trace_func lambda {|event, file, line, id, binding, klass|
next unless SCRIPT_LINES__.has_key? file
case event
when 'call'
if @callsite_hook_activated
receiver = eval("self", binding)
klass = class << klass; self end unless klass === receiver
begin
DEFSITES[[klass.to_s, id.to_s]] = [file, line]
rescue Exception
end
caller_arr = self.format_backtrace_array(caller[1,1])
begin
hash = CALLSITES[[klass.to_s, id.to_s]] ||= {}
hash[caller_arr] ||= 0
hash[caller_arr] += 1
#puts "#{event} #{file} #{line} #{klass.inspect} " +
# "#{klass.object_id} #{id} #{eval('self', binding)}"
rescue Exception
end
end
when 'c-call', 'c-return', 'class'
return
end
if @coverage_hook_activated
COVER[file] ||= Array.new(SCRIPT_LINES__[file].size, 0)
COVER[file][line - 1] ||= 0
COVER[file][line - 1] += 1
end
}
end
def self.remove_coverage_hook # :nodoc:
@coverage_hook_activated = false
set_trace_func(nil) if !@callsite_hook_activated
end
def self.remove_callsite_hook # :nodoc:
@callsite_hook_activated = false
set_trace_func(nil) if !@coverage_hook_activated
end
def self.reset_coverage # :nodoc:
COVER.replace({})
end
def self.reset_callsite # :nodoc:
CALLSITES.replace({})
DEFSITES.replace({})
end
def self.generate_coverage_info # :nodoc:
Marshal.load(Marshal.dump(COVER))
end
def self.generate_callsite_info # :nodoc:
[CALLSITES, DEFSITES]
end
def self.format_backtrace_array(backtrace)
backtrace.map do |line|
md = /^([^:]*)(?::(\d+)(?::in `(.*)'))?/.match(line)
raise "Bad backtrace format" unless md
[nil, md[3] ? md[3].to_sym : nil, md[1], (md[2] || '').to_i]
end
end
end
end # RCOV__
end # Rcov
# vi: set sw=2:
syntax highlighted by Code2HTML, v. 0.9.1