module Rcov class BaseFormatter # :nodoc: require 'pathname' require 'rbconfig' RCOV_IGNORE_REGEXPS = [ /\A#{Regexp.escape(Pathname.new(::RbConfig::CONFIG['libdir']).cleanpath.to_s)}/, /\btc_[^.]*.rb/, /_test\.rb\z/, /\btest\//, /\bvendor\//, /\A#{Regexp.escape(__FILE__)}\z/ ] DEFAULT_OPTS = { :ignore => RCOV_IGNORE_REGEXPS, :sort => :name, :sort_reverse => false, :output_threshold => 101, :dont_ignore => [], :callsite_analyzer => nil, \ :comments_run_by_default => false } def initialize(opts = {}) options = DEFAULT_OPTS.clone.update(opts) @failure_threshold = options[:failure_threshold] @files = {} @ignore_files = options[:ignore] @dont_ignore_files = options[:dont_ignore] @sort_criterium = case options[:sort] when :loc then lambda{|fname, finfo| finfo.num_code_lines} when :coverage then lambda{|fname, finfo| finfo.code_coverage} else lambda { |fname, finfo| fname } end @sort_reverse = options[:sort_reverse] @output_threshold = options[:output_threshold] @callsite_analyzer = options[:callsite_analyzer] @comments_run_by_default = options[:comments_run_by_default] @callsite_index = nil @mangle_filename = Hash.new{|h,base| h[base] = Pathname.new(base).cleanpath.to_s.gsub(%r{^\w:[/\\]}, "").gsub(/\./, "_").gsub(/[\\\/]/, "-") + ".html" } end def add_file(filename, lines, coverage, counts) old_filename = filename filename = normalize_filename(filename) SCRIPT_LINES__[filename] = SCRIPT_LINES__[old_filename] if @ignore_files.any?{|x| x === filename} && !@dont_ignore_files.any?{|x| x === filename} return nil end if @files[filename] @files[filename].merge(lines, coverage, counts) else @files[filename] = FileStatistics.new(filename, lines, counts, @comments_run_by_default) end end def normalize_filename(filename) File.expand_path(filename).gsub(/^#{Regexp.escape(Dir.getwd)}\//, '') end def mangle_filename(base) @mangle_filename[base] end def each_file_pair_sorted(&b) return sorted_file_pairs unless block_given? sorted_file_pairs.each(&b) end def sorted_file_pairs pairs = @files.sort_by do |fname, finfo| @sort_criterium.call(fname, finfo) end.select{|_, finfo| 100 * finfo.code_coverage < @output_threshold} @sort_reverse ? pairs.reverse : pairs end def total_coverage lines = 0 total = 0.0 @files.each do |k,f| total += f.num_lines * f.total_coverage lines += f.num_lines end return 0 if lines == 0 total / lines end def code_coverage lines = 0 total = 0.0 @files.each do |k,f| total += f.num_code_lines * f.code_coverage lines += f.num_code_lines end return 0 if lines == 0 total / lines end def num_code_lines lines = 0 @files.each{|k, f| lines += f.num_code_lines } lines end def num_lines lines = 0 @files.each{|k, f| lines += f.num_lines } lines end private def cross_references_for(filename, lineno) return nil unless @callsite_analyzer @callsite_index ||= build_callsite_index @callsite_index[normalize_filename(filename)][lineno] end def reverse_cross_references_for(filename, lineno) return nil unless @callsite_analyzer @callsite_reverse_index ||= build_reverse_callsite_index @callsite_reverse_index[normalize_filename(filename)][lineno] end def build_callsite_index index = Hash.new{|h,k| h[k] = {}} @callsite_analyzer.analyzed_classes.each do |classname| @callsite_analyzer.analyzed_methods(classname).each do |methname| defsite = @callsite_analyzer.defsite(classname, methname) index[normalize_filename(defsite.file)][defsite.line] = @callsite_analyzer.callsites(classname, methname) end end index end def build_reverse_callsite_index index = Hash.new{|h,k| h[k] = {}} @callsite_analyzer.analyzed_classes.each do |classname| @callsite_analyzer.analyzed_methods(classname).each do |methname| callsites = @callsite_analyzer.callsites(classname, methname) defsite = @callsite_analyzer.defsite(classname, methname) callsites.each_pair do |callsite, count| next unless callsite.file fname = normalize_filename(callsite.file) (index[fname][callsite.line] ||= []) << [classname, methname, defsite, count] end end end index end class XRefHelper < Struct.new(:file, :line, :klass, :mid, :count) # :nodoc: end def _get_defsites(ref_blocks, filename, lineno, linetext, label, &format_call_ref) if @do_cross_references and (rev_xref = reverse_cross_references_for(filename, lineno)) refs = rev_xref.map do |classname, methodname, defsite, count| XRefHelper.new(defsite.file, defsite.line, classname, methodname, count) end.sort_by{|r| r.count}.reverse ref_blocks << [refs, label, format_call_ref] end end def _get_callsites(ref_blocks, filename, lineno, linetext, label, &format_called_ref) if @do_callsites and (refs = cross_references_for(filename, lineno)) refs = refs.sort_by{|k,count| count}.map do |ref, count| XRefHelper.new(ref.file, ref.line, ref.calling_class, ref.calling_method, count) end.reverse ref_blocks << [refs, label, format_called_ref] end end end end