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 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