rcov 0.5.0.1-mswin32 → 0.6.0.1-mswin32
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|