module Rcov class HTMLCoverage < BaseFormatter # :nodoc: require 'fileutils' DEFAULT_OPTS = {:color => false, :fsr => 30, :destdir => "coverage", :callsites => false, :cross_references => false, :charset => nil } def initialize(opts = {}) options = DEFAULT_OPTS.clone.update(opts) super(options) @dest = options[:destdir] @css = options[:css] @color = options[:color] @fsr = options[:fsr] @do_callsites = options[:callsites] @do_cross_references = options[:cross_references] @span_class_index = 0 @charset = options[:charset] end def execute return if @files.empty? FileUtils.mkdir_p @dest # Copy collaterals ['screen.css','print.css','rcov.js','jquery-1.3.2.min.js','jquery.tablesorter.min.js'].each do |_file| _src = File.expand_path("#{File.dirname(__FILE__)}/../templates/#{_file}") FileUtils.cp(_src, File.join(@dest, "#{_file}")) end # Copy custom CSS, if any if @css begin _src = File.expand_path("#{@dest}/../#{@css}") FileUtils.cp(_src, File.join(@dest, "custom.css")) rescue @css = nil end end create_index(File.join(@dest, "index.html")) each_file_pair_sorted do |filename, fileinfo| create_file(File.join(@dest, mangle_filename(filename)), fileinfo) end end private class SummaryFileInfo # :nodoc: def initialize(obj) @o = obj end def num_lines @o.num_lines end def num_code_lines @o.num_code_lines end def code_coverage @o.code_coverage end def code_coverage_for_report code_coverage * 100 end def total_coverage @o.total_coverage end def total_coverage_for_report total_coverage * 100 end def name "TOTAL" end end def create_index(destname) doc = Rcov::Formatters::HtmlErbTemplate.new('index.html.erb', :project_name => project_name, :generated_on => Time.now, :css => @css, :rcov => Rcov, :formatter => self, :output_threshold => @output_threshold, :total => SummaryFileInfo.new(self), :files => each_file_pair_sorted.map{|k,v| v} ) File.open(destname, "w") { |f| f.puts doc.render } end def create_file(destfile, fileinfo) doc = Rcov::Formatters::HtmlErbTemplate.new('detail.html.erb', :project_name => project_name, :rcov_page_title => fileinfo.name, :css => @css, :generated_on => Time.now, :rcov => Rcov, :formatter => self, :output_threshold => @output_threshold, :fileinfo => fileinfo ) File.open(destfile, "w") { |f| f.puts doc.render } end private def project_name Dir.pwd.split('/')[-1].split(/[^a-zA-Z0-9]/).map{|i| i.gsub(/[^a-zA-Z0-9]/,'').capitalize} * " " || "" end end class HTMLProfiling < HTMLCoverage # :nodoc: DEFAULT_OPTS = {:destdir => "profiling"} def initialize(opts = {}) options = DEFAULT_OPTS.clone.update(opts) super(options) @max_cache = {} @median_cache = {} end def default_title "Bogo-profile information" end def default_color if @color "rgb(179,205,255)" else "rgb(255, 255, 255)" end end def output_color_table? false end def span_class(sourceinfo, marked, count) full_scale_range = @fsr # dB nz_count = sourceinfo.counts.select{|x| x && x != 0} nz_count << 1 # avoid div by 0 max = @max_cache[sourceinfo] ||= nz_count.max #avg = @median_cache[sourceinfo] ||= 1.0 * # nz_count.inject{|a,b| a+b} / nz_count.size median = @median_cache[sourceinfo] ||= 1.0 * nz_count.sort[nz_count.size/2] max ||= 2 max = 2 if max == 1 if marked == true count = 1 if !count || count == 0 idx = 50 + 1.0 * (500/full_scale_range) * Math.log(count/median) / Math.log(10) idx = idx.to_i idx = 0 if idx < 0 idx = 100 if idx > 100 "run#{idx}" else nil end end end class RubyAnnotation < BaseFormatter # :nodoc: DEFAULT_OPTS = { :destdir => "coverage" } def initialize(opts = {}) options = DEFAULT_OPTS.clone.update(opts) super(options) @dest = options[:destdir] @do_callsites = true @do_cross_references = true @mangle_filename = Hash.new{ |h,base| h[base] = Pathname.new(base).cleanpath.to_s.gsub(%r{^\w:[/\\]}, "").gsub(/\./, "_").gsub(/[\\\/]/, "-") + ".rb" } end def execute return if @files.empty? FileUtils.mkdir_p @dest each_file_pair_sorted do |filename, fileinfo| create_file(File.join(@dest, mangle_filename(filename)), fileinfo) end end private def format_lines(file) result = "" format_line = "%#{file.num_lines.to_s.size}d" file.num_lines.times do |i| line = file.lines[i].chomp marked = file.coverage[i] count = file.counts[i] result << create_cross_refs(file.name, i+1, line, marked) + "\n" end result end def create_cross_refs(filename, lineno, linetext, marked) return linetext unless @callsite_analyzer && @do_callsites ref_blocks = [] _get_defsites(ref_blocks, filename, lineno, linetext, ">>") do |ref| if ref.file ref.file.sub!(%r!^./!, '') where = "at #{mangle_filename(ref.file)}:#{ref.line}" else where = "(C extension/core)" end "#{ref.klass}##{ref.mid} " + where + "" end _get_callsites(ref_blocks, filename, lineno, linetext, "<<") do |ref| # " ref.file.sub!(%r!^./!, '') "#{mangle_filename(ref.file||'C code')}:#{ref.line} " + "in #{ref.klass}##{ref.mid}" end create_cross_reference_block(linetext, ref_blocks, marked) end def create_cross_reference_block(linetext, ref_blocks, marked) codelen = 75 if ref_blocks.empty? if marked return "%-#{codelen}s #o" % linetext else return linetext end end ret = "" @cross_ref_idx ||= 0 @known_files ||= sorted_file_pairs.map{|fname, finfo| normalize_filename(fname)} ret << "%-#{codelen}s # " % linetext ref_blocks.each do |refs, toplabel, label_proc| unless !toplabel || toplabel.empty? ret << toplabel << " " end refs.each do |dst| dstfile = normalize_filename(dst.file) if dst.file dstline = dst.line label = label_proc.call(dst) if dst.file && @known_files.include?(dstfile) ret << "[[" << label << "]], " else ret << label << ", " end end end ret end def create_file(destfile, fileinfo) #body = format_lines(fileinfo) #File.open(destfile, "w") do |f| #f.puts body #f.puts footer(fileinfo) #end end def footer(fileinfo) s = "# Total lines : %d\n" % fileinfo.num_lines s << "# Lines of code : %d\n" % fileinfo.num_code_lines s << "# Total coverage : %3.1f%%\n" % [ fileinfo.total_coverage*100 ] s << "# Code coverage : %3.1f%%\n\n" % [ fileinfo.code_coverage*100 ] # prevents false positives on Emacs s << "# Local " "Variables:\n" "# mode: " "rcov-xref\n" "# End:\n" end end end