rcov 0.5.0.1 → 0.6.0.1

Sign up to get free protection for your applications and to get access to all the features.
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.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.rcov_opts << "--callsites" # comment to disable cross-references
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", "lib/**/*.rb"]
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_ "&nbsp;"
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&apos;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.nohtml = false
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
- "(much slower; disabled by default)") do |val|
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("--no-html", "Don't generate HTML output.",
882
- "(no output unless text output specified)") do
883
- options.nohtml = true
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
- unless options.nohtml
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::TextCoverage, :coverage => Rcov::TextCoverage,
991
- :summary => Rcov::TextSummary, :report => Rcov::TextReport}
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