mergulhao-rcov 0.8.1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/BLURB +149 -0
- data/CHANGES +177 -0
- data/LEGAL +36 -0
- data/LICENSE +56 -0
- data/README.API +42 -0
- data/README.emacs +64 -0
- data/README.en +130 -0
- data/README.rake +62 -0
- data/README.rant +68 -0
- data/README.vim +47 -0
- data/Rakefile +86 -0
- data/Rantfile +76 -0
- data/THANKS +96 -0
- data/bin/rcov +552 -0
- data/ext/rcovrt/callsite.c +242 -0
- data/ext/rcovrt/extconf.rb +11 -0
- data/ext/rcovrt/rcovrt.c +331 -0
- data/lib/rcov.rb +990 -0
- data/lib/rcov/lowlevel.rb +147 -0
- data/lib/rcov/rant.rb +87 -0
- data/lib/rcov/rcovtask.rb +156 -0
- data/lib/rcov/report.rb +1249 -0
- data/lib/rcov/version.rb +13 -0
- data/lib/rcov/xx.rb +761 -0
- data/rcov.el +131 -0
- data/rcov.vim +38 -0
- data/setup.rb +1588 -0
- data/test/assets/sample_01.rb +7 -0
- data/test/assets/sample_02.rb +5 -0
- data/test/assets/sample_03.rb +20 -0
- data/test/assets/sample_04.rb +10 -0
- data/test/assets/sample_05-new.rb +17 -0
- data/test/assets/sample_05-old.rb +13 -0
- data/test/assets/sample_05.rb +17 -0
- data/test/call_site_analyzer_test.rb +192 -0
- data/test/code_coverage_analyzer_test.rb +181 -0
- data/test/file_statistics_test.rb +471 -0
- data/test/functional_test.rb +94 -0
- data/test/turn_off_rcovrt.rb +4 -0
- metadata +99 -0
@@ -0,0 +1,147 @@
|
|
1
|
+
# rcov Copyright (c) 2004-2006 Mauricio Fernandez <mfp@acm.org>
|
2
|
+
#
|
3
|
+
# See LEGAL and LICENSE for licensing information.
|
4
|
+
|
5
|
+
require 'rcov/version'
|
6
|
+
|
7
|
+
module Rcov
|
8
|
+
|
9
|
+
# RCOV__ performs the low-level tracing of the execution, gathering code
|
10
|
+
# coverage information in the process. The C core made available through the
|
11
|
+
# rcovrt extension will be used if possible. Otherwise the functionality
|
12
|
+
# will be emulated using set_trace_func, but this is very expensive and
|
13
|
+
# will fail if other libraries (e.g. breakpoint) change the trace_func.
|
14
|
+
#
|
15
|
+
# Do not use this module; it is very low-level and subject to frequent
|
16
|
+
# changes. Rcov::CodeCoverageAnalyzer offers a much more convenient and
|
17
|
+
# stable interface.
|
18
|
+
module RCOV__
|
19
|
+
COVER = {}
|
20
|
+
CALLSITES = {}
|
21
|
+
DEFSITES = {}
|
22
|
+
pure_ruby_impl_needed = true
|
23
|
+
unless defined? $rcov_do_not_use_rcovrt
|
24
|
+
begin
|
25
|
+
require 'rcovrt'
|
26
|
+
abi = [0,0,0]
|
27
|
+
begin
|
28
|
+
abi = RCOV__.ABI
|
29
|
+
raise if abi[0] != RCOVRT_ABI[0] || abi[1] < RCOVRT_ABI[1]
|
30
|
+
pure_ruby_impl_needed = false
|
31
|
+
rescue
|
32
|
+
$stderr.puts <<-EOF
|
33
|
+
The rcovrt extension I found was built for a different version of rcov.
|
34
|
+
The required ABI is: #{RCOVRT_ABI.join(".")}
|
35
|
+
Your current rcovrt extension is: #{abi.join(".")}
|
36
|
+
|
37
|
+
Please delete rcovrt.{so,bundle,dll,...} and install the required one.
|
38
|
+
EOF
|
39
|
+
raise LoadError
|
40
|
+
end
|
41
|
+
rescue LoadError
|
42
|
+
$stderr.puts <<-EOF
|
43
|
+
|
44
|
+
Since the rcovrt extension couldn't be loaded, rcov will run in pure-Ruby
|
45
|
+
mode, which is about two orders of magnitude slower.
|
46
|
+
|
47
|
+
If you're on win32, you can find a pre-built extension (usable with recent
|
48
|
+
One Click Installer and mswin32 builds) at http://eigenclass.org/hiki.rb?rcov .
|
49
|
+
|
50
|
+
EOF
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
if pure_ruby_impl_needed
|
55
|
+
methods = %w[install_coverage_hook remove_coverage_hook reset_coverage
|
56
|
+
install_callsite_hook remove_callsite_hook reset_callsite
|
57
|
+
generate_coverage_info generate_callsite_info]
|
58
|
+
sklass = class << self; self end
|
59
|
+
(methods & sklass.instance_methods).each do |meth|
|
60
|
+
sklass.class_eval{ remove_method meth }
|
61
|
+
end
|
62
|
+
|
63
|
+
@coverage_hook_activated = @callsite_hook_activated = false
|
64
|
+
|
65
|
+
def self.install_coverage_hook # :nodoc:
|
66
|
+
install_common_hook
|
67
|
+
@coverage_hook_activated = true
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.install_callsite_hook # :nodoc:
|
71
|
+
install_common_hook
|
72
|
+
@callsite_hook_activated = true
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.install_common_hook # :nodoc:
|
76
|
+
set_trace_func lambda {|event, file, line, id, binding, klass|
|
77
|
+
next unless SCRIPT_LINES__.has_key? file
|
78
|
+
case event
|
79
|
+
when 'call'
|
80
|
+
if @callsite_hook_activated
|
81
|
+
receiver = eval("self", binding)
|
82
|
+
klass = class << klass; self end unless klass === receiver
|
83
|
+
begin
|
84
|
+
DEFSITES[[klass.to_s, id.to_s]] = [file, line]
|
85
|
+
rescue Exception
|
86
|
+
end
|
87
|
+
caller_arr = self.format_backtrace_array(caller[1,1])
|
88
|
+
begin
|
89
|
+
hash = CALLSITES[[klass.to_s, id.to_s]] ||= {}
|
90
|
+
hash[caller_arr] ||= 0
|
91
|
+
hash[caller_arr] += 1
|
92
|
+
#puts "#{event} #{file} #{line} #{klass.inspect} " +
|
93
|
+
# "#{klass.object_id} #{id} #{eval('self', binding)}"
|
94
|
+
rescue Exception
|
95
|
+
end
|
96
|
+
end
|
97
|
+
when 'c-call', 'c-return', 'class'
|
98
|
+
return
|
99
|
+
end
|
100
|
+
if @coverage_hook_activated
|
101
|
+
COVER[file] ||= Array.new(SCRIPT_LINES__[file].size, 0)
|
102
|
+
COVER[file][line - 1] ||= 0
|
103
|
+
COVER[file][line - 1] += 1
|
104
|
+
end
|
105
|
+
}
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.remove_coverage_hook # :nodoc:
|
109
|
+
@coverage_hook_activated = false
|
110
|
+
set_trace_func(nil) if !@callsite_hook_activated
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.remove_callsite_hook # :nodoc:
|
114
|
+
@callsite_hook_activated = false
|
115
|
+
set_trace_func(nil) if !@coverage_hook_activated
|
116
|
+
end
|
117
|
+
|
118
|
+
def self.reset_coverage # :nodoc:
|
119
|
+
COVER.replace({})
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.reset_callsite # :nodoc:
|
123
|
+
CALLSITES.replace({})
|
124
|
+
DEFSITES.replace({})
|
125
|
+
end
|
126
|
+
|
127
|
+
def self.generate_coverage_info # :nodoc:
|
128
|
+
Marshal.load(Marshal.dump(COVER))
|
129
|
+
end
|
130
|
+
|
131
|
+
def self.generate_callsite_info # :nodoc:
|
132
|
+
[CALLSITES, DEFSITES]
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.format_backtrace_array(backtrace)
|
136
|
+
backtrace.map do |line|
|
137
|
+
md = /^([^:]*)(?::(\d+)(?::in `(.*)'))?/.match(line)
|
138
|
+
raise "Bad backtrace format" unless md
|
139
|
+
[nil, md[3] ? md[3].to_sym : nil, md[1], (md[2] || '').to_i]
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end # RCOV__
|
144
|
+
|
145
|
+
end # Rcov
|
146
|
+
|
147
|
+
# vi: set sw=2:
|
data/lib/rcov/rant.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
|
2
|
+
require 'rant/rantlib'
|
3
|
+
|
4
|
+
module Rant # :nodoc:
|
5
|
+
module Generators # :nodoc:
|
6
|
+
class Rcov # :nodoc:
|
7
|
+
def self.rant_gen(app, ch, args, &block)
|
8
|
+
if !args || args.empty?
|
9
|
+
self.new(app, ch, &block)
|
10
|
+
elsif args.size == 1
|
11
|
+
name, pre = app.normalize_task_arg(args.first, ch)
|
12
|
+
self.new(app, ch, name, pre, &block)
|
13
|
+
else
|
14
|
+
app.abort_at(ch,
|
15
|
+
"Rcov takes only one additional argument, " +
|
16
|
+
"which should be like one given to the `task' command.")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_accessor :verbose
|
21
|
+
attr_accessor :libs
|
22
|
+
attr_accessor :test_dirs
|
23
|
+
attr_accessor :pattern
|
24
|
+
attr_accessor :test_files
|
25
|
+
attr_accessor :rcov_opts
|
26
|
+
# Directory where to put the generated XHTML reports
|
27
|
+
attr_accessor :output_dir
|
28
|
+
|
29
|
+
def initialize(app, cinf, name = :rcov, prerequisites = [], &block)
|
30
|
+
@rac = app
|
31
|
+
@name = name
|
32
|
+
@pre = prerequisites
|
33
|
+
#@block = block
|
34
|
+
@verbose = nil
|
35
|
+
cf = cinf[:file]
|
36
|
+
@libs = []
|
37
|
+
libdir = File.join(File.dirname(File.expand_path(cf)), 'lib')
|
38
|
+
@libs << libdir if test(?d, libdir)
|
39
|
+
@rcov_opts = ["--text-report"]
|
40
|
+
@test_dirs = []
|
41
|
+
@pattern = nil
|
42
|
+
@test_files = nil
|
43
|
+
yield self if block_given?
|
44
|
+
@pattern = "test*.rb" if @pattern.nil? && @test_files.nil?
|
45
|
+
@output_dir ||= "coverage"
|
46
|
+
|
47
|
+
@pre ||= []
|
48
|
+
# define the task
|
49
|
+
app.task(:__caller__ => cinf, @name => @pre) { |t|
|
50
|
+
args = []
|
51
|
+
if @libs && !@libs.empty?
|
52
|
+
args << "-I#{@libs.join File::PATH_SEPARATOR}"
|
53
|
+
end
|
54
|
+
if rcov_path = ENV['RCOVPATH']
|
55
|
+
args << rcov_path
|
56
|
+
else
|
57
|
+
args << "-S" << "rcov"
|
58
|
+
end
|
59
|
+
args.concat rcov_opts
|
60
|
+
args << "-o" << @output_dir
|
61
|
+
if test(?d, "test")
|
62
|
+
@test_dirs << "test"
|
63
|
+
elsif test(?d, "tests")
|
64
|
+
@test_dirs << "tests"
|
65
|
+
end
|
66
|
+
args.concat filelist
|
67
|
+
app.context.sys.ruby args
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
def filelist
|
72
|
+
return @rac.sys[@rac.var['TEST']] if @rac.var['TEST']
|
73
|
+
filelist = @test_files || []
|
74
|
+
if filelist.empty?
|
75
|
+
if @test_dirs && !@test_dirs.empty?
|
76
|
+
@test_dirs.each { |dir|
|
77
|
+
filelist.concat(@rac.sys[File.join(dir, @pattern)])
|
78
|
+
}
|
79
|
+
else
|
80
|
+
filelist.concat(@rac.sys[@pattern]) if @pattern
|
81
|
+
end
|
82
|
+
end
|
83
|
+
filelist
|
84
|
+
end
|
85
|
+
end # class Rcov
|
86
|
+
end # module Generators
|
87
|
+
end # module Rant
|
@@ -0,0 +1,156 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Define a task library for performing code coverage analysis of unit tests
|
4
|
+
# using rcov.
|
5
|
+
|
6
|
+
require 'rake'
|
7
|
+
require 'rake/tasklib'
|
8
|
+
|
9
|
+
module Rcov
|
10
|
+
|
11
|
+
# Create a task that runs a set of tests through rcov, generating code
|
12
|
+
# coverage reports.
|
13
|
+
#
|
14
|
+
# Example:
|
15
|
+
#
|
16
|
+
# require 'rcov/rcovtask'
|
17
|
+
#
|
18
|
+
# Rcov::RcovTask.new do |t|
|
19
|
+
# t.libs << "test"
|
20
|
+
# t.test_files = FileList['test/test*.rb']
|
21
|
+
# t.verbose = true
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# If rake is invoked with a "TEST=filename" command line option,
|
25
|
+
# then the list of test files will be overridden to include only the
|
26
|
+
# filename specified on the command line. This provides an easy way
|
27
|
+
# to run just one test.
|
28
|
+
#
|
29
|
+
# If rake is invoked with a "RCOVOPTS=options" command line option,
|
30
|
+
# then the given options are passed to rcov.
|
31
|
+
#
|
32
|
+
# If rake is invoked with a "RCOVPATH=path/to/rcov" command line option,
|
33
|
+
# then the given rcov executable will be used; otherwise the one in your
|
34
|
+
# PATH will be used.
|
35
|
+
#
|
36
|
+
# Examples:
|
37
|
+
#
|
38
|
+
# rake rcov # run tests normally
|
39
|
+
# rake rcov TEST=just_one_file.rb # run just one test file.
|
40
|
+
# rake rcov RCOVOPTS="-p" # run in profile mode
|
41
|
+
# rake rcov RCOVOPTS="-T" # generate text report
|
42
|
+
#
|
43
|
+
class RcovTask < Rake::TaskLib
|
44
|
+
|
45
|
+
# Name of test task. (default is :rcov)
|
46
|
+
attr_accessor :name
|
47
|
+
|
48
|
+
# List of directories to added to $LOAD_PATH before running the
|
49
|
+
# tests. (default is 'lib')
|
50
|
+
attr_accessor :libs
|
51
|
+
|
52
|
+
# True if verbose test output desired. (default is false)
|
53
|
+
attr_accessor :verbose
|
54
|
+
|
55
|
+
# Request that the tests be run with the warning flag set.
|
56
|
+
# E.g. warning=true implies "ruby -w" used to run the tests.
|
57
|
+
attr_accessor :warning
|
58
|
+
|
59
|
+
# Glob pattern to match test files. (default is 'test/test*.rb')
|
60
|
+
attr_accessor :pattern
|
61
|
+
|
62
|
+
# Array of commandline options to pass to ruby when running the rcov loader.
|
63
|
+
attr_accessor :ruby_opts
|
64
|
+
|
65
|
+
# Array of commandline options to pass to rcov. An explicit
|
66
|
+
# RCOVOPTS=opts on the command line will override this. (default
|
67
|
+
# is <tt>["--text-report"]</tt>)
|
68
|
+
attr_accessor :rcov_opts
|
69
|
+
|
70
|
+
# Output directory for the XHTML report.
|
71
|
+
attr_accessor :output_dir
|
72
|
+
|
73
|
+
# Explicitly define the list of test files to be included in a
|
74
|
+
# test. +list+ is expected to be an array of file names (a
|
75
|
+
# FileList is acceptable). If both +pattern+ and +test_files+ are
|
76
|
+
# used, then the list of test files is the union of the two.
|
77
|
+
def test_files=(list)
|
78
|
+
@test_files = list
|
79
|
+
end
|
80
|
+
|
81
|
+
# Create a testing task.
|
82
|
+
def initialize(name=:rcov)
|
83
|
+
@name = name
|
84
|
+
@libs = ["lib"]
|
85
|
+
@pattern = nil
|
86
|
+
@test_files = nil
|
87
|
+
@verbose = false
|
88
|
+
@warning = false
|
89
|
+
@rcov_opts = ["--text-report"]
|
90
|
+
@ruby_opts = []
|
91
|
+
@output_dir = "coverage"
|
92
|
+
yield self if block_given?
|
93
|
+
@pattern = 'test/test*.rb' if @pattern.nil? && @test_files.nil?
|
94
|
+
define
|
95
|
+
end
|
96
|
+
|
97
|
+
# Create the tasks defined by this task lib.
|
98
|
+
def define
|
99
|
+
lib_path = @libs.join(File::PATH_SEPARATOR)
|
100
|
+
actual_name = Hash === name ? name.keys.first : name
|
101
|
+
unless Rake.application.last_comment
|
102
|
+
desc "Analyze code coverage with tests" +
|
103
|
+
(@name==:rcov ? "" : " for #{actual_name}")
|
104
|
+
end
|
105
|
+
task @name do
|
106
|
+
run_code = ''
|
107
|
+
RakeFileUtils.verbose(@verbose) do
|
108
|
+
run_code =
|
109
|
+
case rcov_path
|
110
|
+
when nil, ''
|
111
|
+
"-S rcov"
|
112
|
+
else %!"#{rcov_path}"!
|
113
|
+
end
|
114
|
+
ruby_opts = @ruby_opts.clone
|
115
|
+
ruby_opts.push( "-I#{lib_path}" )
|
116
|
+
ruby_opts.push run_code
|
117
|
+
ruby_opts.push( "-w" ) if @warning
|
118
|
+
ruby ruby_opts.join(" ") + " " + option_list +
|
119
|
+
%[ -o "#{@output_dir}" ] +
|
120
|
+
file_list.collect { |fn| %["#{fn}"] }.join(' ')
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
desc "Remove rcov products for #{actual_name}"
|
125
|
+
task paste("clobber_", actual_name) do
|
126
|
+
rm_r @output_dir rescue nil
|
127
|
+
end
|
128
|
+
|
129
|
+
clobber_task = paste("clobber_", actual_name)
|
130
|
+
task :clobber => [clobber_task]
|
131
|
+
|
132
|
+
task actual_name => clobber_task
|
133
|
+
self
|
134
|
+
end
|
135
|
+
|
136
|
+
def rcov_path # :nodoc:
|
137
|
+
ENV['RCOVPATH']
|
138
|
+
end
|
139
|
+
|
140
|
+
def option_list # :nodoc:
|
141
|
+
ENV['RCOVOPTS'] || @rcov_opts.join(" ") || ""
|
142
|
+
end
|
143
|
+
|
144
|
+
def file_list # :nodoc:
|
145
|
+
if ENV['TEST']
|
146
|
+
FileList[ ENV['TEST'] ]
|
147
|
+
else
|
148
|
+
result = []
|
149
|
+
result += @test_files.to_a if @test_files
|
150
|
+
result += FileList[ @pattern ].to_a if @pattern
|
151
|
+
FileList[result]
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
data/lib/rcov/report.rb
ADDED
@@ -0,0 +1,1249 @@
|
|
1
|
+
# rcov Copyright (c) 2004-2006 Mauricio Fernandez <mfp@acm.org>
|
2
|
+
# See LEGAL and LICENSE for additional licensing information.
|
3
|
+
|
4
|
+
require 'pathname'
|
5
|
+
require 'rcov/xx'
|
6
|
+
|
7
|
+
# extend XX
|
8
|
+
module XX
|
9
|
+
module XMLish
|
10
|
+
include Markup
|
11
|
+
|
12
|
+
def xmlish_ *a, &b
|
13
|
+
xx_which(XMLish){ xx_with_doc_in_effect(*a, &b)}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module Rcov
|
19
|
+
|
20
|
+
# Try to fix bugs in the REXML shipped with Ruby 1.8.6
|
21
|
+
# They affect Mac OSX 10.5.1 users and motivates endless bug reports.
|
22
|
+
begin
|
23
|
+
require 'rexml/formatters/transitive'
|
24
|
+
require 'rexml/formatter/pretty'
|
25
|
+
rescue LoadError
|
26
|
+
end
|
27
|
+
|
28
|
+
if (RUBY_VERSION == "1.8.6" || RUBY_VERSION == "1.8.7") && defined? REXML::Formatters::Transitive
|
29
|
+
class REXML::Document
|
30
|
+
remove_method :write rescue nil
|
31
|
+
def write( output=$stdout, indent=-1, trans=false, ie_hack=false )
|
32
|
+
if xml_decl.encoding != "UTF-8" && !output.kind_of?(Output)
|
33
|
+
output = Output.new( output, xml_decl.encoding )
|
34
|
+
end
|
35
|
+
formatter = if indent > -1
|
36
|
+
#if trans
|
37
|
+
REXML::Formatters::Transitive.new( indent )
|
38
|
+
#else
|
39
|
+
# REXML::Formatters::Pretty.new( indent, ie_hack )
|
40
|
+
#end
|
41
|
+
else
|
42
|
+
REXML::Formatters::Default.new( ie_hack )
|
43
|
+
end
|
44
|
+
formatter.write( self, output )
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class REXML::Formatters::Transitive
|
49
|
+
remove_method :write_element rescue nil
|
50
|
+
def write_element( node, output )
|
51
|
+
output << "<#{node.expanded_name}"
|
52
|
+
|
53
|
+
node.attributes.each_attribute do |attr|
|
54
|
+
output << " "
|
55
|
+
attr.write( output )
|
56
|
+
end unless node.attributes.empty?
|
57
|
+
|
58
|
+
if node.children.empty?
|
59
|
+
output << "/>"
|
60
|
+
else
|
61
|
+
output << ">"
|
62
|
+
# If compact and all children are text, and if the formatted output
|
63
|
+
# is less than the specified width, then try to print everything on
|
64
|
+
# one line
|
65
|
+
skip = false
|
66
|
+
@level += @indentation
|
67
|
+
node.children.each { |child|
|
68
|
+
write( child, output )
|
69
|
+
}
|
70
|
+
@level -= @indentation
|
71
|
+
output << "</#{node.expanded_name}>"
|
72
|
+
end
|
73
|
+
output << "\n"
|
74
|
+
output << ' '*@level
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class Formatter # :nodoc:
|
80
|
+
require 'pathname'
|
81
|
+
ignore_files = [
|
82
|
+
/\A#{Regexp.escape(Pathname.new(Config::CONFIG["libdir"]).cleanpath.to_s)}/,
|
83
|
+
/\btc_[^.]*.rb/,
|
84
|
+
/_test\.rb\z/,
|
85
|
+
/\btest\//,
|
86
|
+
/\bvendor\//,
|
87
|
+
/\A#{Regexp.escape(__FILE__)}\z/]
|
88
|
+
DEFAULT_OPTS = {:ignore => ignore_files, :sort => :name, :sort_reverse => false,
|
89
|
+
:output_threshold => 101, :dont_ignore => [],
|
90
|
+
:callsite_analyzer => nil, :comments_run_by_default => false}
|
91
|
+
def initialize(opts = {})
|
92
|
+
options = DEFAULT_OPTS.clone.update(opts)
|
93
|
+
@files = {}
|
94
|
+
@ignore_files = options[:ignore]
|
95
|
+
@dont_ignore_files = options[:dont_ignore]
|
96
|
+
@sort_criterium = case options[:sort]
|
97
|
+
when :loc : lambda{|fname, finfo| finfo.num_code_lines}
|
98
|
+
when :coverage : lambda{|fname, finfo| finfo.code_coverage}
|
99
|
+
else lambda{|fname, finfo| fname}
|
100
|
+
end
|
101
|
+
@sort_reverse = options[:sort_reverse]
|
102
|
+
@output_threshold = options[:output_threshold]
|
103
|
+
@callsite_analyzer = options[:callsite_analyzer]
|
104
|
+
@comments_run_by_default = options[:comments_run_by_default]
|
105
|
+
@callsite_index = nil
|
106
|
+
|
107
|
+
@mangle_filename = Hash.new{|h,base|
|
108
|
+
h[base] = Pathname.new(base).cleanpath.to_s.gsub(%r{^\w:[/\\]}, "").gsub(/\./, "_").gsub(/[\\\/]/, "-") + ".html"
|
109
|
+
}
|
110
|
+
end
|
111
|
+
|
112
|
+
def add_file(filename, lines, coverage, counts)
|
113
|
+
old_filename = filename
|
114
|
+
filename = normalize_filename(filename)
|
115
|
+
SCRIPT_LINES__[filename] = SCRIPT_LINES__[old_filename]
|
116
|
+
if @ignore_files.any?{|x| x === filename} &&
|
117
|
+
!@dont_ignore_files.any?{|x| x === filename}
|
118
|
+
return nil
|
119
|
+
end
|
120
|
+
if @files[filename]
|
121
|
+
@files[filename].merge(lines, coverage, counts)
|
122
|
+
else
|
123
|
+
@files[filename] = FileStatistics.new(filename, lines, counts,
|
124
|
+
@comments_run_by_default)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def normalize_filename(filename)
|
129
|
+
File.expand_path(filename).gsub(/^#{Regexp.escape(Dir.getwd)}\//, '')
|
130
|
+
end
|
131
|
+
|
132
|
+
def mangle_filename(base)
|
133
|
+
@mangle_filename[base]
|
134
|
+
end
|
135
|
+
|
136
|
+
def each_file_pair_sorted(&b)
|
137
|
+
return sorted_file_pairs unless block_given?
|
138
|
+
sorted_file_pairs.each(&b)
|
139
|
+
end
|
140
|
+
|
141
|
+
def sorted_file_pairs
|
142
|
+
pairs = @files.sort_by do |fname, finfo|
|
143
|
+
@sort_criterium.call(fname, finfo)
|
144
|
+
end.select{|_, finfo| 100 * finfo.code_coverage < @output_threshold}
|
145
|
+
@sort_reverse ? pairs.reverse : pairs
|
146
|
+
end
|
147
|
+
|
148
|
+
def total_coverage
|
149
|
+
lines = 0
|
150
|
+
total = 0.0
|
151
|
+
@files.each do |k,f|
|
152
|
+
total += f.num_lines * f.total_coverage
|
153
|
+
lines += f.num_lines
|
154
|
+
end
|
155
|
+
return 0 if lines == 0
|
156
|
+
total / lines
|
157
|
+
end
|
158
|
+
|
159
|
+
def code_coverage
|
160
|
+
lines = 0
|
161
|
+
total = 0.0
|
162
|
+
@files.each do |k,f|
|
163
|
+
total += f.num_code_lines * f.code_coverage
|
164
|
+
lines += f.num_code_lines
|
165
|
+
end
|
166
|
+
return 0 if lines == 0
|
167
|
+
total / lines
|
168
|
+
end
|
169
|
+
|
170
|
+
def num_code_lines
|
171
|
+
lines = 0
|
172
|
+
@files.each{|k, f| lines += f.num_code_lines }
|
173
|
+
lines
|
174
|
+
end
|
175
|
+
|
176
|
+
def num_lines
|
177
|
+
lines = 0
|
178
|
+
@files.each{|k, f| lines += f.num_lines }
|
179
|
+
lines
|
180
|
+
end
|
181
|
+
|
182
|
+
private
|
183
|
+
def cross_references_for(filename, lineno)
|
184
|
+
return nil unless @callsite_analyzer
|
185
|
+
@callsite_index ||= build_callsite_index
|
186
|
+
@callsite_index[normalize_filename(filename)][lineno]
|
187
|
+
end
|
188
|
+
|
189
|
+
def reverse_cross_references_for(filename, lineno)
|
190
|
+
return nil unless @callsite_analyzer
|
191
|
+
@callsite_reverse_index ||= build_reverse_callsite_index
|
192
|
+
@callsite_reverse_index[normalize_filename(filename)][lineno]
|
193
|
+
end
|
194
|
+
|
195
|
+
def build_callsite_index
|
196
|
+
index = Hash.new{|h,k| h[k] = {}}
|
197
|
+
@callsite_analyzer.analyzed_classes.each do |classname|
|
198
|
+
@callsite_analyzer.analyzed_methods(classname).each do |methname|
|
199
|
+
defsite = @callsite_analyzer.defsite(classname, methname)
|
200
|
+
index[normalize_filename(defsite.file)][defsite.line] =
|
201
|
+
@callsite_analyzer.callsites(classname, methname)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
index
|
205
|
+
end
|
206
|
+
|
207
|
+
def build_reverse_callsite_index
|
208
|
+
index = Hash.new{|h,k| h[k] = {}}
|
209
|
+
@callsite_analyzer.analyzed_classes.each do |classname|
|
210
|
+
@callsite_analyzer.analyzed_methods(classname).each do |methname|
|
211
|
+
callsites = @callsite_analyzer.callsites(classname, methname)
|
212
|
+
defsite = @callsite_analyzer.defsite(classname, methname)
|
213
|
+
callsites.each_pair do |callsite, count|
|
214
|
+
next unless callsite.file
|
215
|
+
fname = normalize_filename(callsite.file)
|
216
|
+
(index[fname][callsite.line] ||= []) << [classname, methname, defsite, count]
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
index
|
221
|
+
end
|
222
|
+
|
223
|
+
class XRefHelper < Struct.new(:file, :line, :klass, :mid, :count) # :nodoc:
|
224
|
+
end
|
225
|
+
|
226
|
+
def _get_defsites(ref_blocks, filename, lineno, linetext, label, &format_call_ref)
|
227
|
+
if @do_cross_references and
|
228
|
+
(rev_xref = reverse_cross_references_for(filename, lineno))
|
229
|
+
refs = rev_xref.map do |classname, methodname, defsite, count|
|
230
|
+
XRefHelper.new(defsite.file, defsite.line, classname, methodname, count)
|
231
|
+
end.sort_by{|r| r.count}.reverse
|
232
|
+
ref_blocks << [refs, label, format_call_ref]
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def _get_callsites(ref_blocks, filename, lineno, linetext, label, &format_called_ref)
|
237
|
+
if @do_callsites and
|
238
|
+
(refs = cross_references_for(filename, lineno))
|
239
|
+
refs = refs.sort_by{|k,count| count}.map do |ref, count|
|
240
|
+
XRefHelper.new(ref.file, ref.line, ref.calling_class, ref.calling_method, count)
|
241
|
+
end.reverse
|
242
|
+
ref_blocks << [refs, label, format_called_ref]
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
class TextSummary < Formatter # :nodoc:
|
248
|
+
def execute
|
249
|
+
puts summary
|
250
|
+
end
|
251
|
+
|
252
|
+
def summary
|
253
|
+
"%.1f%% %d file(s) %d Lines %d LOC" % [code_coverage * 100,
|
254
|
+
@files.size, num_lines, num_code_lines]
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
class TextReport < TextSummary # :nodoc:
|
259
|
+
def execute
|
260
|
+
print_lines
|
261
|
+
print_header
|
262
|
+
print_lines
|
263
|
+
each_file_pair_sorted do |fname, finfo|
|
264
|
+
name = fname.size < 52 ? fname : "..." + fname[-48..-1]
|
265
|
+
print_info(name, finfo.num_lines, finfo.num_code_lines,
|
266
|
+
finfo.code_coverage)
|
267
|
+
end
|
268
|
+
print_lines
|
269
|
+
print_info("Total", num_lines, num_code_lines, code_coverage)
|
270
|
+
print_lines
|
271
|
+
puts summary
|
272
|
+
end
|
273
|
+
|
274
|
+
def print_info(name, lines, loc, coverage)
|
275
|
+
puts "|%-51s | %5d | %5d | %5.1f%% |" % [name, lines, loc, 100 * coverage]
|
276
|
+
end
|
277
|
+
|
278
|
+
def print_lines
|
279
|
+
puts "+----------------------------------------------------+-------+-------+--------+"
|
280
|
+
end
|
281
|
+
|
282
|
+
def print_header
|
283
|
+
puts "| File | Lines | LOC | COV |"
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
class FullTextReport < Formatter # :nodoc:
|
288
|
+
DEFAULT_OPTS = {:textmode => :coverage}
|
289
|
+
def initialize(opts = {})
|
290
|
+
options = DEFAULT_OPTS.clone.update(opts)
|
291
|
+
@textmode = options[:textmode]
|
292
|
+
@color = options[:color]
|
293
|
+
super(options)
|
294
|
+
end
|
295
|
+
|
296
|
+
def execute
|
297
|
+
each_file_pair_sorted do |filename, fileinfo|
|
298
|
+
puts "=" * 80
|
299
|
+
puts filename
|
300
|
+
puts "=" * 80
|
301
|
+
lines = SCRIPT_LINES__[filename]
|
302
|
+
unless lines
|
303
|
+
# try to get the source code from the global code coverage
|
304
|
+
# analyzer
|
305
|
+
re = /#{Regexp.escape(filename)}\z/
|
306
|
+
if $rcov_code_coverage_analyzer and
|
307
|
+
(data = $rcov_code_coverage_analyzer.data_matching(re))
|
308
|
+
lines = data[0]
|
309
|
+
end
|
310
|
+
end
|
311
|
+
(lines || []).each_with_index do |line, i|
|
312
|
+
case @textmode
|
313
|
+
when :counts
|
314
|
+
puts "%-70s| %6d" % [line.chomp[0,70], fileinfo.counts[i]]
|
315
|
+
when :gcc
|
316
|
+
puts "%s:%d:%s" % [filename, i+1, line.chomp] unless fileinfo.coverage[i]
|
317
|
+
when :coverage
|
318
|
+
if @color
|
319
|
+
prefix = fileinfo.coverage[i] ? "\e[32;40m" : "\e[31;40m"
|
320
|
+
puts "#{prefix}%s\e[37;40m" % line.chomp
|
321
|
+
else
|
322
|
+
prefix = fileinfo.coverage[i] ? " " : "!! "
|
323
|
+
puts "#{prefix}#{line}"
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
class TextCoverageDiff < Formatter # :nodoc:
|
332
|
+
FORMAT_VERSION = [0, 1, 0]
|
333
|
+
DEFAULT_OPTS = {:textmode => :coverage_diff,
|
334
|
+
:coverage_diff_mode => :record,
|
335
|
+
:coverage_diff_file => "coverage.info",
|
336
|
+
:diff_cmd => "diff", :comments_run_by_default => true}
|
337
|
+
def SERIALIZER
|
338
|
+
# mfp> this was going to be YAML but I caught it failing at basic
|
339
|
+
# round-tripping, turning "\n" into "" and corrupting the data, so
|
340
|
+
# it must be Marshal for now
|
341
|
+
Marshal
|
342
|
+
end
|
343
|
+
|
344
|
+
def initialize(opts = {})
|
345
|
+
options = DEFAULT_OPTS.clone.update(opts)
|
346
|
+
@textmode = options[:textmode]
|
347
|
+
@color = options[:color]
|
348
|
+
@mode = options[:coverage_diff_mode]
|
349
|
+
@state_file = options[:coverage_diff_file]
|
350
|
+
@diff_cmd = options[:diff_cmd]
|
351
|
+
@gcc_output = options[:gcc_output]
|
352
|
+
super(options)
|
353
|
+
end
|
354
|
+
|
355
|
+
def execute
|
356
|
+
case @mode
|
357
|
+
when :record
|
358
|
+
record_state
|
359
|
+
when :compare
|
360
|
+
compare_state
|
361
|
+
else
|
362
|
+
raise "Unknown TextCoverageDiff mode: #{mode.inspect}."
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
def record_state
|
367
|
+
state = {}
|
368
|
+
each_file_pair_sorted do |filename, fileinfo|
|
369
|
+
state[filename] = {:lines => SCRIPT_LINES__[filename],
|
370
|
+
:coverage => fileinfo.coverage.to_a,
|
371
|
+
:counts => fileinfo.counts}
|
372
|
+
end
|
373
|
+
File.open(@state_file, "w") do |f|
|
374
|
+
self.SERIALIZER.dump([FORMAT_VERSION, state], f)
|
375
|
+
end
|
376
|
+
rescue
|
377
|
+
$stderr.puts <<-EOF
|
378
|
+
Couldn't save coverage data to #{@state_file}.
|
379
|
+
EOF
|
380
|
+
end # '
|
381
|
+
|
382
|
+
require 'tempfile'
|
383
|
+
def compare_state
|
384
|
+
return unless verify_diff_available
|
385
|
+
begin
|
386
|
+
format, prev_state = File.open(@state_file){|f| self.SERIALIZER.load(f) }
|
387
|
+
rescue
|
388
|
+
$stderr.puts <<-EOF
|
389
|
+
Couldn't load coverage data from #{@state_file}.
|
390
|
+
EOF
|
391
|
+
return # '
|
392
|
+
end
|
393
|
+
if !(Array === format) or
|
394
|
+
FORMAT_VERSION[0] != format[0] || FORMAT_VERSION[1] < format[1]
|
395
|
+
$stderr.puts <<-EOF
|
396
|
+
Couldn't load coverage data from #{@state_file}.
|
397
|
+
The file is saved in the format #{format.inspect[0..20]}.
|
398
|
+
This rcov executable understands #{FORMAT_VERSION.inspect}.
|
399
|
+
EOF
|
400
|
+
return # '
|
401
|
+
end
|
402
|
+
each_file_pair_sorted do |filename, fileinfo|
|
403
|
+
old_data = Tempfile.new("#{mangle_filename(filename)}-old")
|
404
|
+
new_data = Tempfile.new("#{mangle_filename(filename)}-new")
|
405
|
+
if prev_state.has_key? filename
|
406
|
+
old_code, old_cov = prev_state[filename].values_at(:lines, :coverage)
|
407
|
+
old_code.each_with_index do |line, i|
|
408
|
+
prefix = old_cov[i] ? " " : "!! "
|
409
|
+
old_data.write "#{prefix}#{line}"
|
410
|
+
end
|
411
|
+
else
|
412
|
+
old_data.write ""
|
413
|
+
end
|
414
|
+
old_data.close
|
415
|
+
SCRIPT_LINES__[filename].each_with_index do |line, i|
|
416
|
+
prefix = fileinfo.coverage[i] ? " " : "!! "
|
417
|
+
new_data.write "#{prefix}#{line}"
|
418
|
+
end
|
419
|
+
new_data.close
|
420
|
+
|
421
|
+
diff = `#{@diff_cmd} -u "#{old_data.path}" "#{new_data.path}"`
|
422
|
+
new_uncovered_hunks = process_unified_diff(filename, diff)
|
423
|
+
old_data.close!
|
424
|
+
new_data.close!
|
425
|
+
display_hunks(filename, new_uncovered_hunks)
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
def display_hunks(filename, hunks)
|
430
|
+
return if hunks.empty?
|
431
|
+
puts
|
432
|
+
puts "=" * 80
|
433
|
+
puts <<EOF
|
434
|
+
!!!!! Uncovered code introduced in #{filename}
|
435
|
+
|
436
|
+
EOF
|
437
|
+
hunks.each do |offset, lines|
|
438
|
+
if @gcc_output
|
439
|
+
lines.each_with_index do |line,i|
|
440
|
+
lineno = offset + i
|
441
|
+
flag = (/^!! / !~ line) ? "-" : ":"
|
442
|
+
prefix = "#{filename}#{flag}#{lineno}#{flag}"
|
443
|
+
puts "#{prefix}#{line[3..-1]}"
|
444
|
+
end
|
445
|
+
elsif @color
|
446
|
+
puts "### #{filename}:#{offset}"
|
447
|
+
lines.each do |line|
|
448
|
+
prefix = (/^!! / !~ line) ? "\e[32;40m" : "\e[31;40m"
|
449
|
+
puts "#{prefix}#{line[3..-1].chomp}\e[37;40m"
|
450
|
+
end
|
451
|
+
else
|
452
|
+
puts "### #{filename}:#{offset}"
|
453
|
+
puts lines
|
454
|
+
end
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
def verify_diff_available
|
459
|
+
old_stderr = STDERR.dup
|
460
|
+
old_stdout = STDOUT.dup
|
461
|
+
# TODO: should use /dev/null or NUL(?), but I don't want to add the
|
462
|
+
# win32 check right now
|
463
|
+
new_stderr = Tempfile.new("rcov_check_diff")
|
464
|
+
STDERR.reopen new_stderr.path
|
465
|
+
STDOUT.reopen new_stderr.path
|
466
|
+
|
467
|
+
retval = system "#{@diff_cmd} --version"
|
468
|
+
unless retval
|
469
|
+
old_stderr.puts <<EOF
|
470
|
+
|
471
|
+
The '#{@diff_cmd}' executable seems not to be available.
|
472
|
+
You can specify which diff executable should be used with --diff-cmd.
|
473
|
+
If your system doesn't have one, you might want to use Diff::LCS's:
|
474
|
+
gem install diff-lcs
|
475
|
+
and use --diff-cmd=ldiff.
|
476
|
+
EOF
|
477
|
+
return false
|
478
|
+
end
|
479
|
+
true
|
480
|
+
ensure
|
481
|
+
STDOUT.reopen old_stdout
|
482
|
+
STDERR.reopen old_stderr
|
483
|
+
new_stderr.close!
|
484
|
+
end
|
485
|
+
|
486
|
+
HUNK_HEADER = /@@ -\d+,\d+ \+(\d+),(\d+) @@/
|
487
|
+
def process_unified_diff(filename, diff)
|
488
|
+
current_hunk = []
|
489
|
+
current_hunk_start = 0
|
490
|
+
keep_current_hunk = false
|
491
|
+
state = :init
|
492
|
+
interesting_hunks = []
|
493
|
+
diff.each_with_index do |line, i|
|
494
|
+
#puts "#{state} %5d #{line}" % i
|
495
|
+
case state
|
496
|
+
when :init
|
497
|
+
if md = HUNK_HEADER.match(line)
|
498
|
+
current_hunk = []
|
499
|
+
current_hunk_start = md[1].to_i
|
500
|
+
state = :body
|
501
|
+
end
|
502
|
+
when :body
|
503
|
+
case line
|
504
|
+
when HUNK_HEADER
|
505
|
+
new_start = $1.to_i
|
506
|
+
if keep_current_hunk
|
507
|
+
interesting_hunks << [current_hunk_start, current_hunk]
|
508
|
+
end
|
509
|
+
current_hunk_start = new_start
|
510
|
+
current_hunk = []
|
511
|
+
keep_current_hunk = false
|
512
|
+
when /^-/
|
513
|
+
# ignore
|
514
|
+
when /^\+!! /
|
515
|
+
keep_current_hunk = true
|
516
|
+
current_hunk << line[1..-1]
|
517
|
+
else
|
518
|
+
current_hunk << line[1..-1]
|
519
|
+
end
|
520
|
+
end
|
521
|
+
end
|
522
|
+
if keep_current_hunk
|
523
|
+
interesting_hunks << [current_hunk_start, current_hunk]
|
524
|
+
end
|
525
|
+
|
526
|
+
interesting_hunks
|
527
|
+
end
|
528
|
+
end
|
529
|
+
|
530
|
+
|
531
|
+
class HTMLCoverage < Formatter # :nodoc:
|
532
|
+
include XX::XHTML
|
533
|
+
include XX::XMLish
|
534
|
+
require 'fileutils'
|
535
|
+
JAVASCRIPT_PROLOG = <<-EOS
|
536
|
+
|
537
|
+
// <![CDATA[
|
538
|
+
function toggleCode( id ) {
|
539
|
+
if ( document.getElementById )
|
540
|
+
elem = document.getElementById( id );
|
541
|
+
else if ( document.all )
|
542
|
+
elem = eval( "document.all." + id );
|
543
|
+
else
|
544
|
+
return false;
|
545
|
+
|
546
|
+
elemStyle = elem.style;
|
547
|
+
|
548
|
+
if ( elemStyle.display != "block" ) {
|
549
|
+
elemStyle.display = "block"
|
550
|
+
} else {
|
551
|
+
elemStyle.display = "none"
|
552
|
+
}
|
553
|
+
|
554
|
+
return true;
|
555
|
+
}
|
556
|
+
|
557
|
+
// Make cross-references hidden by default
|
558
|
+
document.writeln( "<style type=\\"text/css\\">span.cross-ref { display: none }</style>" )
|
559
|
+
// ]]>
|
560
|
+
EOS
|
561
|
+
|
562
|
+
CSS_PROLOG = <<-EOS
|
563
|
+
span.cross-ref-title {
|
564
|
+
font-size: 140%;
|
565
|
+
}
|
566
|
+
span.cross-ref a {
|
567
|
+
text-decoration: none;
|
568
|
+
}
|
569
|
+
span.cross-ref {
|
570
|
+
background-color:#f3f7fa;
|
571
|
+
border: 1px dashed #333;
|
572
|
+
margin: 1em;
|
573
|
+
padding: 0.5em;
|
574
|
+
overflow: hidden;
|
575
|
+
}
|
576
|
+
a.crossref-toggle {
|
577
|
+
text-decoration: none;
|
578
|
+
}
|
579
|
+
span.marked0 {
|
580
|
+
background-color: rgb(185, 210, 200);
|
581
|
+
display: block;
|
582
|
+
}
|
583
|
+
span.marked1 {
|
584
|
+
background-color: rgb(190, 215, 205);
|
585
|
+
display: block;
|
586
|
+
}
|
587
|
+
span.inferred0 {
|
588
|
+
background-color: rgb(255, 255, 240);
|
589
|
+
display: block;
|
590
|
+
}
|
591
|
+
span.inferred1 {
|
592
|
+
background-color: rgb(255, 255, 240);
|
593
|
+
display: block;
|
594
|
+
}
|
595
|
+
span.uncovered0 {
|
596
|
+
background-color: rgb(225, 110, 110);
|
597
|
+
display: block;
|
598
|
+
}
|
599
|
+
span.uncovered1 {
|
600
|
+
background-color: rgb(235, 120, 120);
|
601
|
+
display: block;
|
602
|
+
}
|
603
|
+
span.overview {
|
604
|
+
border-bottom: 8px solid black;
|
605
|
+
}
|
606
|
+
div.overview {
|
607
|
+
border-bottom: 8px solid black;
|
608
|
+
}
|
609
|
+
body {
|
610
|
+
font-family: verdana, arial, helvetica;
|
611
|
+
}
|
612
|
+
|
613
|
+
div.footer {
|
614
|
+
font-size: 68%;
|
615
|
+
margin-top: 1.5em;
|
616
|
+
}
|
617
|
+
|
618
|
+
h1, h2, h3, h4, h5, h6 {
|
619
|
+
margin-bottom: 0.5em;
|
620
|
+
}
|
621
|
+
|
622
|
+
h5 {
|
623
|
+
margin-top: 0.5em;
|
624
|
+
}
|
625
|
+
|
626
|
+
.hidden {
|
627
|
+
display: none;
|
628
|
+
}
|
629
|
+
|
630
|
+
div.separator {
|
631
|
+
height: 10px;
|
632
|
+
}
|
633
|
+
/* Commented out for better readability, esp. on IE */
|
634
|
+
/*
|
635
|
+
table tr td, table tr th {
|
636
|
+
font-size: 68%;
|
637
|
+
}
|
638
|
+
|
639
|
+
td.value table tr td {
|
640
|
+
font-size: 11px;
|
641
|
+
}
|
642
|
+
*/
|
643
|
+
|
644
|
+
table.percent_graph {
|
645
|
+
height: 12px;
|
646
|
+
border: #808080 1px solid;
|
647
|
+
empty-cells: show;
|
648
|
+
}
|
649
|
+
|
650
|
+
table.percent_graph td.covered {
|
651
|
+
height: 10px;
|
652
|
+
background: #00f000;
|
653
|
+
}
|
654
|
+
|
655
|
+
table.percent_graph td.uncovered {
|
656
|
+
height: 10px;
|
657
|
+
background: #e00000;
|
658
|
+
}
|
659
|
+
|
660
|
+
table.percent_graph td.NA {
|
661
|
+
height: 10px;
|
662
|
+
background: #eaeaea;
|
663
|
+
}
|
664
|
+
|
665
|
+
table.report {
|
666
|
+
border-collapse: collapse;
|
667
|
+
width: 100%;
|
668
|
+
}
|
669
|
+
|
670
|
+
table.report td.heading {
|
671
|
+
background: #dcecff;
|
672
|
+
border: #d0d0d0 1px solid;
|
673
|
+
font-weight: bold;
|
674
|
+
text-align: center;
|
675
|
+
}
|
676
|
+
|
677
|
+
table.report td.heading:hover {
|
678
|
+
background: #c0ffc0;
|
679
|
+
}
|
680
|
+
|
681
|
+
table.report td.text {
|
682
|
+
border: #d0d0d0 1px solid;
|
683
|
+
}
|
684
|
+
|
685
|
+
table.report td.value,
|
686
|
+
table.report td.lines_total,
|
687
|
+
table.report td.lines_code {
|
688
|
+
text-align: right;
|
689
|
+
border: #d0d0d0 1px solid;
|
690
|
+
}
|
691
|
+
table.report tr.light {
|
692
|
+
background-color: rgb(240, 240, 245);
|
693
|
+
}
|
694
|
+
table.report tr.dark {
|
695
|
+
background-color: rgb(230, 230, 235);
|
696
|
+
}
|
697
|
+
EOS
|
698
|
+
|
699
|
+
DEFAULT_OPTS = {:color => false, :fsr => 30, :destdir => "coverage",
|
700
|
+
:callsites => false, :cross_references => false,
|
701
|
+
:validator_links => true, :charset => nil
|
702
|
+
}
|
703
|
+
def initialize(opts = {})
|
704
|
+
options = DEFAULT_OPTS.clone.update(opts)
|
705
|
+
super(options)
|
706
|
+
@dest = options[:destdir]
|
707
|
+
@color = options[:color]
|
708
|
+
@fsr = options[:fsr]
|
709
|
+
@do_callsites = options[:callsites]
|
710
|
+
@do_cross_references = options[:cross_references]
|
711
|
+
@span_class_index = 0
|
712
|
+
@show_validator_links = options[:validator_links]
|
713
|
+
@charset = options[:charset]
|
714
|
+
end
|
715
|
+
|
716
|
+
def execute
|
717
|
+
return if @files.empty?
|
718
|
+
FileUtils.mkdir_p @dest
|
719
|
+
create_index(File.join(@dest, "index.html"))
|
720
|
+
each_file_pair_sorted do |filename, fileinfo|
|
721
|
+
create_file(File.join(@dest, mangle_filename(filename)), fileinfo)
|
722
|
+
end
|
723
|
+
end
|
724
|
+
|
725
|
+
private
|
726
|
+
|
727
|
+
def blurb
|
728
|
+
xmlish_ {
|
729
|
+
p_ {
|
730
|
+
t_{ "Generated using the " }
|
731
|
+
a_(:href => "http://eigenclass.org/hiki.rb?rcov") {
|
732
|
+
t_{ "rcov code coverage analysis tool for Ruby" }
|
733
|
+
}
|
734
|
+
t_{ " version #{Rcov::VERSION}." }
|
735
|
+
}
|
736
|
+
}.pretty
|
737
|
+
end
|
738
|
+
|
739
|
+
def output_color_table?
|
740
|
+
true
|
741
|
+
end
|
742
|
+
|
743
|
+
def default_color
|
744
|
+
"rgb(240, 240, 245)"
|
745
|
+
end
|
746
|
+
|
747
|
+
def default_title
|
748
|
+
"C0 code coverage information"
|
749
|
+
end
|
750
|
+
|
751
|
+
def format_overview(*file_infos)
|
752
|
+
table_text = xmlish_ {
|
753
|
+
table_(:class => "report") {
|
754
|
+
thead_ {
|
755
|
+
tr_ {
|
756
|
+
["Name", "Total lines", "Lines of code", "Total coverage",
|
757
|
+
"Code coverage"].each do |heading|
|
758
|
+
td_(:class => "heading") { heading }
|
759
|
+
end
|
760
|
+
}
|
761
|
+
}
|
762
|
+
tbody_ {
|
763
|
+
color_class_index = 1
|
764
|
+
color_classes = %w[light dark]
|
765
|
+
file_infos.each do |f|
|
766
|
+
color_class_index += 1
|
767
|
+
color_class_index %= color_classes.size
|
768
|
+
tr_(:class => color_classes[color_class_index]) {
|
769
|
+
td_ {
|
770
|
+
case f.name
|
771
|
+
when "TOTAL":
|
772
|
+
t_ { "TOTAL" }
|
773
|
+
else
|
774
|
+
a_(:href => mangle_filename(f.name)){ t_ { f.name } }
|
775
|
+
end
|
776
|
+
}
|
777
|
+
[[f.num_lines, "lines_total"],
|
778
|
+
[f.num_code_lines, "lines_code"]].each do |value, css_class|
|
779
|
+
td_(:class => css_class) { tt_{ value } }
|
780
|
+
end
|
781
|
+
[[f.total_coverage, "coverage_total"],
|
782
|
+
[f.code_coverage, "coverage_code"]].each do |value, css_class|
|
783
|
+
value *= 100
|
784
|
+
td_ {
|
785
|
+
table_(:cellpadding => "0", :cellspacing => "0", :align => "right") {
|
786
|
+
tr_ {
|
787
|
+
td_ {
|
788
|
+
tt_(:class => css_class) { "%3.1f%%" % value }
|
789
|
+
x_ " "
|
790
|
+
}
|
791
|
+
ivalue = value.round
|
792
|
+
td_ {
|
793
|
+
table_(:class => "percent_graph", :cellpadding => "0",
|
794
|
+
:cellspacing => "0", :width => "100") {
|
795
|
+
tr_ {
|
796
|
+
td_(:class => "covered", :width => ivalue.to_s)
|
797
|
+
td_(:class => "uncovered", :width => (100-ivalue).to_s)
|
798
|
+
}
|
799
|
+
}
|
800
|
+
}
|
801
|
+
}
|
802
|
+
}
|
803
|
+
}
|
804
|
+
end
|
805
|
+
}
|
806
|
+
end
|
807
|
+
}
|
808
|
+
}
|
809
|
+
}
|
810
|
+
table_text.pretty
|
811
|
+
end
|
812
|
+
|
813
|
+
class SummaryFileInfo # :nodoc:
|
814
|
+
def initialize(obj); @o = obj end
|
815
|
+
%w[num_lines num_code_lines code_coverage total_coverage].each do |m|
|
816
|
+
define_method(m){ @o.send(m) }
|
817
|
+
end
|
818
|
+
def name; "TOTAL" end
|
819
|
+
end
|
820
|
+
|
821
|
+
def create_index(destname)
|
822
|
+
files = [SummaryFileInfo.new(self)] + each_file_pair_sorted.map{|k,v| v}
|
823
|
+
title = default_title
|
824
|
+
output = xhtml_ { html_ {
|
825
|
+
head_ {
|
826
|
+
if @charset
|
827
|
+
meta_("http-equiv".to_sym => "Content-Type",
|
828
|
+
:content => "text/html;charset=#{@charset}")
|
829
|
+
end
|
830
|
+
title_{ title }
|
831
|
+
style_(:type => "text/css") { t_{ "body { background-color: #{default_color}; }" } }
|
832
|
+
style_(:type => "text/css") { CSS_PROLOG }
|
833
|
+
script_(:type => "text/javascript") { h_{ JAVASCRIPT_PROLOG } }
|
834
|
+
}
|
835
|
+
body_ {
|
836
|
+
h3_{
|
837
|
+
t_{ title }
|
838
|
+
}
|
839
|
+
p_ {
|
840
|
+
t_{ "Generated on #{Time.new.to_s} with " }
|
841
|
+
a_(:href => Rcov::UPSTREAM_URL){ "rcov #{Rcov::VERSION}" }
|
842
|
+
}
|
843
|
+
p_ { "Threshold: #{@output_threshold}%" } if @output_threshold != 101
|
844
|
+
hr_
|
845
|
+
x_{ format_overview(*files) }
|
846
|
+
hr_
|
847
|
+
x_{ blurb }
|
848
|
+
|
849
|
+
if @show_validator_links
|
850
|
+
p_ {
|
851
|
+
a_(:href => "http://validator.w3.org/check/referer") {
|
852
|
+
img_(:src => "http://www.w3.org/Icons/valid-xhtml11",
|
853
|
+
:alt => "Valid XHTML 1.1!", :height => "31", :width => "88")
|
854
|
+
}
|
855
|
+
a_(:href => "http://jigsaw.w3.org/css-validator/check/referer") {
|
856
|
+
img_(:style => "border:0;width:88px;height:31px",
|
857
|
+
:src => "http://jigsaw.w3.org/css-validator/images/vcss",
|
858
|
+
:alt => "Valid CSS!")
|
859
|
+
}
|
860
|
+
}
|
861
|
+
end
|
862
|
+
}
|
863
|
+
} }
|
864
|
+
lines = output.pretty.to_a
|
865
|
+
lines.unshift lines.pop if /DOCTYPE/ =~ lines[-1]
|
866
|
+
File.open(destname, "w") do |f|
|
867
|
+
f.puts lines
|
868
|
+
end
|
869
|
+
end
|
870
|
+
|
871
|
+
def format_lines(file)
|
872
|
+
result = ""
|
873
|
+
last = nil
|
874
|
+
end_of_span = ""
|
875
|
+
format_line = "%#{file.num_lines.to_s.size}d"
|
876
|
+
file.num_lines.times do |i|
|
877
|
+
line = file.lines[i].chomp
|
878
|
+
marked = file.coverage[i]
|
879
|
+
count = file.counts[i]
|
880
|
+
spanclass = span_class(file, marked, count)
|
881
|
+
if spanclass != last
|
882
|
+
result += end_of_span
|
883
|
+
case spanclass
|
884
|
+
when nil
|
885
|
+
end_of_span = ""
|
886
|
+
else
|
887
|
+
result += %[<span class="#{spanclass}">]
|
888
|
+
end_of_span = "</span>"
|
889
|
+
end
|
890
|
+
end
|
891
|
+
result += %[<a name="line#{i+1}"></a>] + (format_line % (i+1)) +
|
892
|
+
" " + create_cross_refs(file.name, i+1, CGI.escapeHTML(line)) + "\n"
|
893
|
+
last = spanclass
|
894
|
+
end
|
895
|
+
result += end_of_span
|
896
|
+
"<pre>#{result}</pre>"
|
897
|
+
end
|
898
|
+
|
899
|
+
def create_cross_refs(filename, lineno, linetext)
|
900
|
+
return linetext unless @callsite_analyzer && @do_callsites
|
901
|
+
ref_blocks = []
|
902
|
+
_get_defsites(ref_blocks, filename, lineno, "Calls", linetext) do |ref|
|
903
|
+
if ref.file
|
904
|
+
where = "at #{normalize_filename(ref.file)}:#{ref.line}"
|
905
|
+
else
|
906
|
+
where = "(C extension/core)"
|
907
|
+
end
|
908
|
+
CGI.escapeHTML("%7d %s" %
|
909
|
+
[ref.count, "#{ref.klass}##{ref.mid} " + where])
|
910
|
+
end
|
911
|
+
_get_callsites(ref_blocks, filename, lineno, "Called by", linetext) do |ref|
|
912
|
+
r = "%7d %s" % [ref.count,
|
913
|
+
"#{normalize_filename(ref.file||'C code')}:#{ref.line} " +
|
914
|
+
"in '#{ref.klass}##{ref.mid}'"]
|
915
|
+
CGI.escapeHTML(r)
|
916
|
+
end
|
917
|
+
|
918
|
+
create_cross_reference_block(linetext, ref_blocks)
|
919
|
+
end
|
920
|
+
|
921
|
+
def create_cross_reference_block(linetext, ref_blocks)
|
922
|
+
return linetext if ref_blocks.empty?
|
923
|
+
ret = ""
|
924
|
+
@cross_ref_idx ||= 0
|
925
|
+
@known_files ||= sorted_file_pairs.map{|fname, finfo| normalize_filename(fname)}
|
926
|
+
ret << %[<a class="crossref-toggle" href="#" onclick="toggleCode('XREF-#{@cross_ref_idx+=1}'); return false;">#{linetext}</a>]
|
927
|
+
ret << %[<span class="cross-ref" id="XREF-#{@cross_ref_idx}">]
|
928
|
+
ret << "\n"
|
929
|
+
ref_blocks.each do |refs, toplabel, label_proc|
|
930
|
+
unless !toplabel || toplabel.empty?
|
931
|
+
ret << %!<span class="cross-ref-title">#{toplabel}</span>\n!
|
932
|
+
end
|
933
|
+
refs.each do |dst|
|
934
|
+
dstfile = normalize_filename(dst.file) if dst.file
|
935
|
+
dstline = dst.line
|
936
|
+
label = label_proc.call(dst)
|
937
|
+
if dst.file && @known_files.include?(dstfile)
|
938
|
+
ret << %[<a href="#{mangle_filename(dstfile)}#line#{dstline}">#{label}</a>]
|
939
|
+
else
|
940
|
+
ret << label
|
941
|
+
end
|
942
|
+
ret << "\n"
|
943
|
+
end
|
944
|
+
end
|
945
|
+
ret << "</span>"
|
946
|
+
end
|
947
|
+
|
948
|
+
def span_class(sourceinfo, marked, count)
|
949
|
+
@span_class_index ^= 1
|
950
|
+
case marked
|
951
|
+
when true
|
952
|
+
"marked#{@span_class_index}"
|
953
|
+
when :inferred
|
954
|
+
"inferred#{@span_class_index}"
|
955
|
+
else
|
956
|
+
"uncovered#{@span_class_index}"
|
957
|
+
end
|
958
|
+
end
|
959
|
+
|
960
|
+
def create_file(destfile, fileinfo)
|
961
|
+
#$stderr.puts "Generating #{destfile.inspect}"
|
962
|
+
body = format_overview(fileinfo) + format_lines(fileinfo)
|
963
|
+
title = fileinfo.name + " - #{default_title}"
|
964
|
+
do_ctable = output_color_table?
|
965
|
+
output = xhtml_ { html_ {
|
966
|
+
head_ {
|
967
|
+
if @charset
|
968
|
+
meta_("http-equiv".to_sym => "Content-Type",
|
969
|
+
:content => "text/html;charset=#{@charset}")
|
970
|
+
end
|
971
|
+
title_{ title }
|
972
|
+
style_(:type => "text/css") { t_{ "body { background-color: #{default_color}; }" } }
|
973
|
+
style_(:type => "text/css") { CSS_PROLOG }
|
974
|
+
script_(:type => "text/javascript") { h_ { JAVASCRIPT_PROLOG } }
|
975
|
+
style_(:type => "text/css") { h_ { colorscale } }
|
976
|
+
}
|
977
|
+
body_ {
|
978
|
+
h3_{ t_{ default_title } }
|
979
|
+
p_ {
|
980
|
+
t_{ "Generated on #{Time.new.to_s} with " }
|
981
|
+
a_(:href => Rcov::UPSTREAM_URL){ "rcov #{Rcov::VERSION}" }
|
982
|
+
}
|
983
|
+
hr_
|
984
|
+
if do_ctable
|
985
|
+
# this kludge needed to ensure .pretty doesn't mangle it
|
986
|
+
x_ { <<EOS
|
987
|
+
<pre><span class='marked0'>Code reported as executed by Ruby looks like this...
|
988
|
+
</span><span class='marked1'>and this: this line is also marked as covered.
|
989
|
+
</span><span class='inferred0'>Lines considered as run by rcov, but not reported by Ruby, look like this,
|
990
|
+
</span><span class='inferred1'>and this: these lines were inferred by rcov (using simple heuristics).
|
991
|
+
</span><span class='uncovered0'>Finally, here's a line marked as not executed.
|
992
|
+
</span></pre>
|
993
|
+
EOS
|
994
|
+
}
|
995
|
+
end
|
996
|
+
x_{ body }
|
997
|
+
hr_
|
998
|
+
x_ { blurb }
|
999
|
+
|
1000
|
+
if @show_validator_links
|
1001
|
+
p_ {
|
1002
|
+
a_(:href => "http://validator.w3.org/check/referer") {
|
1003
|
+
img_(:src => "http://www.w3.org/Icons/valid-xhtml10",
|
1004
|
+
:alt => "Valid XHTML 1.0!", :height => "31", :width => "88")
|
1005
|
+
}
|
1006
|
+
a_(:href => "http://jigsaw.w3.org/css-validator/check/referer") {
|
1007
|
+
img_(:style => "border:0;width:88px;height:31px",
|
1008
|
+
:src => "http://jigsaw.w3.org/css-validator/images/vcss",
|
1009
|
+
:alt => "Valid CSS!")
|
1010
|
+
}
|
1011
|
+
}
|
1012
|
+
end
|
1013
|
+
}
|
1014
|
+
} }
|
1015
|
+
# .pretty needed to make sure DOCTYPE is in a separate line
|
1016
|
+
lines = output.pretty.to_a
|
1017
|
+
lines.unshift lines.pop if /DOCTYPE/ =~ lines[-1]
|
1018
|
+
File.open(destfile, "w") do |f|
|
1019
|
+
f.puts lines
|
1020
|
+
end
|
1021
|
+
end
|
1022
|
+
|
1023
|
+
def colorscale
|
1024
|
+
colorscalebase =<<EOF
|
1025
|
+
span.run%d {
|
1026
|
+
background-color: rgb(%d, %d, %d);
|
1027
|
+
display: block;
|
1028
|
+
}
|
1029
|
+
EOF
|
1030
|
+
cscale = ""
|
1031
|
+
101.times do |i|
|
1032
|
+
if @color
|
1033
|
+
r, g, b = hsv2rgb(220-(2.2*i).to_i, 0.3, 1)
|
1034
|
+
r = (r * 255).to_i
|
1035
|
+
g = (g * 255).to_i
|
1036
|
+
b = (b * 255).to_i
|
1037
|
+
else
|
1038
|
+
r = g = b = 255 - i
|
1039
|
+
end
|
1040
|
+
cscale << colorscalebase % [i, r, g, b]
|
1041
|
+
end
|
1042
|
+
cscale
|
1043
|
+
end
|
1044
|
+
|
1045
|
+
# thanks to kig @ #ruby-lang for this one
|
1046
|
+
def hsv2rgb(h,s,v)
|
1047
|
+
return [v,v,v] if s == 0
|
1048
|
+
h = h/60.0
|
1049
|
+
i = h.floor
|
1050
|
+
f = h-i
|
1051
|
+
p = v * (1-s)
|
1052
|
+
q = v * (1-s*f)
|
1053
|
+
t = v * (1-s*(1-f))
|
1054
|
+
case i
|
1055
|
+
when 0
|
1056
|
+
r = v
|
1057
|
+
g = t
|
1058
|
+
b = p
|
1059
|
+
when 1
|
1060
|
+
r = q
|
1061
|
+
g = v
|
1062
|
+
b = p
|
1063
|
+
when 2
|
1064
|
+
r = p
|
1065
|
+
g = v
|
1066
|
+
b = t
|
1067
|
+
when 3
|
1068
|
+
r = p
|
1069
|
+
g = q
|
1070
|
+
b = v
|
1071
|
+
when 4
|
1072
|
+
r = t
|
1073
|
+
g = p
|
1074
|
+
b = v
|
1075
|
+
when 5
|
1076
|
+
r = v
|
1077
|
+
g = p
|
1078
|
+
b = q
|
1079
|
+
end
|
1080
|
+
[r,g,b]
|
1081
|
+
end
|
1082
|
+
end
|
1083
|
+
|
1084
|
+
class HTMLProfiling < HTMLCoverage # :nodoc:
|
1085
|
+
|
1086
|
+
DEFAULT_OPTS = {:destdir => "profiling"}
|
1087
|
+
def initialize(opts = {})
|
1088
|
+
options = DEFAULT_OPTS.clone.update(opts)
|
1089
|
+
super(options)
|
1090
|
+
@max_cache = {}
|
1091
|
+
@median_cache = {}
|
1092
|
+
end
|
1093
|
+
|
1094
|
+
def default_title
|
1095
|
+
"Bogo-profile information"
|
1096
|
+
end
|
1097
|
+
|
1098
|
+
def default_color
|
1099
|
+
if @color
|
1100
|
+
"rgb(179,205,255)"
|
1101
|
+
else
|
1102
|
+
"rgb(255, 255, 255)"
|
1103
|
+
end
|
1104
|
+
end
|
1105
|
+
|
1106
|
+
def output_color_table?
|
1107
|
+
false
|
1108
|
+
end
|
1109
|
+
|
1110
|
+
def span_class(sourceinfo, marked, count)
|
1111
|
+
full_scale_range = @fsr # dB
|
1112
|
+
nz_count = sourceinfo.counts.select{|x| x && x != 0}
|
1113
|
+
nz_count << 1 # avoid div by 0
|
1114
|
+
max = @max_cache[sourceinfo] ||= nz_count.max
|
1115
|
+
#avg = @median_cache[sourceinfo] ||= 1.0 *
|
1116
|
+
# nz_count.inject{|a,b| a+b} / nz_count.size
|
1117
|
+
median = @median_cache[sourceinfo] ||= 1.0 * nz_count.sort[nz_count.size/2]
|
1118
|
+
max ||= 2
|
1119
|
+
max = 2 if max == 1
|
1120
|
+
if marked == true
|
1121
|
+
count = 1 if !count || count == 0
|
1122
|
+
idx = 50 + 1.0 * (500/full_scale_range) * Math.log(count/median) /
|
1123
|
+
Math.log(10)
|
1124
|
+
idx = idx.to_i
|
1125
|
+
idx = 0 if idx < 0
|
1126
|
+
idx = 100 if idx > 100
|
1127
|
+
"run#{idx}"
|
1128
|
+
else
|
1129
|
+
nil
|
1130
|
+
end
|
1131
|
+
end
|
1132
|
+
end
|
1133
|
+
|
1134
|
+
class RubyAnnotation < Formatter # :nodoc:
|
1135
|
+
DEFAULT_OPTS = { :destdir => "coverage" }
|
1136
|
+
def initialize(opts = {})
|
1137
|
+
options = DEFAULT_OPTS.clone.update(opts)
|
1138
|
+
super(options)
|
1139
|
+
@dest = options[:destdir]
|
1140
|
+
@do_callsites = true
|
1141
|
+
@do_cross_references = true
|
1142
|
+
|
1143
|
+
@mangle_filename = Hash.new{|h,base|
|
1144
|
+
h[base] = Pathname.new(base).cleanpath.to_s.gsub(%r{^\w:[/\\]}, "").gsub(/\./, "_").gsub(/[\\\/]/, "-") + ".rb"
|
1145
|
+
}
|
1146
|
+
end
|
1147
|
+
|
1148
|
+
def execute
|
1149
|
+
return if @files.empty?
|
1150
|
+
FileUtils.mkdir_p @dest
|
1151
|
+
each_file_pair_sorted do |filename, fileinfo|
|
1152
|
+
create_file(File.join(@dest, mangle_filename(filename)), fileinfo)
|
1153
|
+
end
|
1154
|
+
end
|
1155
|
+
|
1156
|
+
private
|
1157
|
+
|
1158
|
+
def format_lines(file)
|
1159
|
+
result = ""
|
1160
|
+
format_line = "%#{file.num_lines.to_s.size}d"
|
1161
|
+
file.num_lines.times do |i|
|
1162
|
+
line = file.lines[i].chomp
|
1163
|
+
marked = file.coverage[i]
|
1164
|
+
count = file.counts[i]
|
1165
|
+
result << create_cross_refs(file.name, i+1, line, marked) + "\n"
|
1166
|
+
end
|
1167
|
+
result
|
1168
|
+
end
|
1169
|
+
|
1170
|
+
def create_cross_refs(filename, lineno, linetext, marked)
|
1171
|
+
return linetext unless @callsite_analyzer && @do_callsites
|
1172
|
+
ref_blocks = []
|
1173
|
+
_get_defsites(ref_blocks, filename, lineno, linetext, ">>") do |ref|
|
1174
|
+
if ref.file
|
1175
|
+
ref.file.sub!(%r!^./!, '')
|
1176
|
+
where = "at #{mangle_filename(ref.file)}:#{ref.line}"
|
1177
|
+
else
|
1178
|
+
where = "(C extension/core)"
|
1179
|
+
end
|
1180
|
+
"#{ref.klass}##{ref.mid} " + where + ""
|
1181
|
+
end
|
1182
|
+
_get_callsites(ref_blocks, filename, lineno, linetext, "<<") do |ref| # "
|
1183
|
+
ref.file.sub!(%r!^./!, '')
|
1184
|
+
"#{mangle_filename(ref.file||'C code')}:#{ref.line} " +
|
1185
|
+
"in #{ref.klass}##{ref.mid}"
|
1186
|
+
end
|
1187
|
+
|
1188
|
+
create_cross_reference_block(linetext, ref_blocks, marked)
|
1189
|
+
end
|
1190
|
+
|
1191
|
+
def create_cross_reference_block(linetext, ref_blocks, marked)
|
1192
|
+
codelen = 75
|
1193
|
+
if ref_blocks.empty?
|
1194
|
+
if marked
|
1195
|
+
return "%-#{codelen}s #o" % linetext
|
1196
|
+
else
|
1197
|
+
return linetext
|
1198
|
+
end
|
1199
|
+
end
|
1200
|
+
ret = ""
|
1201
|
+
@cross_ref_idx ||= 0
|
1202
|
+
@known_files ||= sorted_file_pairs.map{|fname, finfo| normalize_filename(fname)}
|
1203
|
+
ret << "%-#{codelen}s # " % linetext
|
1204
|
+
ref_blocks.each do |refs, toplabel, label_proc|
|
1205
|
+
unless !toplabel || toplabel.empty?
|
1206
|
+
ret << toplabel << " "
|
1207
|
+
end
|
1208
|
+
refs.each do |dst|
|
1209
|
+
dstfile = normalize_filename(dst.file) if dst.file
|
1210
|
+
dstline = dst.line
|
1211
|
+
label = label_proc.call(dst)
|
1212
|
+
if dst.file && @known_files.include?(dstfile)
|
1213
|
+
ret << "[[" << label << "]], "
|
1214
|
+
else
|
1215
|
+
ret << label << ", "
|
1216
|
+
end
|
1217
|
+
end
|
1218
|
+
end
|
1219
|
+
|
1220
|
+
ret
|
1221
|
+
end
|
1222
|
+
|
1223
|
+
def create_file(destfile, fileinfo)
|
1224
|
+
#$stderr.puts "Generating #{destfile.inspect}"
|
1225
|
+
body = format_lines(fileinfo)
|
1226
|
+
File.open(destfile, "w") do |f|
|
1227
|
+
f.puts body
|
1228
|
+
f.puts footer(fileinfo)
|
1229
|
+
end
|
1230
|
+
end
|
1231
|
+
|
1232
|
+
def footer(fileinfo)
|
1233
|
+
s = "# Total lines : %d\n" % fileinfo.num_lines
|
1234
|
+
s << "# Lines of code : %d\n" % fileinfo.num_code_lines
|
1235
|
+
s << "# Total coverage : %3.1f%%\n" % [ fileinfo.total_coverage*100 ]
|
1236
|
+
s << "# Code coverage : %3.1f%%\n\n" % [ fileinfo.code_coverage*100 ]
|
1237
|
+
# prevents false positives on Emacs
|
1238
|
+
s << "# Local " "Variables:\n" "# mode: " "rcov-xref\n" "# End:\n"
|
1239
|
+
end
|
1240
|
+
end
|
1241
|
+
|
1242
|
+
|
1243
|
+
end # Rcov
|
1244
|
+
|
1245
|
+
# vi: set sw=4:
|
1246
|
+
# Here is Emacs setting. DO NOT REMOVE!
|
1247
|
+
# Local Variables:
|
1248
|
+
# ruby-indent-level: 4
|
1249
|
+
# End:
|