rcov 0.5.0.1-mswin32 → 0.6.0.1-mswin32
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 +35 -1
- data/CHANGES +28 -0
- data/README.en +2 -0
- data/README.vim +47 -0
- data/Rakefile +6 -5
- data/Rantfile +2 -1
- data/THANKS +14 -0
- data/bin/rcov +124 -743
- data/ext/rcovrt/extconf.rb +9 -1
- data/ext/rcovrt/rcov.c +115 -32
- data/lib/rcov.rb +67 -42
- data/lib/rcov/lowlevel.rb +9 -1
- data/lib/rcov/rant.rb +5 -3
- data/lib/rcov/rcovtask.rb +3 -3
- data/lib/rcov/report.rb +1005 -0
- data/lib/rcov/version.rb +3 -3
- data/lib/rcovrt.so +0 -0
- data/test/test_CallSiteAnalyzer.rb +41 -18
- data/test/test_CodeCoverageAnalyzer.rb +2 -2
- data/test/test_FileStatistics.rb +72 -2
- metadata +6 -2
data/BLURB
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
Source code, additional information, screenshots... available at
|
3
3
|
http://eigenclass.org/hiki.rb?rcov
|
4
4
|
Release information:
|
5
|
-
http://eigenclass.org/hiki.rb?rcov+0.
|
5
|
+
http://eigenclass.org/hiki.rb?rcov+0.6.0
|
6
6
|
|
7
7
|
If you're on win32, you can also find a pre-built rcovrt.so (which makes
|
8
8
|
code coverage analysis >100 times faster) in the above-mentioned pages.
|
@@ -14,6 +14,8 @@ test coverage of target code. It features:
|
|
14
14
|
* fast execution: 20-300 times faster than previous tools
|
15
15
|
* multiple analysis modes: standard, bogo-profile, "intentional testing",
|
16
16
|
dependency analysis...
|
17
|
+
* detection of uncovered code introduced since the last run ("differential
|
18
|
+
code coverage")
|
17
19
|
* fairly accurate coverage information through code linkage inference using
|
18
20
|
simple heuristics
|
19
21
|
* cross-referenced XHTML and several kinds of text reports
|
@@ -83,6 +85,38 @@ The (undecorated) textual output with execution count information looks like thi
|
|
83
85
|
end | 0
|
84
86
|
|
85
87
|
|
88
|
+
rcov can detect when you've added code that was not covered by your unit
|
89
|
+
tests:
|
90
|
+
|
91
|
+
$ rcov --text-coverage-diff --no-color test/*.rb
|
92
|
+
Started
|
93
|
+
.......................................
|
94
|
+
Finished in 1.163085 seconds.
|
95
|
+
|
96
|
+
39 tests, 415 assertions, 0 failures, 0 errors
|
97
|
+
|
98
|
+
================================================================================
|
99
|
+
!!!!! Uncovered code introduced in lib/rcov.rb
|
100
|
+
|
101
|
+
### lib/rcov.rb:207
|
102
|
+
|
103
|
+
def precompute_coverage(comments_run_by_default = true)
|
104
|
+
changed = false
|
105
|
+
lastidx = lines.size - 1
|
106
|
+
if (!is_code?(lastidx) || /^__END__$/ =~ @lines[-1]) && !@coverage[lastidx]
|
107
|
+
!! # mark the last block of comments
|
108
|
+
!! @coverage[lastidx] ||= :inferred
|
109
|
+
!! (lastidx-1).downto(0) do |i|
|
110
|
+
!! break if is_code?(i)
|
111
|
+
!! @coverage[i] ||= :inferred
|
112
|
+
!! end
|
113
|
+
!! end
|
114
|
+
(0...lines.size).each do |i|
|
115
|
+
next if @coverage[i]
|
116
|
+
line = @lines[i]
|
117
|
+
|
118
|
+
|
119
|
+
|
86
120
|
License
|
87
121
|
-------
|
88
122
|
rcov is released under the terms of Ruby's license.
|
data/CHANGES
CHANGED
@@ -1,6 +1,34 @@
|
|
1
1
|
|
2
2
|
User-visible changes.
|
3
3
|
|
4
|
+
Since 0.5.0 (2006-05-30)
|
5
|
+
========================
|
6
|
+
Features
|
7
|
+
--------
|
8
|
+
* differential coverage report: --text-coverage-diff (-D) and --save
|
9
|
+
Tells you when you've added new code that was not covered by the tests and
|
10
|
+
when code that used to be covered isn't anymore. Integration with vim
|
11
|
+
(contributions for other editors/IDEs welcome).
|
12
|
+
* fully cross-referenced reports, indicating where methods are called from
|
13
|
+
and which methods were called for each line (--xrefs)
|
14
|
+
* cross-referenced report generation is now over 4 times faster for
|
15
|
+
applications with deep call stacks (such as Rails apps)
|
16
|
+
|
17
|
+
Bugfixes
|
18
|
+
--------
|
19
|
+
* comments at EOF are marked now
|
20
|
+
* better handling of multiline hashes/arrays
|
21
|
+
* better handling of end/}: support chained method calls and more expressions
|
22
|
+
on the same line
|
23
|
+
* better handling of heredocs with interpolation
|
24
|
+
|
25
|
+
Minor enhancements
|
26
|
+
------------------
|
27
|
+
* more readable --text-coverage
|
28
|
+
* set whether comments are "run" by default instead of attaching them to
|
29
|
+
the following block of code (--[no-]comments)
|
30
|
+
* --report-cov-bug can be used to report bugs in the coverage analysis
|
31
|
+
|
4
32
|
Since 0.4.0 (2006-05-22)
|
5
33
|
========================
|
6
34
|
Features
|
data/README.en
CHANGED
@@ -9,6 +9,8 @@ rcov README
|
|
9
9
|
* fast execution: 20-300 times faster than previous tools
|
10
10
|
* multiple analysis modes: standard, bogo-profile, "intentional testing",
|
11
11
|
dependency analysis...
|
12
|
+
* detection of uncovered code introduced since the last run ("differential
|
13
|
+
code coverage")
|
12
14
|
* fairly accurate coverage information through code linkage inference using
|
13
15
|
simple heuristics
|
14
16
|
* cross-referenced XHTML and several kinds of text reports
|
data/README.vim
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
|
2
|
+
<tt>rcov.vim</tt> allows you to run unit tests from vim and enter quickfix mode in
|
3
|
+
order to jump to uncovered code introduced since the last run.
|
4
|
+
|
5
|
+
== Installation
|
6
|
+
Copy <tt>rcov.vim</tt> to the appropriate "compiler" directory (typically
|
7
|
+
<tt>$HOME/.vim/compiler</tt>).
|
8
|
+
|
9
|
+
== Usage
|
10
|
+
|
11
|
+
=== Setting the reference point
|
12
|
+
|
13
|
+
+rcov+'s <tt>--text-coverage-diff</tt> mode compares the current coverage status against
|
14
|
+
the saved one. It therefore needs that information to be recorded
|
15
|
+
before you write new code (typically right after you perform a commit) in
|
16
|
+
order to have something to compare against.
|
17
|
+
|
18
|
+
You can save the current status with the <tt>--save</tt> option.
|
19
|
+
If you're running +rcov+ from Rake, you can do something like
|
20
|
+
rake rcov_units RCOVOPTS="-T --save --rails"
|
21
|
+
in order to take the current status as the reference point.
|
22
|
+
|
23
|
+
=== Finding new uncovered code
|
24
|
+
|
25
|
+
Type the following in command mode while editing your program:
|
26
|
+
:compiler rcov
|
27
|
+
|
28
|
+
rcov.vim assumes +rcov+ can be invoked with a rake task (see
|
29
|
+
README.rake[link:files/README_rake.html] for
|
30
|
+
information on how to create it).
|
31
|
+
|
32
|
+
You can then execute +rcov+ and enter quickfix mode by typing
|
33
|
+
|
34
|
+
:make <taskname>
|
35
|
+
|
36
|
+
where taskname is the +rcov+ task you want to use; if you didn't override the
|
37
|
+
default name in the Rakefile, just
|
38
|
+
|
39
|
+
:make rcov
|
40
|
+
|
41
|
+
will do.
|
42
|
+
|
43
|
+
vim will then enter quickfix mode, allowing you to jump to the areas that were
|
44
|
+
not covered since the last time you saved the coverage data.
|
45
|
+
|
46
|
+
--------
|
47
|
+
# vim: ft=text :
|
data/Rakefile
CHANGED
@@ -18,26 +18,26 @@ ENV["RCOVPATH"] = "bin/rcov"
|
|
18
18
|
# (really!)
|
19
19
|
desc "Create a cross-referenced code coverage report."
|
20
20
|
Rcov::RcovTask.new do |t|
|
21
|
-
t.libs << "ext/rcovrt"
|
22
21
|
t.test_files = FileList['test/test*.rb']
|
23
|
-
t.
|
22
|
+
t.ruby_opts << "-Ilib:ext/rcovrt" # in order to use this rcov
|
23
|
+
t.rcov_opts << "--xrefs" # comment to disable cross-references
|
24
24
|
t.verbose = true
|
25
25
|
end
|
26
26
|
|
27
27
|
desc "Analyze code coverage for the FileStatistics class."
|
28
28
|
Rcov::RcovTask.new(:rcov_sourcefile) do |t|
|
29
|
-
t.libs << "ext/rcovrt"
|
30
29
|
t.test_files = FileList['test/test_FileStatistics.rb']
|
31
30
|
t.verbose = true
|
32
31
|
t.rcov_opts << "--test-unit-only"
|
32
|
+
t.ruby_opts << "-Ilib:ext/rcovrt" # in order to use this rcov
|
33
33
|
t.output_dir = "coverage.sourcefile"
|
34
34
|
end
|
35
35
|
|
36
36
|
Rcov::RcovTask.new(:rcov_ccanalyzer) do |t|
|
37
|
-
t.libs << "ext/rcovrt"
|
38
37
|
t.test_files = FileList['test/test_CodeCoverageAnalyzer.rb']
|
39
38
|
t.verbose = true
|
40
39
|
t.rcov_opts << "--test-unit-only"
|
40
|
+
t.ruby_opts << "-Ilib:ext/rcovrt" # in order to use this rcov
|
41
41
|
t.output_dir = "coverage.ccanalyzer"
|
42
42
|
end
|
43
43
|
|
@@ -73,6 +73,7 @@ Rake::RDocTask.new("rdoc") { |rdoc|
|
|
73
73
|
rdoc.rdoc_files.include('README.API')
|
74
74
|
rdoc.rdoc_files.include('README.rake')
|
75
75
|
rdoc.rdoc_files.include('README.rant')
|
76
|
+
rdoc.rdoc_files.include('README.vim')
|
76
77
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
77
78
|
}
|
78
79
|
|
@@ -114,7 +115,7 @@ EOF
|
|
114
115
|
s.bindir = "bin" # Use these for applications.
|
115
116
|
s.executables = ["rcov"]
|
116
117
|
s.has_rdoc = true
|
117
|
-
s.extra_rdoc_files = %w[README.API README.rake]
|
118
|
+
s.extra_rdoc_files = %w[README.API README.rake README.rant README.vim]
|
118
119
|
s.rdoc_options << "--main" << "README.API" << "--title" << 'rcov code coverage tool'
|
119
120
|
s.test_files = Dir["test/test_*.rb"]
|
120
121
|
end
|
data/Rantfile
CHANGED
@@ -64,7 +64,8 @@ desc "Generate documentation."
|
|
64
64
|
gen RubyDoc, :rdoc do |g|
|
65
65
|
g.verbose = true
|
66
66
|
g.dir = "doc"
|
67
|
-
g.files = sys["README.API", "README.rake", "README.rant", "
|
67
|
+
g.files = sys["README.API", "README.rake", "README.rant", "README.vim",
|
68
|
+
"lib/**/*.rb"]
|
68
69
|
g.opts = %w(--line-numbers --inline-source --title rcov --main README.API)
|
69
70
|
end
|
70
71
|
|
data/THANKS
CHANGED
@@ -27,3 +27,17 @@ John-Mason Shackelford:
|
|
27
27
|
|
28
28
|
Dave Burt:
|
29
29
|
* reported an issue with text reports under cmd.exe (should use < 80 cols)
|
30
|
+
|
31
|
+
Alex Wayne:
|
32
|
+
* reported problem with heredocs: they were not being marked as a whole if
|
33
|
+
the "header" wasn't reported by Ruby.
|
34
|
+
* reported problem with the last line of literal data structs not being
|
35
|
+
covered if there was stuff after the end delimiter
|
36
|
+
|
37
|
+
Coda Hale:
|
38
|
+
* reported problem with blocks were the first line is not being marked
|
39
|
+
and ditto for the last line when end/} is followed by more stuff
|
40
|
+
|
41
|
+
Tim Shadel:
|
42
|
+
* reported that the last comment block was not being marked even when
|
43
|
+
it was the last thing in the file
|
data/bin/rcov
CHANGED
@@ -30,737 +30,7 @@ end
|
|
30
30
|
|
31
31
|
SCRIPT_LINES__ = {} unless defined? SCRIPT_LINES__
|
32
32
|
require 'rcov/version'
|
33
|
-
|
34
|
-
module Rcov
|
35
|
-
|
36
|
-
class Formatter
|
37
|
-
ignore_files = [/\A#{Regexp.escape(Config::CONFIG["libdir"])}/, /\btc_[^.]*.rb/,
|
38
|
-
/_test\.rb\z/, /\btest\//, /\bvendor\//, /\A#{Regexp.escape(__FILE__)}\z/]
|
39
|
-
DEFAULT_OPTS = {:ignore => ignore_files, :sort => :name, :sort_reverse => false,
|
40
|
-
:output_threshold => 101, :dont_ignore => [],
|
41
|
-
:callsite_analyzer => nil}
|
42
|
-
def initialize(opts = {})
|
43
|
-
options = DEFAULT_OPTS.clone.update(opts)
|
44
|
-
@files = {}
|
45
|
-
@ignore_files = options[:ignore]
|
46
|
-
@dont_ignore_files = options[:dont_ignore]
|
47
|
-
@sort_criterium = case options[:sort]
|
48
|
-
when :loc : lambda{|fname, finfo| finfo.num_code_lines}
|
49
|
-
when :coverage : lambda{|fname, finfo| finfo.code_coverage}
|
50
|
-
else lambda{|fname, finfo| fname}
|
51
|
-
end
|
52
|
-
@sort_reverse = options[:sort_reverse]
|
53
|
-
@output_threshold = options[:output_threshold]
|
54
|
-
@callsite_analyzer = options[:callsite_analyzer]
|
55
|
-
@callsite_index = nil
|
56
|
-
end
|
57
|
-
|
58
|
-
def add_file(filename, lines, coverage, counts)
|
59
|
-
old_filename = filename
|
60
|
-
filename = normalize_filename(filename)
|
61
|
-
SCRIPT_LINES__[filename] = SCRIPT_LINES__[old_filename]
|
62
|
-
if @ignore_files.any?{|x| x === filename} &&
|
63
|
-
!@dont_ignore_files.any?{|x| x === filename}
|
64
|
-
return nil
|
65
|
-
end
|
66
|
-
if @files[filename]
|
67
|
-
@files[filename].merge(lines, coverage, counts)
|
68
|
-
else
|
69
|
-
@files[filename] = FileStatistics.new filename, lines, counts
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
def normalize_filename(filename)
|
74
|
-
File.expand_path(filename).gsub(/^#{Regexp.escape(Dir.getwd)}\//, '')
|
75
|
-
end
|
76
|
-
|
77
|
-
def each_file_pair_sorted(&b)
|
78
|
-
return sorted_file_pairs unless block_given?
|
79
|
-
sorted_file_pairs.each(&b)
|
80
|
-
end
|
81
|
-
|
82
|
-
def sorted_file_pairs
|
83
|
-
pairs = @files.sort_by do |fname, finfo|
|
84
|
-
@sort_criterium.call(fname, finfo)
|
85
|
-
end.select{|_, finfo| 100 * finfo.code_coverage < @output_threshold}
|
86
|
-
@sort_reverse ? pairs.reverse : pairs
|
87
|
-
end
|
88
|
-
|
89
|
-
def total_coverage
|
90
|
-
lines = 0
|
91
|
-
total = 0.0
|
92
|
-
@files.each do |k,f|
|
93
|
-
total += f.num_lines * f.total_coverage
|
94
|
-
lines += f.num_lines
|
95
|
-
end
|
96
|
-
return 0 if lines == 0
|
97
|
-
total / lines
|
98
|
-
end
|
99
|
-
|
100
|
-
def code_coverage
|
101
|
-
lines = 0
|
102
|
-
total = 0.0
|
103
|
-
@files.each do |k,f|
|
104
|
-
total += f.num_code_lines * f.code_coverage
|
105
|
-
lines += f.num_code_lines
|
106
|
-
end
|
107
|
-
return 0 if lines == 0
|
108
|
-
total / lines
|
109
|
-
end
|
110
|
-
|
111
|
-
def num_code_lines
|
112
|
-
lines = 0
|
113
|
-
@files.each{|k, f| lines += f.num_code_lines }
|
114
|
-
lines
|
115
|
-
end
|
116
|
-
|
117
|
-
def num_lines
|
118
|
-
lines = 0
|
119
|
-
@files.each{|k, f| lines += f.num_lines }
|
120
|
-
lines
|
121
|
-
end
|
122
|
-
|
123
|
-
private
|
124
|
-
def cross_references_for(filename, lineno)
|
125
|
-
return nil unless @callsite_analyzer
|
126
|
-
@callsite_index ||= build_callsite_index
|
127
|
-
@callsite_index[normalize_filename(filename)][lineno]
|
128
|
-
end
|
129
|
-
|
130
|
-
def build_callsite_index
|
131
|
-
index = Hash.new{|h,k| h[k] = {}}
|
132
|
-
@callsite_analyzer.analyzed_classes.each do |classname|
|
133
|
-
@callsite_analyzer.analyzed_methods(classname).each do |methname|
|
134
|
-
defsite = @callsite_analyzer.defsite(classname, methname)
|
135
|
-
index[normalize_filename(defsite.file)][defsite.line] =
|
136
|
-
@callsite_analyzer.callsites(classname, methname)
|
137
|
-
end
|
138
|
-
end
|
139
|
-
index
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
class TextSummary < Formatter
|
144
|
-
def execute
|
145
|
-
puts summary
|
146
|
-
end
|
147
|
-
|
148
|
-
def summary
|
149
|
-
"%.1f%% %d file(s) %d Lines %d LOC" % [code_coverage * 100,
|
150
|
-
@files.size, num_lines, num_code_lines]
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
|
-
class TextReport < TextSummary
|
155
|
-
def execute
|
156
|
-
print_lines
|
157
|
-
print_header
|
158
|
-
print_lines
|
159
|
-
each_file_pair_sorted do |fname, finfo|
|
160
|
-
name = fname.size < 52 ? fname : "..." + fname[-48..-1]
|
161
|
-
print_info(name, finfo.num_lines, finfo.num_code_lines,
|
162
|
-
finfo.code_coverage)
|
163
|
-
end
|
164
|
-
print_lines
|
165
|
-
print_info("Total", num_lines, num_code_lines, code_coverage)
|
166
|
-
print_lines
|
167
|
-
puts summary
|
168
|
-
end
|
169
|
-
|
170
|
-
def print_info(name, lines, loc, coverage)
|
171
|
-
puts "|%-51s | %5d | %5d | %5.1f%% |" % [name, lines, loc, 100 * coverage]
|
172
|
-
end
|
173
|
-
|
174
|
-
def print_lines
|
175
|
-
puts "+----------------------------------------------------+-------+-------+--------+"
|
176
|
-
end
|
177
|
-
|
178
|
-
def print_header
|
179
|
-
puts "| File | Lines | LOC | COV |"
|
180
|
-
end
|
181
|
-
end
|
182
|
-
|
183
|
-
class TextCoverage < Formatter
|
184
|
-
DEFAULT_OPTS = {:textmode => :coverage}
|
185
|
-
def initialize(opts = {})
|
186
|
-
options = DEFAULT_OPTS.clone.update(opts)
|
187
|
-
@textmode = options[:textmode]
|
188
|
-
@color = options[:color]
|
189
|
-
super(options)
|
190
|
-
end
|
191
|
-
|
192
|
-
def execute
|
193
|
-
each_file_pair_sorted do |filename, fileinfo|
|
194
|
-
puts "=" * 80
|
195
|
-
puts filename
|
196
|
-
puts "=" * 80
|
197
|
-
SCRIPT_LINES__[filename].each_with_index do |line, i|
|
198
|
-
case @textmode
|
199
|
-
when :counts
|
200
|
-
puts "%-70s| %6d" % [line.chomp[0,70], fileinfo.counts[i]]
|
201
|
-
when :coverage
|
202
|
-
if @color
|
203
|
-
prefix = fileinfo.coverage[i] ? "\e[32;40m" : "\e[31;40m"
|
204
|
-
puts "#{prefix}%s\e[37;40m" % line.chomp
|
205
|
-
else
|
206
|
-
prefix = fileinfo.coverage[i] ? " " : "## "
|
207
|
-
puts "#{prefix}#{line}"
|
208
|
-
end
|
209
|
-
end
|
210
|
-
end
|
211
|
-
end
|
212
|
-
end
|
213
|
-
end
|
214
|
-
|
215
|
-
|
216
|
-
class HTMLCoverage < Formatter
|
217
|
-
include XX::XHTML
|
218
|
-
include XX::XMLish
|
219
|
-
require 'fileutils'
|
220
|
-
JAVASCRIPT_PROLOG = <<-EOS
|
221
|
-
|
222
|
-
// <![CDATA[
|
223
|
-
function toggleCode( id ) {
|
224
|
-
if ( document.getElementById )
|
225
|
-
elem = document.getElementById( id );
|
226
|
-
else if ( document.all )
|
227
|
-
elem = eval( "document.all." + id );
|
228
|
-
else
|
229
|
-
return false;
|
230
|
-
|
231
|
-
elemStyle = elem.style;
|
232
|
-
|
233
|
-
if ( elemStyle.display != "block" ) {
|
234
|
-
elemStyle.display = "block"
|
235
|
-
} else {
|
236
|
-
elemStyle.display = "none"
|
237
|
-
}
|
238
|
-
|
239
|
-
return true;
|
240
|
-
}
|
241
|
-
|
242
|
-
// Make cross-references hidden by default
|
243
|
-
document.writeln( "<style type=\\"text/css\\">span.cross-ref { display: none }</style>" )
|
244
|
-
|
245
|
-
// ]]>
|
246
|
-
EOS
|
247
|
-
|
248
|
-
CSS_PROLOG = <<-EOS
|
249
|
-
span.marked0 {
|
250
|
-
background-color: rgb(185, 210, 200);
|
251
|
-
display: block;
|
252
|
-
}
|
253
|
-
span.marked1 {
|
254
|
-
background-color: rgb(190, 215, 205);
|
255
|
-
display: block;
|
256
|
-
}
|
257
|
-
span.inferred0 {
|
258
|
-
background-color: rgb(175, 200, 200);
|
259
|
-
display: block;
|
260
|
-
}
|
261
|
-
span.inferred1 {
|
262
|
-
background-color: rgb(180, 205, 205);
|
263
|
-
display: block;
|
264
|
-
}
|
265
|
-
span.uncovered0 {
|
266
|
-
background-color: rgb(225, 110, 110);
|
267
|
-
display: block;
|
268
|
-
}
|
269
|
-
span.uncovered1 {
|
270
|
-
background-color: rgb(235, 120, 120);
|
271
|
-
display: block;
|
272
|
-
}
|
273
|
-
span.overview {
|
274
|
-
border-bottom: 8px solid black;
|
275
|
-
}
|
276
|
-
div.overview {
|
277
|
-
border-bottom: 8px solid black;
|
278
|
-
}
|
279
|
-
body {
|
280
|
-
font-family: verdana, arial, helvetica;
|
281
|
-
}
|
282
|
-
|
283
|
-
div.footer {
|
284
|
-
font-size: 68%;
|
285
|
-
margin-top: 1.5em;
|
286
|
-
}
|
287
|
-
|
288
|
-
h1, h2, h3, h4, h5, h6 {
|
289
|
-
margin-bottom: 0.5em;
|
290
|
-
}
|
291
|
-
|
292
|
-
h5 {
|
293
|
-
margin-top: 0.5em;
|
294
|
-
}
|
295
|
-
|
296
|
-
.hidden {
|
297
|
-
display: none;
|
298
|
-
}
|
299
|
-
|
300
|
-
div.separator {
|
301
|
-
height: 10px;
|
302
|
-
}
|
303
|
-
/* Commented out for better readability, esp. on IE */
|
304
|
-
/*
|
305
|
-
table tr td, table tr th {
|
306
|
-
font-size: 68%;
|
307
|
-
}
|
308
|
-
|
309
|
-
td.value table tr td {
|
310
|
-
font-size: 11px;
|
311
|
-
}
|
312
|
-
*/
|
313
|
-
|
314
|
-
table.percent_graph {
|
315
|
-
height: 12px;
|
316
|
-
border: #808080 1px solid;
|
317
|
-
empty-cells: show;
|
318
|
-
}
|
319
|
-
|
320
|
-
table.percent_graph td.covered {
|
321
|
-
height: 10px;
|
322
|
-
background: #00f000;
|
323
|
-
}
|
324
|
-
|
325
|
-
table.percent_graph td.uncovered {
|
326
|
-
height: 10px;
|
327
|
-
background: #e00000;
|
328
|
-
}
|
329
|
-
|
330
|
-
table.percent_graph td.NA {
|
331
|
-
height: 10px;
|
332
|
-
background: #eaeaea;
|
333
|
-
}
|
334
|
-
|
335
|
-
table.report {
|
336
|
-
border-collapse: collapse;
|
337
|
-
width: 100%;
|
338
|
-
}
|
339
|
-
|
340
|
-
table.report td.heading {
|
341
|
-
background: #dcecff;
|
342
|
-
border: #d0d0d0 1px solid;
|
343
|
-
font-weight: bold;
|
344
|
-
text-align: center;
|
345
|
-
}
|
346
|
-
|
347
|
-
table.report td.heading:hover {
|
348
|
-
background: #c0ffc0;
|
349
|
-
}
|
350
|
-
|
351
|
-
table.report td.text {
|
352
|
-
border: #d0d0d0 1px solid;
|
353
|
-
}
|
354
|
-
|
355
|
-
table.report td.value {
|
356
|
-
text-align: right;
|
357
|
-
border: #d0d0d0 1px solid;
|
358
|
-
}
|
359
|
-
table.report tr.light {
|
360
|
-
background-color: rgb(240, 240, 245);
|
361
|
-
}
|
362
|
-
table.report tr.dark {
|
363
|
-
background-color: rgb(230, 230, 235);
|
364
|
-
}
|
365
|
-
EOS
|
366
|
-
|
367
|
-
DEFAULT_OPTS = {:color => false, :fsr => 30, :destdir => "coverage"}
|
368
|
-
def initialize(opts = {})
|
369
|
-
options = DEFAULT_OPTS.clone.update(opts)
|
370
|
-
super(options)
|
371
|
-
@dest = options[:destdir]
|
372
|
-
@color = options[:color]
|
373
|
-
@fsr = options[:fsr]
|
374
|
-
@span_class_index = 0
|
375
|
-
end
|
376
|
-
|
377
|
-
def execute
|
378
|
-
return if @files.empty?
|
379
|
-
FileUtils.mkdir_p @dest
|
380
|
-
create_index(File.join(@dest, "index.html"))
|
381
|
-
each_file_pair_sorted do |filename, fileinfo|
|
382
|
-
create_file(File.join(@dest, mangle_filename(filename)), fileinfo)
|
383
|
-
end
|
384
|
-
end
|
385
|
-
|
386
|
-
def mangle_filename(base)
|
387
|
-
base.gsub(%r{^\w:[/\\]}, "").gsub(/\./, "_").gsub(/[\\\/]/, "-") + ".html"
|
388
|
-
end
|
389
|
-
|
390
|
-
private
|
391
|
-
|
392
|
-
def blurb
|
393
|
-
xmlish_ {
|
394
|
-
p_ {
|
395
|
-
t_{ "Generated using the " }
|
396
|
-
a_(:href => "http://eigenclass.org/hiki.rb?rcov") {
|
397
|
-
t_{ "rcov code coverage analysis tool for Ruby" }
|
398
|
-
}
|
399
|
-
t_{ " version #{Rcov::VERSION}." }
|
400
|
-
}
|
401
|
-
}.pretty
|
402
|
-
end
|
403
|
-
|
404
|
-
def output_color_table?
|
405
|
-
true
|
406
|
-
end
|
407
|
-
|
408
|
-
def default_color
|
409
|
-
"rgb(240, 240, 245)"
|
410
|
-
end
|
411
|
-
|
412
|
-
def default_title
|
413
|
-
"C0 code coverage information"
|
414
|
-
end
|
415
|
-
|
416
|
-
def format_overview(*file_infos)
|
417
|
-
table_text = xmlish_ {
|
418
|
-
table_(:class => "report") {
|
419
|
-
thead_ {
|
420
|
-
tr_ {
|
421
|
-
["Name", "Total lines", "Lines of code", "Total coverage",
|
422
|
-
"Code coverage"].each do |heading|
|
423
|
-
td_(:class => "heading") { heading }
|
424
|
-
end
|
425
|
-
}
|
426
|
-
}
|
427
|
-
tbody_ {
|
428
|
-
color_class_index = 1
|
429
|
-
color_classes = %w[light dark]
|
430
|
-
file_infos.each do |f|
|
431
|
-
color_class_index += 1
|
432
|
-
color_class_index %= color_classes.size
|
433
|
-
tr_(:class => color_classes[color_class_index]) {
|
434
|
-
td_ {
|
435
|
-
case f.name
|
436
|
-
when "TOTAL":
|
437
|
-
t_ { "TOTAL" }
|
438
|
-
else
|
439
|
-
a_(:href => mangle_filename(f.name)){ t_ { f.name } }
|
440
|
-
end
|
441
|
-
}
|
442
|
-
[f.num_lines, f.num_code_lines].each do |value|
|
443
|
-
td_(:class => "value") { tt_{ value } }
|
444
|
-
end
|
445
|
-
[f.total_coverage, f.code_coverage].each do |value|
|
446
|
-
value *= 100
|
447
|
-
td_ {
|
448
|
-
table_(:cellpadding => 0, :cellspacing => 0, :align => "right") {
|
449
|
-
tr_ {
|
450
|
-
td_ {
|
451
|
-
tt_ { "%3.1f%%" % value }
|
452
|
-
x_ " "
|
453
|
-
}
|
454
|
-
ivalue = value.round
|
455
|
-
td_ {
|
456
|
-
table_(:class => "percent_graph", :cellpadding => 0,
|
457
|
-
:cellspacing => 0, :width => 100) {
|
458
|
-
tr_ {
|
459
|
-
td_(:class => "covered", :width => ivalue)
|
460
|
-
td_(:class => "uncovered", :width => (100-ivalue))
|
461
|
-
}
|
462
|
-
}
|
463
|
-
}
|
464
|
-
}
|
465
|
-
}
|
466
|
-
}
|
467
|
-
end
|
468
|
-
}
|
469
|
-
end
|
470
|
-
}
|
471
|
-
}
|
472
|
-
}
|
473
|
-
table_text.pretty
|
474
|
-
end
|
475
|
-
|
476
|
-
class SummaryFileInfo
|
477
|
-
def initialize(obj); @o = obj end
|
478
|
-
%w[num_lines num_code_lines code_coverage total_coverage].each do |m|
|
479
|
-
define_method(m){ @o.send(m) }
|
480
|
-
end
|
481
|
-
def name; "TOTAL" end
|
482
|
-
end
|
483
|
-
|
484
|
-
def create_index(destname)
|
485
|
-
files = [SummaryFileInfo.new(self)] + each_file_pair_sorted.map{|k,v| v}
|
486
|
-
title = default_title
|
487
|
-
output = xhtml_ { html_ {
|
488
|
-
head_ {
|
489
|
-
title_{ title }
|
490
|
-
style_(:type => "text/css") { t_{ "body { background-color: #{default_color}; }" } }
|
491
|
-
style_(:type => "text/css") { CSS_PROLOG }
|
492
|
-
script_(:type => "text/javascript") { h_{ JAVASCRIPT_PROLOG } }
|
493
|
-
}
|
494
|
-
body_ {
|
495
|
-
h3_{
|
496
|
-
t_{ title }
|
497
|
-
}
|
498
|
-
p_ {
|
499
|
-
t_{ "Generated on #{Time.new.to_s} with " }
|
500
|
-
a_(:href => Rcov::UPSTREAM_URL){ "rcov #{Rcov::VERSION}" }
|
501
|
-
}
|
502
|
-
p_ { "Threshold: #{@output_threshold}%" } if @output_threshold != 101
|
503
|
-
hr_
|
504
|
-
x_{ format_overview(*files) }
|
505
|
-
hr_
|
506
|
-
x_{ blurb }
|
507
|
-
p_ {
|
508
|
-
a_(:href => "http://validator.w3.org/check/referer") {
|
509
|
-
img_(:src => "http://www.w3.org/Icons/valid-xhtml11",
|
510
|
-
:alt => "Valid XHTML 1.1!", :height => 31, :width => 88)
|
511
|
-
}
|
512
|
-
a_(:href => "http://jigsaw.w3.org/css-validator/check/referer") {
|
513
|
-
img_(:style => "border:0;width:88px;height:31px",
|
514
|
-
:src => "http://jigsaw.w3.org/css-validator/images/vcss",
|
515
|
-
:alt => "Valid CSS!")
|
516
|
-
}
|
517
|
-
}
|
518
|
-
}
|
519
|
-
} }
|
520
|
-
lines = output.pretty.to_a
|
521
|
-
lines.unshift lines.pop if /DOCTYPE/ =~ lines[-1]
|
522
|
-
File.open(destname, "w") do |f|
|
523
|
-
f.puts lines
|
524
|
-
end
|
525
|
-
end
|
526
|
-
|
527
|
-
def format_lines(file)
|
528
|
-
result = ""
|
529
|
-
last = nil
|
530
|
-
end_of_span = ""
|
531
|
-
format_line = "%#{file.num_lines.to_s.size}d"
|
532
|
-
file.num_lines.times do |i|
|
533
|
-
line = file.lines[i]
|
534
|
-
marked = file.coverage[i]
|
535
|
-
count = file.counts[i]
|
536
|
-
spanclass = span_class(file, marked, count)
|
537
|
-
if spanclass != last
|
538
|
-
result += end_of_span
|
539
|
-
case spanclass
|
540
|
-
when nil
|
541
|
-
end_of_span = ""
|
542
|
-
else
|
543
|
-
result += %[<span class="#{spanclass}">]
|
544
|
-
end_of_span = "</span>"
|
545
|
-
end
|
546
|
-
end
|
547
|
-
result += %[<a name="line#{i+1}" />] + (format_line % (i+1)) +
|
548
|
-
" " + create_cross_refs(file.name, i+1, CGI.escapeHTML(line)) + "\n"
|
549
|
-
last = spanclass
|
550
|
-
end
|
551
|
-
result += end_of_span
|
552
|
-
"<pre>#{result}</pre>"
|
553
|
-
end
|
554
|
-
|
555
|
-
def create_cross_refs(filename, lineno, linetext)
|
556
|
-
return linetext unless @callsite_analyzer
|
557
|
-
@cross_ref_idx ||= 0
|
558
|
-
ret = ""
|
559
|
-
refs = cross_references_for(filename, lineno)
|
560
|
-
return linetext unless refs
|
561
|
-
refs = refs.sort_by{|k,count| count}
|
562
|
-
ret << %[<a class="crossref-toggle" href="#" onclick="toggleCode('XREF-#{@cross_ref_idx+=1}'); return false;">#{linetext}</a>]
|
563
|
-
ret << %[<span class="cross-ref" id="XREF-#{@cross_ref_idx}">]
|
564
|
-
ret << %[\nThis method was called by:\n\n]
|
565
|
-
known_files = sorted_file_pairs.map{|fname, finfo| normalize_filename(fname)}
|
566
|
-
refs.reverse_each do |dst, count|
|
567
|
-
dstfile = normalize_filename(dst.file)
|
568
|
-
dstline = dst.line
|
569
|
-
calling_method = dst.calling_method
|
570
|
-
label = "%7d %s" %
|
571
|
-
[count, CGI.escapeHTML("#{dstfile}:#{dstline} in '#{calling_method}'")]
|
572
|
-
if known_files.include? dstfile
|
573
|
-
ret << %[<a href="#{mangle_filename(dstfile)}#line#{dstline}">#{label}</a>]
|
574
|
-
else
|
575
|
-
ret << label
|
576
|
-
end
|
577
|
-
ret << "\n"
|
578
|
-
end
|
579
|
-
ret << "</span>"
|
580
|
-
end
|
581
|
-
|
582
|
-
def span_class(sourceinfo, marked, count)
|
583
|
-
@span_class_index ^= 1
|
584
|
-
case marked
|
585
|
-
when true
|
586
|
-
"marked#{@span_class_index}"
|
587
|
-
when :inferred
|
588
|
-
"inferred#{@span_class_index}"
|
589
|
-
else
|
590
|
-
"uncovered#{@span_class_index}"
|
591
|
-
end
|
592
|
-
end
|
593
|
-
|
594
|
-
def create_file(destfile, fileinfo)
|
595
|
-
#$stderr.puts "Generating #{destfile.inspect}"
|
596
|
-
body = format_overview(fileinfo) + format_lines(fileinfo)
|
597
|
-
title = fileinfo.name + " - #{default_title}"
|
598
|
-
do_ctable = output_color_table?
|
599
|
-
output = xhtml_ { html_ {
|
600
|
-
head_ {
|
601
|
-
title_{ title }
|
602
|
-
style_(:type => "text/css") { t_{ "body { background-color: #{default_color}; }" } }
|
603
|
-
style_(:type => "text/css") { CSS_PROLOG }
|
604
|
-
script_(:type => "text/javascript") { h_ { JAVASCRIPT_PROLOG } }
|
605
|
-
style_(:type => "text/css") { h_ { colorscale } }
|
606
|
-
}
|
607
|
-
body_ {
|
608
|
-
h3_{ t_{ default_title } }
|
609
|
-
p_ {
|
610
|
-
t_{ "Generated on #{Time.new.to_s} with " }
|
611
|
-
a_(:href => Rcov::UPSTREAM_URL){ "rcov #{Rcov::VERSION}" }
|
612
|
-
}
|
613
|
-
hr_
|
614
|
-
if do_ctable
|
615
|
-
# this kludge needed to ensure .pretty doesn't mangle it
|
616
|
-
x_ { <<EOS
|
617
|
-
<pre><span class='marked0'>Code reported as executed by Ruby looks like this...
|
618
|
-
</span><span class='marked1'>and this: this line is also marked as covered.
|
619
|
-
</span><span class='inferred0'>Lines considered as run by rcov, but not reported by Ruby, look like this,
|
620
|
-
</span><span class='inferred1'>and this: these lines were inferred by rcov (using simple heuristics).
|
621
|
-
</span><span class='uncovered0'>Finally, here's a line marked as not executed.
|
622
|
-
</span></pre>
|
623
|
-
EOS
|
624
|
-
}
|
625
|
-
end
|
626
|
-
x_{ body }
|
627
|
-
hr_
|
628
|
-
x_ { blurb }
|
629
|
-
p_ {
|
630
|
-
a_(:href => "http://validator.w3.org/check/referer") {
|
631
|
-
img_(:src => "http://www.w3.org/Icons/valid-xhtml10",
|
632
|
-
:alt => "Valid XHTML 1.0!", :height => 31, :width => 88)
|
633
|
-
}
|
634
|
-
a_(:href => "http://jigsaw.w3.org/css-validator/check/referer") {
|
635
|
-
img_(:style => "border:0;width:88px;height:31px",
|
636
|
-
:src => "http://jigsaw.w3.org/css-validator/images/vcss",
|
637
|
-
:alt => "Valid CSS!")
|
638
|
-
}
|
639
|
-
}
|
640
|
-
}
|
641
|
-
} }
|
642
|
-
# .pretty needed to make sure DOCTYPE is in a separate line
|
643
|
-
lines = output.pretty.to_a
|
644
|
-
lines.unshift lines.pop if /DOCTYPE/ =~ lines[-1]
|
645
|
-
File.open(destfile, "w") do |f|
|
646
|
-
f.puts lines
|
647
|
-
end
|
648
|
-
end
|
649
|
-
|
650
|
-
def colorscale
|
651
|
-
colorscalebase =<<EOF
|
652
|
-
span.run%d {
|
653
|
-
background-color: rgb(%d, %d, %d);
|
654
|
-
display: block;
|
655
|
-
}
|
656
|
-
EOF
|
657
|
-
cscale = ""
|
658
|
-
101.times do |i|
|
659
|
-
if @color
|
660
|
-
r, g, b = hsv2rgb(220-(2.2*i).to_i, 0.3, 1)
|
661
|
-
r = (r * 255).to_i
|
662
|
-
g = (g * 255).to_i
|
663
|
-
b = (b * 255).to_i
|
664
|
-
else
|
665
|
-
r = g = b = 255 - i
|
666
|
-
end
|
667
|
-
cscale << colorscalebase % [i, r, g, b]
|
668
|
-
end
|
669
|
-
cscale
|
670
|
-
end
|
671
|
-
|
672
|
-
# thanks to kig @ #ruby-lang for this one
|
673
|
-
def hsv2rgb(h,s,v)
|
674
|
-
return [v,v,v] if s == 0
|
675
|
-
h = h/60.0
|
676
|
-
i = h.floor
|
677
|
-
f = h-i
|
678
|
-
p = v * (1-s)
|
679
|
-
q = v * (1-s*f)
|
680
|
-
t = v * (1-s*(1-f))
|
681
|
-
case i
|
682
|
-
when 0
|
683
|
-
r = v
|
684
|
-
g = t
|
685
|
-
b = p
|
686
|
-
when 1
|
687
|
-
r = q
|
688
|
-
g = v
|
689
|
-
b = p
|
690
|
-
when 2
|
691
|
-
r = p
|
692
|
-
g = v
|
693
|
-
b = t
|
694
|
-
when 3
|
695
|
-
r = p
|
696
|
-
g = q
|
697
|
-
b = v
|
698
|
-
when 4
|
699
|
-
r = t
|
700
|
-
g = p
|
701
|
-
b = v
|
702
|
-
when 5
|
703
|
-
r = v
|
704
|
-
g = p
|
705
|
-
b = q
|
706
|
-
end
|
707
|
-
[r,g,b]
|
708
|
-
end
|
709
|
-
end
|
710
|
-
|
711
|
-
class HTMLProfiling < HTMLCoverage
|
712
|
-
|
713
|
-
DEFAULT_OPTS = {:destdir => "profiling"}
|
714
|
-
def initialize(opts = {})
|
715
|
-
options = DEFAULT_OPTS.clone.update(opts)
|
716
|
-
super(options)
|
717
|
-
@max_cache = {}
|
718
|
-
@median_cache = {}
|
719
|
-
end
|
720
|
-
|
721
|
-
def default_title
|
722
|
-
"Bogo-profile information"
|
723
|
-
end
|
724
|
-
|
725
|
-
def default_color
|
726
|
-
if @color
|
727
|
-
"rgb(179,205,255)"
|
728
|
-
else
|
729
|
-
"rgb(255, 255, 255)"
|
730
|
-
end
|
731
|
-
end
|
732
|
-
|
733
|
-
def output_color_table?
|
734
|
-
false
|
735
|
-
end
|
736
|
-
|
737
|
-
def span_class(sourceinfo, marked, count)
|
738
|
-
full_scale_range = @fsr # dB
|
739
|
-
nz_count = sourceinfo.counts.select{|x| x && x != 0}
|
740
|
-
nz_count << 1 # avoid div by 0
|
741
|
-
max = @max_cache[sourceinfo] ||= nz_count.max
|
742
|
-
#avg = @median_cache[sourceinfo] ||= 1.0 *
|
743
|
-
# nz_count.inject{|a,b| a+b} / nz_count.size
|
744
|
-
median = @median_cache[sourceinfo] ||= 1.0 * nz_count.sort[nz_count.size/2]
|
745
|
-
max ||= 2
|
746
|
-
max = 2 if max == 1
|
747
|
-
if marked == true
|
748
|
-
count = 1 if !count || count == 0
|
749
|
-
idx = 50 + 1.0 * (500/full_scale_range) * Math.log(count/median) /
|
750
|
-
Math.log(10)
|
751
|
-
idx = idx.to_i
|
752
|
-
idx = 0 if idx < 0
|
753
|
-
idx = 100 if idx > 100
|
754
|
-
"run#{idx}"
|
755
|
-
else
|
756
|
-
nil
|
757
|
-
end
|
758
|
-
end
|
759
|
-
end
|
760
|
-
|
761
|
-
end # Rcov
|
762
|
-
|
763
|
-
#{{{ only run if executed directly
|
33
|
+
require 'rcov/report'
|
764
34
|
|
765
35
|
#{{{ "main" code
|
766
36
|
options = OpenStruct.new
|
@@ -772,13 +42,20 @@ options.loadpaths = []
|
|
772
42
|
options.textmode = false
|
773
43
|
options.skip = Rcov::Formatter::DEFAULT_OPTS[:ignore]
|
774
44
|
options.include = []
|
775
|
-
options.
|
45
|
+
options.html = true
|
46
|
+
options.comments_run_by_default = false
|
776
47
|
options.test_unit_only = false
|
777
48
|
options.sort = :name
|
778
49
|
options.sort_reverse = false
|
779
50
|
options.output_threshold = 101
|
780
51
|
options.replace_prog_name = false
|
781
52
|
options.callsites = false
|
53
|
+
options.crossrefs = false
|
54
|
+
options.coverage_diff_file = "coverage.info"
|
55
|
+
options.coverage_diff_mode = :compare
|
56
|
+
options.coverage_diff_save = false
|
57
|
+
options.diff_cmd = "diff"
|
58
|
+
options.report_cov_bug_for = nil
|
782
59
|
|
783
60
|
EXTRA_HELP = <<-EOF
|
784
61
|
|
@@ -813,6 +90,11 @@ EOF
|
|
813
90
|
"Prepend PATHS to $: (colon separated list)") do |paths|
|
814
91
|
options.loadpaths = paths.split(/:/)
|
815
92
|
end
|
93
|
+
opts.on("--[no-]comments",
|
94
|
+
"Mark all comments by default.",
|
95
|
+
"(default: --no-comments)") do |comments_run_p|
|
96
|
+
options.comments_run_by_default = comments_run_p
|
97
|
+
end
|
816
98
|
opts.on("--test-unit-only",
|
817
99
|
"Only trace code executed in TestCases.") do
|
818
100
|
options.test_unit_only = true
|
@@ -853,9 +135,14 @@ EOF
|
|
853
135
|
options.skip.concat [%r{\bvendor/},%r{\bconfig/},%r{\benvironment/}]
|
854
136
|
end
|
855
137
|
opts.on("--[no-]callsites", "Show callsites in generated XHTML report.",
|
856
|
-
"(
|
138
|
+
"(somewhat slower; disabled by default)") do |val|
|
857
139
|
options.callsites = val
|
858
140
|
end
|
141
|
+
opts.on("--[no-]xrefs", "Generate fully cross-referenced report.",
|
142
|
+
"(includes --callsites)") do |val|
|
143
|
+
options.crossrefs = val
|
144
|
+
options.callsites ||= val
|
145
|
+
end
|
859
146
|
opts.on("-p", "--profile", "Generate bogo-profiling info.") do
|
860
147
|
options.profiling = true
|
861
148
|
options.destdir ||= "profiling"
|
@@ -878,9 +165,30 @@ EOF
|
|
878
165
|
"ANSI color sequences unless -n.") do
|
879
166
|
options.textmode = :coverage
|
880
167
|
end
|
881
|
-
opts.on("
|
882
|
-
"
|
883
|
-
|
168
|
+
opts.on("-D [FILE]", "--text-coverage-diff [FILE]",
|
169
|
+
"Compare code coverage with saved state",
|
170
|
+
"in FILE, defaults to coverage.info.",
|
171
|
+
"Implies --comments.") do |file|
|
172
|
+
options.textmode = :coverage_diff
|
173
|
+
options.comments_run_by_default = true
|
174
|
+
if options.coverage_diff_save
|
175
|
+
raise "You shouldn't use --save and --text-coverage-diff at a time."
|
176
|
+
end
|
177
|
+
options.coverage_diff_mode = :compare
|
178
|
+
options.coverage_diff_file = file if file && !file.empty?
|
179
|
+
end
|
180
|
+
opts.on("--save [FILE]", "Save coverage data to FILE.",
|
181
|
+
"(default: coverage.info)") do |file|
|
182
|
+
options.coverage_diff_save = true
|
183
|
+
options.coverage_diff_mode = :record
|
184
|
+
if options.textmode == :coverage_diff
|
185
|
+
raise "You shouldn't use --save and --text-coverage-diff at a time."
|
186
|
+
end
|
187
|
+
options.coverage_diff_file = file if file && !file.empty?
|
188
|
+
end
|
189
|
+
opts.on("--[no-]html", "Generate HTML output.",
|
190
|
+
"(default: --html)") do |val|
|
191
|
+
options.html = val
|
884
192
|
end
|
885
193
|
opts.on("--sort CRITERION", [:name, :loc, :coverage],
|
886
194
|
"Sort files in the output by the specified",
|
@@ -913,8 +221,12 @@ EOF
|
|
913
221
|
"(will run 30-300 times slower)") do
|
914
222
|
$rcov_do_not_use_rcovrt = true
|
915
223
|
end
|
224
|
+
opts.on("--diff-cmd PROGNAME", "Use PROGNAME for --text-coverage-diff.",
|
225
|
+
"(default: diff)") do |cmd|
|
226
|
+
options.diff_cmd = cmd
|
227
|
+
end
|
916
228
|
opts.separator ""
|
917
|
-
opts.on_tail("-h", "--help", "Show help message") do
|
229
|
+
opts.on_tail("-h", "--help", "Show extended help message") do
|
918
230
|
require 'pp'
|
919
231
|
puts opts
|
920
232
|
puts <<EOF
|
@@ -925,12 +237,26 @@ EOF
|
|
925
237
|
puts EXTRA_HELP
|
926
238
|
exit
|
927
239
|
end
|
240
|
+
opts.on_tail("--report-cov-bug SELECTOR",
|
241
|
+
"Report coverage analysis bug for the",
|
242
|
+
"method specified by SELECTOR",
|
243
|
+
"(format: Foo::Bar#method, A::B.method)") do |selector|
|
244
|
+
case selector
|
245
|
+
when /([^.]+)(#|\.)(.*)/: options.report_cov_bug_for = selector
|
246
|
+
else
|
247
|
+
raise OptionParser::InvalidArgument, selector
|
248
|
+
end
|
249
|
+
options.textmode = nil
|
250
|
+
options.html = false
|
251
|
+
options.callsites = true
|
252
|
+
end
|
928
253
|
opts.on_tail("--version", "Show version") do
|
929
254
|
puts "rcov " + Rcov::VERSION + " " + Rcov::RELEASE_DATE
|
930
255
|
exit
|
931
256
|
end
|
932
257
|
end
|
933
258
|
|
259
|
+
$ORIGINAL_ARGV = ARGV.clone
|
934
260
|
if (idx = ARGV.index("--"))
|
935
261
|
extra_args = ARGV[idx+1..-1]
|
936
262
|
ARGV.replace(ARGV[0,idx])
|
@@ -961,6 +287,8 @@ options.loadpaths.reverse_each{|x| $:.unshift x}
|
|
961
287
|
|
962
288
|
require 'rcov'
|
963
289
|
|
290
|
+
options.callsites = true if options.report_cov_bug_for
|
291
|
+
|
964
292
|
if options.callsites
|
965
293
|
$rcov_callsite_analyzer = Rcov::CallSiteAnalyzer.new
|
966
294
|
$rcov_callsite_analyzer.install_hook
|
@@ -974,26 +302,37 @@ make_formatter = lambda do |klass|
|
|
974
302
|
klass.new(:destdir => options.destdir, :color => options.color,
|
975
303
|
:fsr => options.range, :textmode => options.textmode,
|
976
304
|
:ignore => options.skip, :dont_ignore => options.include,
|
977
|
-
:sort => options.sort,
|
305
|
+
:sort => options.sort,
|
978
306
|
:sort_reverse => options.sort_reverse,
|
979
307
|
:output_threshold => options.output_threshold,
|
980
|
-
:callsite_analyzer => $rcov_callsite_analyzer
|
308
|
+
:callsite_analyzer => $rcov_callsite_analyzer,
|
309
|
+
:coverage_diff_mode => options.coverage_diff_mode,
|
310
|
+
:coverage_diff_file => options.coverage_diff_file,
|
311
|
+
:callsites => options.callsites,
|
312
|
+
:cross_references => options.crossrefs,
|
313
|
+
:diff_cmd => options.diff_cmd,
|
314
|
+
:comments_run_by_default => options.comments_run_by_default
|
315
|
+
)
|
981
316
|
end
|
982
317
|
|
983
|
-
|
318
|
+
if options.html
|
984
319
|
if options.profiling
|
985
320
|
formatters << make_formatter[Rcov::HTMLProfiling]
|
986
321
|
else
|
987
322
|
formatters << make_formatter[Rcov::HTMLCoverage]
|
988
323
|
end
|
989
324
|
end
|
990
|
-
textual_formatters = {:counts => Rcov::
|
991
|
-
:
|
325
|
+
textual_formatters = {:counts => Rcov::FullTextReport,
|
326
|
+
:coverage => Rcov::FullTextReport,
|
327
|
+
:summary => Rcov::TextSummary, :report => Rcov::TextReport,
|
328
|
+
:coverage_diff => Rcov::TextCoverageDiff}
|
992
329
|
|
993
330
|
if textual_formatters[options.textmode]
|
994
331
|
formatters << make_formatter[textual_formatters[options.textmode]]
|
995
332
|
end
|
996
333
|
|
334
|
+
formatters << make_formatter[Rcov::TextCoverageDiff] if options.coverage_diff_save
|
335
|
+
|
997
336
|
$rcov_code_coverage_analyzer = Rcov::CodeCoverageAnalyzer.new
|
998
337
|
|
999
338
|
# must be registered before test/unit puts its own
|
@@ -1001,6 +340,49 @@ END {
|
|
1001
340
|
$rcov_code_coverage_analyzer.remove_hook
|
1002
341
|
$rcov_callsite_analyzer.remove_hook if $rcov_callsite_analyzer
|
1003
342
|
$rcov_code_coverage_analyzer.dump_coverage_info(formatters)
|
343
|
+
if options.report_cov_bug_for
|
344
|
+
defsite = $rcov_callsite_analyzer.defsite(options.report_cov_bug_for)
|
345
|
+
if !defsite
|
346
|
+
$stderr.puts <<-EOF
|
347
|
+
Couldn't find definition site of #{options.report_cov_bug_for}.
|
348
|
+
Was it executed at all?
|
349
|
+
EOF
|
350
|
+
exit(-1)
|
351
|
+
end
|
352
|
+
lines, mark_info, count_info = $rcov_code_coverage_analyzer.data(defsite.file)
|
353
|
+
puts <<EOF
|
354
|
+
|
355
|
+
Please fill in the blanks in the following report.
|
356
|
+
|
357
|
+
You can report the bug via the Ruby-Talk ML, send it directly to
|
358
|
+
<mfp at acm dot org> (include "rcov" in the subject to get past the spam filters),
|
359
|
+
or post it to
|
360
|
+
http://eigenclass.org/hiki.rb?rcov+#{VERSION}
|
361
|
+
|
362
|
+
Thank you!
|
363
|
+
|
364
|
+
=============================================================================
|
365
|
+
Bug report generated on #{Time.new}
|
366
|
+
|
367
|
+
Ruby version: #{RUBY_VERSION} (#{RUBY_RELEASE_DATE})
|
368
|
+
Platform: #{RUBY_PLATFORM}
|
369
|
+
rcov version: #{Rcov::VERSION}
|
370
|
+
rcovrt loaded? #{$".any?{|x| /\brcovrt\b/ =~ x} }
|
371
|
+
using RubyGems? #{$".any?{|x| /\brubygems\b/ =~ x} }
|
372
|
+
Command-line arguments: #{$ORIGINAL_ARGV.inspect}
|
373
|
+
Coverage analysis bug in: #{options.report_cov_bug_for}
|
374
|
+
|
375
|
+
Line(s) ____________ should be ______ (red/green).
|
376
|
+
|
377
|
+
Raw coverage information (feel free to remove useless data, but please leave
|
378
|
+
some context around the faulty lines):
|
379
|
+
|
380
|
+
EOF
|
381
|
+
defsite.line.upto(SCRIPT_LINES__[defsite.file].size) do |i|
|
382
|
+
puts "%7d:%5d:%s" % [count_info[i-1], i, lines[i-1]]
|
383
|
+
end
|
384
|
+
exit
|
385
|
+
end
|
1004
386
|
if formatters.all?{|formatter| formatter.sorted_file_pairs.empty? }
|
1005
387
|
require 'pp'
|
1006
388
|
$stderr.puts <<-EOF
|
@@ -1022,7 +404,6 @@ You can solve this by doing one or more of the following:
|
|
1022
404
|
to test/test_*.rb, or running rcov via a Rakefile (read the RDoc
|
1023
405
|
documentation or README.rake in the source distribution).
|
1024
406
|
EOF
|
1025
|
-
exit(-1)
|
1026
407
|
end
|
1027
408
|
}
|
1028
409
|
|