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/lib/rcov/rant.rb CHANGED
@@ -1,8 +1,9 @@
1
1
 
2
2
  require 'rant/rantlib'
3
3
 
4
- module Rant # :nodoc:
5
- class Generators::Rcov
4
+ module Rant # :nodoc:
5
+ module Generators # :nodoc:
6
+ class Rcov # :nodoc:
6
7
  def self.rant_gen(app, ch, args, &block)
7
8
  if !args || args.empty?
8
9
  self.new(app, ch, &block)
@@ -81,5 +82,6 @@ module Rant # :nodoc:
81
82
  end
82
83
  filelist
83
84
  end
84
- end # class Generators::RubyTest
85
+ end # class Rcov
86
+ end # module Generators
85
87
  end # module Rant
data/lib/rcov/rcovtask.rb CHANGED
@@ -112,9 +112,9 @@ module Rcov
112
112
  else %!"#{rcov_path}"!
113
113
  end
114
114
  ruby_opts = @ruby_opts.clone
115
- ruby_opts.unshift run_code
116
- ruby_opts.unshift( "-I#{lib_path}" )
117
- ruby_opts.unshift( "-w" ) if @warning
115
+ ruby_opts.push run_code
116
+ ruby_opts.push( "-I#{lib_path}" )
117
+ ruby_opts.push( "-w" ) if @warning
118
118
  ruby ruby_opts.join(" ") + " " + option_list +
119
119
  %[ -o "#{@output_dir}" ] +
120
120
  file_list.collect { |fn| %["#{fn}"] }.join(' ')
@@ -0,0 +1,1005 @@
1
+ # rcov Copyright (c) 2004-2006 Mauricio Fernandez <mfp@acm.org>
2
+ # See LEGAL and LICENSE for additional licensing information.
3
+
4
+ module Rcov
5
+
6
+ class Formatter # :nodoc:
7
+ ignore_files = [/\A#{Regexp.escape(Config::CONFIG["libdir"])}/, /\btc_[^.]*.rb/,
8
+ /_test\.rb\z/, /\btest\//, /\bvendor\//, /\A#{Regexp.escape(__FILE__)}\z/]
9
+ DEFAULT_OPTS = {:ignore => ignore_files, :sort => :name, :sort_reverse => false,
10
+ :output_threshold => 101, :dont_ignore => [],
11
+ :callsite_analyzer => nil, :comments_run_by_default => false}
12
+ def initialize(opts = {})
13
+ options = DEFAULT_OPTS.clone.update(opts)
14
+ @files = {}
15
+ @ignore_files = options[:ignore]
16
+ @dont_ignore_files = options[:dont_ignore]
17
+ @sort_criterium = case options[:sort]
18
+ when :loc : lambda{|fname, finfo| finfo.num_code_lines}
19
+ when :coverage : lambda{|fname, finfo| finfo.code_coverage}
20
+ else lambda{|fname, finfo| fname}
21
+ end
22
+ @sort_reverse = options[:sort_reverse]
23
+ @output_threshold = options[:output_threshold]
24
+ @callsite_analyzer = options[:callsite_analyzer]
25
+ @comments_run_by_default = options[:comments_run_by_default]
26
+ @callsite_index = nil
27
+ end
28
+
29
+ def add_file(filename, lines, coverage, counts)
30
+ old_filename = filename
31
+ filename = normalize_filename(filename)
32
+ SCRIPT_LINES__[filename] = SCRIPT_LINES__[old_filename]
33
+ if @ignore_files.any?{|x| x === filename} &&
34
+ !@dont_ignore_files.any?{|x| x === filename}
35
+ return nil
36
+ end
37
+ if @files[filename]
38
+ @files[filename].merge(lines, coverage, counts)
39
+ else
40
+ @files[filename] = FileStatistics.new(filename, lines, counts,
41
+ @comments_run_by_default)
42
+ end
43
+ end
44
+
45
+ def normalize_filename(filename)
46
+ File.expand_path(filename).gsub(/^#{Regexp.escape(Dir.getwd)}\//, '')
47
+ end
48
+
49
+ def mangle_filename(base)
50
+ base.gsub(%r{^\w:[/\\]}, "").gsub(/\./, "_").gsub(/[\\\/]/, "-") + ".html"
51
+ end
52
+
53
+ def each_file_pair_sorted(&b)
54
+ return sorted_file_pairs unless block_given?
55
+ sorted_file_pairs.each(&b)
56
+ end
57
+
58
+ def sorted_file_pairs
59
+ pairs = @files.sort_by do |fname, finfo|
60
+ @sort_criterium.call(fname, finfo)
61
+ end.select{|_, finfo| 100 * finfo.code_coverage < @output_threshold}
62
+ @sort_reverse ? pairs.reverse : pairs
63
+ end
64
+
65
+ def total_coverage
66
+ lines = 0
67
+ total = 0.0
68
+ @files.each do |k,f|
69
+ total += f.num_lines * f.total_coverage
70
+ lines += f.num_lines
71
+ end
72
+ return 0 if lines == 0
73
+ total / lines
74
+ end
75
+
76
+ def code_coverage
77
+ lines = 0
78
+ total = 0.0
79
+ @files.each do |k,f|
80
+ total += f.num_code_lines * f.code_coverage
81
+ lines += f.num_code_lines
82
+ end
83
+ return 0 if lines == 0
84
+ total / lines
85
+ end
86
+
87
+ def num_code_lines
88
+ lines = 0
89
+ @files.each{|k, f| lines += f.num_code_lines }
90
+ lines
91
+ end
92
+
93
+ def num_lines
94
+ lines = 0
95
+ @files.each{|k, f| lines += f.num_lines }
96
+ lines
97
+ end
98
+
99
+ private
100
+ def cross_references_for(filename, lineno)
101
+ return nil unless @callsite_analyzer
102
+ @callsite_index ||= build_callsite_index
103
+ @callsite_index[normalize_filename(filename)][lineno]
104
+ end
105
+
106
+ def reverse_cross_references_for(filename, lineno)
107
+ return nil unless @callsite_analyzer
108
+ @callsite_reverse_index ||= build_reverse_callsite_index
109
+ @callsite_reverse_index[normalize_filename(filename)][lineno]
110
+ end
111
+
112
+ def build_callsite_index
113
+ index = Hash.new{|h,k| h[k] = {}}
114
+ @callsite_analyzer.analyzed_classes.each do |classname|
115
+ @callsite_analyzer.analyzed_methods(classname).each do |methname|
116
+ defsite = @callsite_analyzer.defsite(classname, methname)
117
+ index[normalize_filename(defsite.file)][defsite.line] =
118
+ @callsite_analyzer.callsites(classname, methname)
119
+ end
120
+ end
121
+ index
122
+ end
123
+
124
+ def build_reverse_callsite_index
125
+ index = Hash.new{|h,k| h[k] = {}}
126
+ @callsite_analyzer.analyzed_classes.each do |classname|
127
+ @callsite_analyzer.analyzed_methods(classname).each do |methname|
128
+ callsites = @callsite_analyzer.callsites(classname, methname)
129
+ defsite = @callsite_analyzer.defsite(classname, methname)
130
+ callsites.each_pair do |callsite, count|
131
+ next unless callsite.file
132
+ fname = normalize_filename(callsite.file)
133
+ (index[fname][callsite.line] ||= []) << [classname, methname, defsite, count]
134
+ end
135
+ end
136
+ end
137
+ index
138
+ end
139
+ end
140
+
141
+ class TextSummary < Formatter # :nodoc:
142
+ def execute
143
+ puts summary
144
+ end
145
+
146
+ def summary
147
+ "%.1f%% %d file(s) %d Lines %d LOC" % [code_coverage * 100,
148
+ @files.size, num_lines, num_code_lines]
149
+ end
150
+ end
151
+
152
+ class TextReport < TextSummary # :nodoc:
153
+ def execute
154
+ print_lines
155
+ print_header
156
+ print_lines
157
+ each_file_pair_sorted do |fname, finfo|
158
+ name = fname.size < 52 ? fname : "..." + fname[-48..-1]
159
+ print_info(name, finfo.num_lines, finfo.num_code_lines,
160
+ finfo.code_coverage)
161
+ end
162
+ print_lines
163
+ print_info("Total", num_lines, num_code_lines, code_coverage)
164
+ print_lines
165
+ puts summary
166
+ end
167
+
168
+ def print_info(name, lines, loc, coverage)
169
+ puts "|%-51s | %5d | %5d | %5.1f%% |" % [name, lines, loc, 100 * coverage]
170
+ end
171
+
172
+ def print_lines
173
+ puts "+----------------------------------------------------+-------+-------+--------+"
174
+ end
175
+
176
+ def print_header
177
+ puts "| File | Lines | LOC | COV |"
178
+ end
179
+ end
180
+
181
+ class FullTextReport < Formatter # :nodoc:
182
+ DEFAULT_OPTS = {:textmode => :coverage}
183
+ def initialize(opts = {})
184
+ options = DEFAULT_OPTS.clone.update(opts)
185
+ @textmode = options[:textmode]
186
+ @color = options[:color]
187
+ super(options)
188
+ end
189
+
190
+ def execute
191
+ each_file_pair_sorted do |filename, fileinfo|
192
+ puts "=" * 80
193
+ puts filename
194
+ puts "=" * 80
195
+ SCRIPT_LINES__[filename].each_with_index do |line, i|
196
+ case @textmode
197
+ when :counts
198
+ puts "%-70s| %6d" % [line.chomp[0,70], fileinfo.counts[i]]
199
+ when :coverage
200
+ if @color
201
+ prefix = fileinfo.coverage[i] ? "\e[32;40m" : "\e[31;40m"
202
+ puts "#{prefix}%s\e[37;40m" % line.chomp
203
+ else
204
+ prefix = fileinfo.coverage[i] ? " " : "!! "
205
+ puts "#{prefix}#{line}"
206
+ end
207
+ end
208
+ end
209
+ end
210
+ end
211
+ end
212
+
213
+ class TextCoverageDiff < Formatter # :nodoc:
214
+ FORMAT_VERSION = [0, 1, 0]
215
+ DEFAULT_OPTS = {:textmode => :coverage_diff,
216
+ :coverage_diff_mode => :record,
217
+ :coverage_diff_file => "coverage.info",
218
+ :diff_cmd => "diff", :comments_run_by_default => true}
219
+ def SERIALIZER
220
+ # mfp> this was going to be YAML but I caught it failing at basic
221
+ # round-tripping, turning "\n" into "" and corrupting the data, so
222
+ # it must be Marshal for now
223
+ Marshal
224
+ end
225
+
226
+ def initialize(opts = {})
227
+ options = DEFAULT_OPTS.clone.update(opts)
228
+ @textmode = options[:textmode]
229
+ @color = options[:color]
230
+ @mode = options[:coverage_diff_mode]
231
+ @state_file = options[:coverage_diff_file]
232
+ @diff_cmd = options[:diff_cmd]
233
+ super(options)
234
+ end
235
+
236
+ def execute
237
+ case @mode
238
+ when :record
239
+ record_state
240
+ when :compare
241
+ compare_state
242
+ else
243
+ raise "Unknown TextCoverageDiff mode: #{mode.inspect}."
244
+ end
245
+ end
246
+
247
+ def record_state
248
+ state = {}
249
+ each_file_pair_sorted do |filename, fileinfo|
250
+ state[filename] = {:lines => SCRIPT_LINES__[filename],
251
+ :coverage => fileinfo.coverage.to_a,
252
+ :counts => fileinfo.counts}
253
+ end
254
+ File.open(@state_file, "w") do |f|
255
+ self.SERIALIZER.dump([FORMAT_VERSION, state], f)
256
+ end
257
+ rescue
258
+ $stderr.puts <<-EOF
259
+ Couldn't save coverage data to #{@state_file}.
260
+ EOF
261
+ end
262
+
263
+ require 'tempfile'
264
+ def compare_state
265
+ return unless verify_diff_available
266
+ begin
267
+ format, prev_state = File.open(@state_file){|f| self.SERIALIZER.load(f) }
268
+ rescue
269
+ $stderr.puts <<-EOF
270
+ Couldn't load coverage data from #{@state_file}.
271
+ EOF
272
+ return
273
+ end
274
+ if !(Array === format) or
275
+ FORMAT_VERSION[0] != format[0] || FORMAT_VERSION[1] < format[1]
276
+ $stderr.puts <<-EOF
277
+ Couldn't load coverage data from #{@state_file}.
278
+ The file is saved in the format #{format.inspect[0..20]}.
279
+ This rcov executable understands #{FORMAT_VERSION.inspect}.
280
+ EOF
281
+ return
282
+ end
283
+ each_file_pair_sorted do |filename, fileinfo|
284
+ old_data = Tempfile.new("#{mangle_filename(filename)}-old")
285
+ new_data = Tempfile.new("#{mangle_filename(filename)}-new")
286
+ if prev_state.has_key? filename
287
+ old_code, old_cov = prev_state[filename].values_at(:lines, :coverage)
288
+ old_code.each_with_index do |line, i|
289
+ prefix = old_cov[i] ? " " : "!! "
290
+ old_data.write "#{prefix}#{line}"
291
+ end
292
+ else
293
+ old_data.write ""
294
+ end
295
+ old_data.close
296
+ SCRIPT_LINES__[filename].each_with_index do |line, i|
297
+ prefix = fileinfo.coverage[i] ? " " : "!! "
298
+ new_data.write "#{prefix}#{line}"
299
+ end
300
+ new_data.close
301
+
302
+ diff = `#{@diff_cmd} -u #{old_data.path} #{new_data.path}`
303
+ new_uncovered_hunks = process_unified_diff(filename, diff)
304
+ old_data.close!
305
+ new_data.close!
306
+ display_hunks(filename, new_uncovered_hunks)
307
+ end
308
+ end
309
+
310
+ def display_hunks(filename, hunks)
311
+ return if hunks.empty?
312
+ puts
313
+ puts "=" * 80
314
+ puts <<EOF
315
+ !!!!! Uncovered code introduced in #{filename}
316
+
317
+ EOF
318
+ hunks.each do |offset, lines|
319
+ puts "### #{filename}:#{offset}"
320
+ if @color
321
+ lines.each do |line|
322
+ prefix = (/^!! / !~ line) ? "\e[32;40m" : "\e[31;40m"
323
+ puts "#{prefix}#{line[3..-1].chomp}\e[37;40m"
324
+ end
325
+ else
326
+ puts lines
327
+ end
328
+ end
329
+ end
330
+
331
+ def verify_diff_available
332
+ old_stderr = STDERR.dup
333
+ old_stdout = STDOUT.dup
334
+ # TODO: should use /dev/null or NUL(?), but I don't want to add the
335
+ # win32 check right now
336
+ new_stderr = Tempfile.new("rcov_check_diff")
337
+ STDERR.reopen new_stderr.path
338
+ STDOUT.reopen new_stderr.path
339
+
340
+ retval = system "#{@diff_cmd} --version"
341
+ unless retval
342
+ old_stderr.puts <<EOF
343
+
344
+ The '#{@diff_cmd}' executable seems not to be available.
345
+ You can specify which diff executable should be used with --diff-cmd.
346
+ If your system doesn't have one, you might want to use Diff::LCS's:
347
+ gem install diff-lcs
348
+ and use --diff-cmd=ldiff.
349
+ EOF
350
+ return false
351
+ end
352
+ true
353
+ ensure
354
+ STDOUT.reopen old_stdout
355
+ STDERR.reopen old_stderr
356
+ new_stderr.close!
357
+ end
358
+
359
+ HUNK_HEADER = /@@ -\d+,\d+ \+(\d+),(\d+) @@/
360
+ def process_unified_diff(filename, diff)
361
+ current_hunk = []
362
+ current_hunk_start = 0
363
+ keep_current_hunk = false
364
+ state = :init
365
+ interesting_hunks = []
366
+ diff.each_with_index do |line, i|
367
+ #puts "#{state} %5d #{line}" % i
368
+ case state
369
+ when :init
370
+ if md = HUNK_HEADER.match(line)
371
+ current_hunk = []
372
+ current_hunk_start = md[1].to_i
373
+ state = :body
374
+ end
375
+ when :body
376
+ case line
377
+ when HUNK_HEADER
378
+ new_start = $1.to_i
379
+ if keep_current_hunk
380
+ interesting_hunks << [current_hunk_start, current_hunk]
381
+ end
382
+ current_hunk_start = new_start
383
+ current_hunk = []
384
+ keep_current_hunk = false
385
+ when /^-/
386
+ # ignore
387
+ when /^\+!! /
388
+ keep_current_hunk = true
389
+ current_hunk << line[1..-1]
390
+ else
391
+ current_hunk << line[1..-1]
392
+ end
393
+ end
394
+ end
395
+ if keep_current_hunk
396
+ interesting_hunks << [current_hunk_start, current_hunk]
397
+ end
398
+
399
+ interesting_hunks
400
+ end
401
+ end
402
+
403
+
404
+ class HTMLCoverage < Formatter # :nodoc:
405
+ include XX::XHTML
406
+ include XX::XMLish
407
+ require 'fileutils'
408
+ JAVASCRIPT_PROLOG = <<-EOS
409
+
410
+ // <![CDATA[
411
+ function toggleCode( id ) {
412
+ if ( document.getElementById )
413
+ elem = document.getElementById( id );
414
+ else if ( document.all )
415
+ elem = eval( "document.all." + id );
416
+ else
417
+ return false;
418
+
419
+ elemStyle = elem.style;
420
+
421
+ if ( elemStyle.display != "block" ) {
422
+ elemStyle.display = "block"
423
+ } else {
424
+ elemStyle.display = "none"
425
+ }
426
+
427
+ return true;
428
+ }
429
+
430
+ // Make cross-references hidden by default
431
+ document.writeln( "<style type=\\"text/css\\">span.cross-ref { display: none }</style>" )
432
+ // ]]>
433
+ EOS
434
+
435
+ CSS_PROLOG = <<-EOS
436
+ span.cross-ref-title {
437
+ font-size: 140%;
438
+ }
439
+ span.cross-ref a {
440
+ text-decoration: none;
441
+ }
442
+ span.cross-ref {
443
+ background-color:#f3f7fa;
444
+ border: 1px dashed #333;
445
+ margin: 1em;
446
+ padding: 0.5em;
447
+ overflow: hidden;
448
+ }
449
+ a.crossref-toggle {
450
+ text-decoration: none;
451
+ }
452
+ span.marked0 {
453
+ background-color: rgb(185, 210, 200);
454
+ display: block;
455
+ }
456
+ span.marked1 {
457
+ background-color: rgb(190, 215, 205);
458
+ display: block;
459
+ }
460
+ span.inferred0 {
461
+ background-color: rgb(175, 200, 200);
462
+ display: block;
463
+ }
464
+ span.inferred1 {
465
+ background-color: rgb(180, 205, 205);
466
+ display: block;
467
+ }
468
+ span.uncovered0 {
469
+ background-color: rgb(225, 110, 110);
470
+ display: block;
471
+ }
472
+ span.uncovered1 {
473
+ background-color: rgb(235, 120, 120);
474
+ display: block;
475
+ }
476
+ span.overview {
477
+ border-bottom: 8px solid black;
478
+ }
479
+ div.overview {
480
+ border-bottom: 8px solid black;
481
+ }
482
+ body {
483
+ font-family: verdana, arial, helvetica;
484
+ }
485
+
486
+ div.footer {
487
+ font-size: 68%;
488
+ margin-top: 1.5em;
489
+ }
490
+
491
+ h1, h2, h3, h4, h5, h6 {
492
+ margin-bottom: 0.5em;
493
+ }
494
+
495
+ h5 {
496
+ margin-top: 0.5em;
497
+ }
498
+
499
+ .hidden {
500
+ display: none;
501
+ }
502
+
503
+ div.separator {
504
+ height: 10px;
505
+ }
506
+ /* Commented out for better readability, esp. on IE */
507
+ /*
508
+ table tr td, table tr th {
509
+ font-size: 68%;
510
+ }
511
+
512
+ td.value table tr td {
513
+ font-size: 11px;
514
+ }
515
+ */
516
+
517
+ table.percent_graph {
518
+ height: 12px;
519
+ border: #808080 1px solid;
520
+ empty-cells: show;
521
+ }
522
+
523
+ table.percent_graph td.covered {
524
+ height: 10px;
525
+ background: #00f000;
526
+ }
527
+
528
+ table.percent_graph td.uncovered {
529
+ height: 10px;
530
+ background: #e00000;
531
+ }
532
+
533
+ table.percent_graph td.NA {
534
+ height: 10px;
535
+ background: #eaeaea;
536
+ }
537
+
538
+ table.report {
539
+ border-collapse: collapse;
540
+ width: 100%;
541
+ }
542
+
543
+ table.report td.heading {
544
+ background: #dcecff;
545
+ border: #d0d0d0 1px solid;
546
+ font-weight: bold;
547
+ text-align: center;
548
+ }
549
+
550
+ table.report td.heading:hover {
551
+ background: #c0ffc0;
552
+ }
553
+
554
+ table.report td.text {
555
+ border: #d0d0d0 1px solid;
556
+ }
557
+
558
+ table.report td.value {
559
+ text-align: right;
560
+ border: #d0d0d0 1px solid;
561
+ }
562
+ table.report tr.light {
563
+ background-color: rgb(240, 240, 245);
564
+ }
565
+ table.report tr.dark {
566
+ background-color: rgb(230, 230, 235);
567
+ }
568
+ EOS
569
+
570
+ DEFAULT_OPTS = {:color => false, :fsr => 30, :destdir => "coverage",
571
+ :callsites => false, :cross_references => false}
572
+ def initialize(opts = {})
573
+ options = DEFAULT_OPTS.clone.update(opts)
574
+ super(options)
575
+ @dest = options[:destdir]
576
+ @color = options[:color]
577
+ @fsr = options[:fsr]
578
+ @do_callsites = options[:callsites]
579
+ @do_cross_references = options[:cross_references]
580
+ @span_class_index = 0
581
+ end
582
+
583
+ def execute
584
+ return if @files.empty?
585
+ FileUtils.mkdir_p @dest
586
+ create_index(File.join(@dest, "index.html"))
587
+ each_file_pair_sorted do |filename, fileinfo|
588
+ create_file(File.join(@dest, mangle_filename(filename)), fileinfo)
589
+ end
590
+ end
591
+
592
+ private
593
+
594
+ def blurb
595
+ xmlish_ {
596
+ p_ {
597
+ t_{ "Generated using the " }
598
+ a_(:href => "http://eigenclass.org/hiki.rb?rcov") {
599
+ t_{ "rcov code coverage analysis tool for Ruby" }
600
+ }
601
+ t_{ " version #{Rcov::VERSION}." }
602
+ }
603
+ }.pretty
604
+ end
605
+
606
+ def output_color_table?
607
+ true
608
+ end
609
+
610
+ def default_color
611
+ "rgb(240, 240, 245)"
612
+ end
613
+
614
+ def default_title
615
+ "C0 code coverage information"
616
+ end
617
+
618
+ def format_overview(*file_infos)
619
+ table_text = xmlish_ {
620
+ table_(:class => "report") {
621
+ thead_ {
622
+ tr_ {
623
+ ["Name", "Total lines", "Lines of code", "Total coverage",
624
+ "Code coverage"].each do |heading|
625
+ td_(:class => "heading") { heading }
626
+ end
627
+ }
628
+ }
629
+ tbody_ {
630
+ color_class_index = 1
631
+ color_classes = %w[light dark]
632
+ file_infos.each do |f|
633
+ color_class_index += 1
634
+ color_class_index %= color_classes.size
635
+ tr_(:class => color_classes[color_class_index]) {
636
+ td_ {
637
+ case f.name
638
+ when "TOTAL":
639
+ t_ { "TOTAL" }
640
+ else
641
+ a_(:href => mangle_filename(f.name)){ t_ { f.name } }
642
+ end
643
+ }
644
+ [f.num_lines, f.num_code_lines].each do |value|
645
+ td_(:class => "value") { tt_{ value } }
646
+ end
647
+ [f.total_coverage, f.code_coverage].each do |value|
648
+ value *= 100
649
+ td_ {
650
+ table_(:cellpadding => 0, :cellspacing => 0, :align => "right") {
651
+ tr_ {
652
+ td_ {
653
+ tt_ { "%3.1f%%" % value }
654
+ x_ "&nbsp;"
655
+ }
656
+ ivalue = value.round
657
+ td_ {
658
+ table_(:class => "percent_graph", :cellpadding => 0,
659
+ :cellspacing => 0, :width => 100) {
660
+ tr_ {
661
+ td_(:class => "covered", :width => ivalue)
662
+ td_(:class => "uncovered", :width => (100-ivalue))
663
+ }
664
+ }
665
+ }
666
+ }
667
+ }
668
+ }
669
+ end
670
+ }
671
+ end
672
+ }
673
+ }
674
+ }
675
+ table_text.pretty
676
+ end
677
+
678
+ class SummaryFileInfo # :nodoc:
679
+ def initialize(obj); @o = obj end
680
+ %w[num_lines num_code_lines code_coverage total_coverage].each do |m|
681
+ define_method(m){ @o.send(m) }
682
+ end
683
+ def name; "TOTAL" end
684
+ end
685
+
686
+ def create_index(destname)
687
+ files = [SummaryFileInfo.new(self)] + each_file_pair_sorted.map{|k,v| v}
688
+ title = default_title
689
+ output = xhtml_ { html_ {
690
+ head_ {
691
+ title_{ title }
692
+ style_(:type => "text/css") { t_{ "body { background-color: #{default_color}; }" } }
693
+ style_(:type => "text/css") { CSS_PROLOG }
694
+ script_(:type => "text/javascript") { h_{ JAVASCRIPT_PROLOG } }
695
+ }
696
+ body_ {
697
+ h3_{
698
+ t_{ title }
699
+ }
700
+ p_ {
701
+ t_{ "Generated on #{Time.new.to_s} with " }
702
+ a_(:href => Rcov::UPSTREAM_URL){ "rcov #{Rcov::VERSION}" }
703
+ }
704
+ p_ { "Threshold: #{@output_threshold}%" } if @output_threshold != 101
705
+ hr_
706
+ x_{ format_overview(*files) }
707
+ hr_
708
+ x_{ blurb }
709
+ p_ {
710
+ a_(:href => "http://validator.w3.org/check/referer") {
711
+ img_(:src => "http://www.w3.org/Icons/valid-xhtml11",
712
+ :alt => "Valid XHTML 1.1!", :height => 31, :width => 88)
713
+ }
714
+ a_(:href => "http://jigsaw.w3.org/css-validator/check/referer") {
715
+ img_(:style => "border:0;width:88px;height:31px",
716
+ :src => "http://jigsaw.w3.org/css-validator/images/vcss",
717
+ :alt => "Valid CSS!")
718
+ }
719
+ }
720
+ }
721
+ } }
722
+ lines = output.pretty.to_a
723
+ lines.unshift lines.pop if /DOCTYPE/ =~ lines[-1]
724
+ File.open(destname, "w") do |f|
725
+ f.puts lines
726
+ end
727
+ end
728
+
729
+ def format_lines(file)
730
+ result = ""
731
+ last = nil
732
+ end_of_span = ""
733
+ format_line = "%#{file.num_lines.to_s.size}d"
734
+ file.num_lines.times do |i|
735
+ line = file.lines[i].chomp
736
+ marked = file.coverage[i]
737
+ count = file.counts[i]
738
+ spanclass = span_class(file, marked, count)
739
+ if spanclass != last
740
+ result += end_of_span
741
+ case spanclass
742
+ when nil
743
+ end_of_span = ""
744
+ else
745
+ result += %[<span class="#{spanclass}">]
746
+ end_of_span = "</span>"
747
+ end
748
+ end
749
+ result += %[<a name="line#{i+1}" />] + (format_line % (i+1)) +
750
+ " " + create_cross_refs(file.name, i+1, CGI.escapeHTML(line)) + "\n"
751
+ last = spanclass
752
+ end
753
+ result += end_of_span
754
+ "<pre>#{result}</pre>"
755
+ end
756
+
757
+ class XRefHelper < Struct.new(:file, :line, :klass, :mid, :count) # :nodoc:
758
+ end
759
+
760
+ def create_cross_refs(filename, lineno, linetext)
761
+ return linetext unless @callsite_analyzer && @do_callsites
762
+ ret = ""
763
+ ref_blocks = []
764
+ if @do_cross_references and
765
+ (rev_xref = reverse_cross_references_for(filename, lineno))
766
+ refs = rev_xref.map do |classname, methodname, defsite, count|
767
+ XRefHelper.new(defsite.file, defsite.line, classname, methodname, count)
768
+ end.sort_by{|r| r.count}.reverse
769
+ format_call_ref = lambda do |ref|
770
+ if ref.file
771
+ where = "at #{normalize_filename(ref.file)}:#{ref.line}"
772
+ else
773
+ where = "(C extension/core)"
774
+ end
775
+ CGI.escapeHTML("%7d %s" %
776
+ [ref.count, "#{ref.klass}##{ref.mid} " + where])
777
+ end
778
+ ref_blocks << [refs, "Calls", format_call_ref]
779
+ end
780
+ if @do_callsites and
781
+ (refs = cross_references_for(filename, lineno))
782
+ refs = refs.sort_by{|k,count| count}.map do |ref, count|
783
+ XRefHelper.new(ref.file, ref.line, ref.calling_class, ref.calling_method, count)
784
+ end.reverse
785
+ format_called_ref = lambda do |ref|
786
+ r = "%7d %s" % [ref.count,
787
+ "#{normalize_filename(ref.file||'C code')}:#{ref.line} " +
788
+ "in '#{ref.klass}##{ref.mid}'"]
789
+ CGI.escapeHTML(r)
790
+ end
791
+ ref_blocks << [refs, "Called by", format_called_ref]
792
+ end
793
+
794
+ create_cross_reference_block(linetext, ref_blocks)
795
+ end
796
+
797
+ def create_cross_reference_block(linetext, ref_blocks)
798
+ return linetext if ref_blocks.empty?
799
+ ret = ""
800
+ @cross_ref_idx ||= 0
801
+ @known_files ||= sorted_file_pairs.map{|fname, finfo| normalize_filename(fname)}
802
+ ret << %[<a class="crossref-toggle" href="#" onclick="toggleCode('XREF-#{@cross_ref_idx+=1}'); return false;">#{linetext}</a>]
803
+ ret << %[<span class="cross-ref" id="XREF-#{@cross_ref_idx}">]
804
+ ret << "\n"
805
+ ref_blocks.each do |refs, toplabel, label_proc|
806
+ unless !toplabel || toplabel.empty?
807
+ ret << %!<span class="cross-ref-title">#{toplabel}</span>\n!
808
+ end
809
+ refs.each do |dst|
810
+ dstfile = normalize_filename(dst.file) if dst.file
811
+ dstline = dst.line
812
+ label = label_proc.call(dst)
813
+ if dst.file && @known_files.include?(dstfile)
814
+ ret << %[<a href="#{mangle_filename(dstfile)}#line#{dstline}">#{label}</a>]
815
+ else
816
+ ret << label
817
+ end
818
+ ret << "\n"
819
+ end
820
+ end
821
+ ret << "</span>"
822
+ end
823
+
824
+ def span_class(sourceinfo, marked, count)
825
+ @span_class_index ^= 1
826
+ case marked
827
+ when true
828
+ "marked#{@span_class_index}"
829
+ when :inferred
830
+ "inferred#{@span_class_index}"
831
+ else
832
+ "uncovered#{@span_class_index}"
833
+ end
834
+ end
835
+
836
+ def create_file(destfile, fileinfo)
837
+ #$stderr.puts "Generating #{destfile.inspect}"
838
+ body = format_overview(fileinfo) + format_lines(fileinfo)
839
+ title = fileinfo.name + " - #{default_title}"
840
+ do_ctable = output_color_table?
841
+ output = xhtml_ { html_ {
842
+ head_ {
843
+ title_{ title }
844
+ style_(:type => "text/css") { t_{ "body { background-color: #{default_color}; }" } }
845
+ style_(:type => "text/css") { CSS_PROLOG }
846
+ script_(:type => "text/javascript") { h_ { JAVASCRIPT_PROLOG } }
847
+ style_(:type => "text/css") { h_ { colorscale } }
848
+ }
849
+ body_ {
850
+ h3_{ t_{ default_title } }
851
+ p_ {
852
+ t_{ "Generated on #{Time.new.to_s} with " }
853
+ a_(:href => Rcov::UPSTREAM_URL){ "rcov #{Rcov::VERSION}" }
854
+ }
855
+ hr_
856
+ if do_ctable
857
+ # this kludge needed to ensure .pretty doesn't mangle it
858
+ x_ { <<EOS
859
+ <pre><span class='marked0'>Code reported as executed by Ruby looks like this...
860
+ </span><span class='marked1'>and this: this line is also marked as covered.
861
+ </span><span class='inferred0'>Lines considered as run by rcov, but not reported by Ruby, look like this,
862
+ </span><span class='inferred1'>and this: these lines were inferred by rcov (using simple heuristics).
863
+ </span><span class='uncovered0'>Finally, here&apos;s a line marked as not executed.
864
+ </span></pre>
865
+ EOS
866
+ }
867
+ end
868
+ x_{ body }
869
+ hr_
870
+ x_ { blurb }
871
+ p_ {
872
+ a_(:href => "http://validator.w3.org/check/referer") {
873
+ img_(:src => "http://www.w3.org/Icons/valid-xhtml10",
874
+ :alt => "Valid XHTML 1.0!", :height => 31, :width => 88)
875
+ }
876
+ a_(:href => "http://jigsaw.w3.org/css-validator/check/referer") {
877
+ img_(:style => "border:0;width:88px;height:31px",
878
+ :src => "http://jigsaw.w3.org/css-validator/images/vcss",
879
+ :alt => "Valid CSS!")
880
+ }
881
+ }
882
+ }
883
+ } }
884
+ # .pretty needed to make sure DOCTYPE is in a separate line
885
+ lines = output.pretty.to_a
886
+ lines.unshift lines.pop if /DOCTYPE/ =~ lines[-1]
887
+ File.open(destfile, "w") do |f|
888
+ f.puts lines
889
+ end
890
+ end
891
+
892
+ def colorscale
893
+ colorscalebase =<<EOF
894
+ span.run%d {
895
+ background-color: rgb(%d, %d, %d);
896
+ display: block;
897
+ }
898
+ EOF
899
+ cscale = ""
900
+ 101.times do |i|
901
+ if @color
902
+ r, g, b = hsv2rgb(220-(2.2*i).to_i, 0.3, 1)
903
+ r = (r * 255).to_i
904
+ g = (g * 255).to_i
905
+ b = (b * 255).to_i
906
+ else
907
+ r = g = b = 255 - i
908
+ end
909
+ cscale << colorscalebase % [i, r, g, b]
910
+ end
911
+ cscale
912
+ end
913
+
914
+ # thanks to kig @ #ruby-lang for this one
915
+ def hsv2rgb(h,s,v)
916
+ return [v,v,v] if s == 0
917
+ h = h/60.0
918
+ i = h.floor
919
+ f = h-i
920
+ p = v * (1-s)
921
+ q = v * (1-s*f)
922
+ t = v * (1-s*(1-f))
923
+ case i
924
+ when 0
925
+ r = v
926
+ g = t
927
+ b = p
928
+ when 1
929
+ r = q
930
+ g = v
931
+ b = p
932
+ when 2
933
+ r = p
934
+ g = v
935
+ b = t
936
+ when 3
937
+ r = p
938
+ g = q
939
+ b = v
940
+ when 4
941
+ r = t
942
+ g = p
943
+ b = v
944
+ when 5
945
+ r = v
946
+ g = p
947
+ b = q
948
+ end
949
+ [r,g,b]
950
+ end
951
+ end
952
+
953
+ class HTMLProfiling < HTMLCoverage # :nodoc:
954
+
955
+ DEFAULT_OPTS = {:destdir => "profiling"}
956
+ def initialize(opts = {})
957
+ options = DEFAULT_OPTS.clone.update(opts)
958
+ super(options)
959
+ @max_cache = {}
960
+ @median_cache = {}
961
+ end
962
+
963
+ def default_title
964
+ "Bogo-profile information"
965
+ end
966
+
967
+ def default_color
968
+ if @color
969
+ "rgb(179,205,255)"
970
+ else
971
+ "rgb(255, 255, 255)"
972
+ end
973
+ end
974
+
975
+ def output_color_table?
976
+ false
977
+ end
978
+
979
+ def span_class(sourceinfo, marked, count)
980
+ full_scale_range = @fsr # dB
981
+ nz_count = sourceinfo.counts.select{|x| x && x != 0}
982
+ nz_count << 1 # avoid div by 0
983
+ max = @max_cache[sourceinfo] ||= nz_count.max
984
+ #avg = @median_cache[sourceinfo] ||= 1.0 *
985
+ # nz_count.inject{|a,b| a+b} / nz_count.size
986
+ median = @median_cache[sourceinfo] ||= 1.0 * nz_count.sort[nz_count.size/2]
987
+ max ||= 2
988
+ max = 2 if max == 1
989
+ if marked == true
990
+ count = 1 if !count || count == 0
991
+ idx = 50 + 1.0 * (500/full_scale_range) * Math.log(count/median) /
992
+ Math.log(10)
993
+ idx = idx.to_i
994
+ idx = 0 if idx < 0
995
+ idx = 100 if idx > 100
996
+ "run#{idx}"
997
+ else
998
+ nil
999
+ end
1000
+ end
1001
+ end
1002
+
1003
+ end # Rcov
1004
+
1005
+ # vi: set sw=4: