jruby-rcov 0.8.2.1-java

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.
@@ -0,0 +1,14 @@
1
+ require 'rcov/formatters/base_formatter'
2
+ require 'rcov/formatters/text_summary'
3
+ require 'rcov/formatters/text_report'
4
+ require 'rcov/formatters/text_coverage_diff'
5
+ require 'rcov/formatters/full_text_report'
6
+ require 'rcov/formatters/html_coverage'
7
+
8
+ module Rcov
9
+
10
+ module Formatters
11
+
12
+ end
13
+
14
+ end
@@ -0,0 +1,199 @@
1
+ require 'rcov/xx'
2
+ require "erb"
3
+
4
+ class Hash
5
+ def to_binding(object = Object.new)
6
+ object.instance_eval("def binding_for(#{keys.join(",")}) binding end")
7
+ object.binding_for(*values)
8
+ end
9
+ end
10
+
11
+ class Document
12
+ def initialize(template)
13
+ @template = ERB.new(template)
14
+ end
15
+
16
+ def interpolate(replacements = {})
17
+ @template.result(replacements.to_binding)
18
+ end
19
+ end
20
+
21
+ module XX
22
+ module XMLish
23
+ include Markup
24
+
25
+ def xmlish_ *a, &b
26
+ xx_which(XMLish){ xx_with_doc_in_effect(*a, &b)}
27
+ end
28
+ end
29
+ end
30
+
31
+ module Rcov
32
+
33
+ class BaseFormatter # :nodoc:
34
+ require 'pathname'
35
+
36
+ ignore_files = [/\A#{Regexp.escape(Pathname.new(::Config::CONFIG["libdir"]).cleanpath.to_s)}/,
37
+ /\btc_[^.]*.rb/, /_test\.rb\z/, /\btest\//, /\bvendor\//, /\A#{Regexp.escape(__FILE__)}\z/]
38
+
39
+ DEFAULT_OPTS = {:ignore => ignore_files, :sort => :name, :sort_reverse => false,
40
+ :output_threshold => 101, :dont_ignore => [], :callsite_analyzer => nil, :comments_run_by_default => false}
41
+
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 then lambda{|fname, finfo| finfo.num_code_lines}
49
+ when :coverage then 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
+ @comments_run_by_default = options[:comments_run_by_default]
56
+ @callsite_index = nil
57
+
58
+ @mangle_filename = Hash.new{|h,base|
59
+ h[base] = Pathname.new(base).cleanpath.to_s.gsub(%r{^\w:[/\\]}, "").gsub(/\./, "_").gsub(/[\\\/]/, "-") + ".html"
60
+ }
61
+ end
62
+
63
+ def add_file(filename, lines, coverage, counts)
64
+ old_filename = filename
65
+ filename = normalize_filename(filename)
66
+ SCRIPT_LINES__[filename] = SCRIPT_LINES__[old_filename]
67
+ if @ignore_files.any?{|x| x === filename} &&
68
+ !@dont_ignore_files.any?{|x| x === filename}
69
+ return nil
70
+ end
71
+ if @files[filename]
72
+ @files[filename].merge(lines, coverage, counts)
73
+ else
74
+ @files[filename] = FileStatistics.new(filename, lines, counts,
75
+ @comments_run_by_default)
76
+ end
77
+ end
78
+
79
+ def normalize_filename(filename)
80
+ File.expand_path(filename).gsub(/^#{Regexp.escape(Dir.getwd)}\//, '')
81
+ end
82
+
83
+ def mangle_filename(base)
84
+ @mangle_filename[base]
85
+ end
86
+
87
+ def each_file_pair_sorted(&b)
88
+ return sorted_file_pairs unless block_given?
89
+ sorted_file_pairs.each(&b)
90
+ end
91
+
92
+ def sorted_file_pairs
93
+ pairs = @files.sort_by do |fname, finfo|
94
+ @sort_criterium.call(fname, finfo)
95
+ end.select{|_, finfo| 100 * finfo.code_coverage < @output_threshold}
96
+ @sort_reverse ? pairs.reverse : pairs
97
+ end
98
+
99
+ def total_coverage
100
+ lines = 0
101
+ total = 0.0
102
+ @files.each do |k,f|
103
+ total += f.num_lines * f.total_coverage
104
+ lines += f.num_lines
105
+ end
106
+ return 0 if lines == 0
107
+ total / lines
108
+ end
109
+
110
+ def code_coverage
111
+ lines = 0
112
+ total = 0.0
113
+ @files.each do |k,f|
114
+ total += f.num_code_lines * f.code_coverage
115
+ lines += f.num_code_lines
116
+ end
117
+ return 0 if lines == 0
118
+ total / lines
119
+ end
120
+
121
+ def num_code_lines
122
+ lines = 0
123
+ @files.each{|k, f| lines += f.num_code_lines }
124
+ lines
125
+ end
126
+
127
+ def num_lines
128
+ lines = 0
129
+ @files.each{|k, f| lines += f.num_lines }
130
+ lines
131
+ end
132
+
133
+ private
134
+ def cross_references_for(filename, lineno)
135
+ return nil unless @callsite_analyzer
136
+ @callsite_index ||= build_callsite_index
137
+ @callsite_index[normalize_filename(filename)][lineno]
138
+ end
139
+
140
+ def reverse_cross_references_for(filename, lineno)
141
+ return nil unless @callsite_analyzer
142
+ @callsite_reverse_index ||= build_reverse_callsite_index
143
+ @callsite_reverse_index[normalize_filename(filename)][lineno]
144
+ end
145
+
146
+ def build_callsite_index
147
+ index = Hash.new{|h,k| h[k] = {}}
148
+ @callsite_analyzer.analyzed_classes.each do |classname|
149
+ @callsite_analyzer.analyzed_methods(classname).each do |methname|
150
+ defsite = @callsite_analyzer.defsite(classname, methname)
151
+ index[normalize_filename(defsite.file)][defsite.line] =
152
+ @callsite_analyzer.callsites(classname, methname)
153
+ end
154
+ end
155
+ index
156
+ end
157
+
158
+ def build_reverse_callsite_index
159
+ index = Hash.new{|h,k| h[k] = {}}
160
+ @callsite_analyzer.analyzed_classes.each do |classname|
161
+ @callsite_analyzer.analyzed_methods(classname).each do |methname|
162
+ callsites = @callsite_analyzer.callsites(classname, methname)
163
+ defsite = @callsite_analyzer.defsite(classname, methname)
164
+ callsites.each_pair do |callsite, count|
165
+ next unless callsite.file
166
+ fname = normalize_filename(callsite.file)
167
+ (index[fname][callsite.line] ||= []) << [classname, methname, defsite, count]
168
+ end
169
+ end
170
+ end
171
+ index
172
+ end
173
+
174
+ class XRefHelper < Struct.new(:file, :line, :klass, :mid, :count) # :nodoc:
175
+ end
176
+
177
+ def _get_defsites(ref_blocks, filename, lineno, linetext, label, &format_call_ref)
178
+ if @do_cross_references and
179
+ (rev_xref = reverse_cross_references_for(filename, lineno))
180
+ refs = rev_xref.map do |classname, methodname, defsite, count|
181
+ XRefHelper.new(defsite.file, defsite.line, classname, methodname, count)
182
+ end.sort_by{|r| r.count}.reverse
183
+ ref_blocks << [refs, label, format_call_ref]
184
+ end
185
+ end
186
+
187
+ def _get_callsites(ref_blocks, filename, lineno, linetext, label, &format_called_ref)
188
+ if @do_callsites and
189
+ (refs = cross_references_for(filename, lineno))
190
+ refs = refs.sort_by{|k,count| count}.map do |ref, count|
191
+ XRefHelper.new(ref.file, ref.line, ref.calling_class, ref.calling_method, count)
192
+ end.reverse
193
+ ref_blocks << [refs, label, format_called_ref]
194
+ end
195
+ end
196
+
197
+ end
198
+
199
+ end
@@ -0,0 +1,55 @@
1
+ module Rcov
2
+
3
+ class FullTextReport < BaseFormatter # :nodoc:
4
+ DEFAULT_OPTS = {:textmode => :coverage}
5
+
6
+ def initialize(opts = {})
7
+ options = DEFAULT_OPTS.clone.update(opts)
8
+ @textmode = options[:textmode]
9
+ @color = options[:color]
10
+ super(options)
11
+ end
12
+
13
+ def execute
14
+ each_file_pair_sorted do |filename, fileinfo|
15
+ puts "=" * 80
16
+ puts filename
17
+ puts "=" * 80
18
+ lines = SCRIPT_LINES__[filename]
19
+
20
+ unless lines
21
+ # try to get the source code from the global code coverage
22
+ # analyzer
23
+ re = /#{Regexp.escape(filename)}\z/
24
+ if $rcov_code_coverage_analyzer and
25
+ (data = $rcov_code_coverage_analyzer.data_matching(re))
26
+ lines = data[0]
27
+ end
28
+ end
29
+
30
+ (lines || []).each_with_index do |line, i|
31
+
32
+ case @textmode
33
+ when :counts
34
+ puts "%-70s| %6d" % [line.chomp[0,70], fileinfo.counts[i]]
35
+ when :gcc
36
+ puts "%s:%d:%s" % [filename, i+1, line.chomp] unless fileinfo.coverage[i]
37
+ when :coverage
38
+ if @color
39
+ prefix = fileinfo.coverage[i] ? "\e[32;40m" : "\e[31;40m"
40
+ puts "#{prefix}%s\e[37;40m" % line.chomp
41
+ else
42
+ prefix = fileinfo.coverage[i] ? " " : "!! "
43
+ puts "#{prefix}#{line}"
44
+ end
45
+ end
46
+
47
+ end
48
+
49
+ end
50
+
51
+ end
52
+
53
+ end
54
+
55
+ end
@@ -0,0 +1,735 @@
1
+ module Rcov
2
+
3
+ class HTMLCoverage < BaseFormatter # :nodoc:
4
+ include XX::XHTML
5
+ include XX::XMLish
6
+ require 'fileutils'
7
+ JAVASCRIPT_PROLOG = <<-EOS
8
+
9
+ // <![CDATA[
10
+ function toggleCode( id ) {
11
+ if ( document.getElementById )
12
+ elem = document.getElementById( id );
13
+ else if ( document.all )
14
+ elem = eval( "document.all." + id );
15
+ else
16
+ return false;
17
+
18
+ elemStyle = elem.style;
19
+
20
+ if ( elemStyle.display != "block" ) {
21
+ elemStyle.display = "block"
22
+ } else {
23
+ elemStyle.display = "none"
24
+ }
25
+
26
+ return true;
27
+ }
28
+
29
+ // Make cross-references hidden by default
30
+ document.writeln( "<style type=\\"text/css\\">span.cross-ref { display: none }</style>" )
31
+ // ]]>
32
+ EOS
33
+
34
+ CSS_PROLOG = <<-EOS
35
+ span.cross-ref-title {
36
+ font-size: 140%;
37
+ }
38
+ span.cross-ref a {
39
+ text-decoration: none;
40
+ }
41
+ span.cross-ref {
42
+ background-color:#f3f7fa;
43
+ border: 1px dashed #333;
44
+ margin: 1em;
45
+ padding: 0.5em;
46
+ overflow: hidden;
47
+ }
48
+ a.crossref-toggle {
49
+ text-decoration: none;
50
+ }
51
+ span.marked0 {
52
+ background-color: rgb(185, 210, 200);
53
+ display: block;
54
+ }
55
+ span.marked1 {
56
+ background-color: rgb(190, 215, 205);
57
+ display: block;
58
+ }
59
+ span.inferred0 {
60
+ background-color: rgb(255, 255, 240);
61
+ display: block;
62
+ }
63
+ span.inferred1 {
64
+ background-color: rgb(255, 255, 240);
65
+ display: block;
66
+ }
67
+ span.uncovered0 {
68
+ background-color: rgb(225, 110, 110);
69
+ display: block;
70
+ }
71
+ span.uncovered1 {
72
+ background-color: rgb(235, 120, 120);
73
+ display: block;
74
+ }
75
+ span.overview {
76
+ border-bottom: 8px solid black;
77
+ }
78
+ div.overview {
79
+ border-bottom: 8px solid black;
80
+ }
81
+ body {
82
+ font-family: verdana, arial, helvetica;
83
+ }
84
+
85
+ div.footer {
86
+ font-size: 68%;
87
+ margin-top: 1.5em;
88
+ }
89
+
90
+ h1, h2, h3, h4, h5, h6 {
91
+ margin-bottom: 0.5em;
92
+ }
93
+
94
+ h5 {
95
+ margin-top: 0.5em;
96
+ }
97
+
98
+ .hidden {
99
+ display: none;
100
+ }
101
+
102
+ div.separator {
103
+ height: 10px;
104
+ }
105
+ /* Commented out for better readability, esp. on IE */
106
+ /*
107
+ table tr td, table tr th {
108
+ font-size: 68%;
109
+ }
110
+
111
+ td.value table tr td {
112
+ font-size: 11px;
113
+ }
114
+ */
115
+
116
+ table.percent_graph {
117
+ height: 12px;
118
+ border: #808080 1px solid;
119
+ empty-cells: show;
120
+ }
121
+
122
+ table.percent_graph td.covered {
123
+ height: 10px;
124
+ background: #00f000;
125
+ }
126
+
127
+ table.percent_graph td.uncovered {
128
+ height: 10px;
129
+ background: #e00000;
130
+ }
131
+
132
+ table.percent_graph td.NA {
133
+ height: 10px;
134
+ background: #eaeaea;
135
+ }
136
+
137
+ table.report {
138
+ border-collapse: collapse;
139
+ width: 100%;
140
+ }
141
+
142
+ table.report td.heading {
143
+ background: #dcecff;
144
+ border: #d0d0d0 1px solid;
145
+ font-weight: bold;
146
+ text-align: center;
147
+ }
148
+
149
+ table.report td.heading:hover {
150
+ background: #c0ffc0;
151
+ }
152
+
153
+ table.report td.text {
154
+ border: #d0d0d0 1px solid;
155
+ }
156
+
157
+ table.report td.value,
158
+ table.report td.lines_total,
159
+ table.report td.lines_code {
160
+ text-align: right;
161
+ border: #d0d0d0 1px solid;
162
+ }
163
+ table.report tr.light {
164
+ background-color: rgb(240, 240, 245);
165
+ }
166
+ table.report tr.dark {
167
+ background-color: rgb(230, 230, 235);
168
+ }
169
+ EOS
170
+
171
+ DEFAULT_OPTS = {:color => false, :fsr => 30, :destdir => "coverage",
172
+ :callsites => false, :cross_references => false,
173
+ :validator_links => true, :charset => nil
174
+ }
175
+
176
+ def initialize(opts = {})
177
+ options = DEFAULT_OPTS.clone.update(opts)
178
+ super(options)
179
+ @dest = options[:destdir]
180
+ @color = options[:color]
181
+ @fsr = options[:fsr]
182
+ @do_callsites = options[:callsites]
183
+ @do_cross_references = options[:cross_references]
184
+ @span_class_index = 0
185
+ @show_validator_links = options[:validator_links]
186
+ @charset = options[:charset]
187
+ end
188
+
189
+ def execute
190
+ return if @files.empty?
191
+ FileUtils.mkdir_p @dest
192
+ create_index(File.join(@dest, "index.html"))
193
+
194
+ each_file_pair_sorted do |filename, fileinfo|
195
+ create_file(File.join(@dest, mangle_filename(filename)), fileinfo)
196
+ end
197
+ end
198
+
199
+ private
200
+
201
+ def blurb
202
+ xmlish_ {
203
+ p_ {
204
+ t_{ "Generated using the " }
205
+ a_(:href => "http://eigenclass.org/hiki.rb?rcov") {
206
+ t_{ "rcov code coverage analysis tool for Ruby" }
207
+ }
208
+ t_{ " version #{Rcov::VERSION}." }
209
+ }
210
+ }.pretty
211
+ end
212
+
213
+ def output_color_table?
214
+ true
215
+ end
216
+
217
+ def default_color
218
+ "rgb(240, 240, 245)"
219
+ end
220
+
221
+ def default_title
222
+ "C0 code coverage information"
223
+ end
224
+
225
+ def format_overview(*file_infos)
226
+ table_text = xmlish_ {
227
+ table_(:class => "report") {
228
+ thead_ {
229
+ tr_ {
230
+ ["Name", "Total lines", "Lines of code", "Total coverage",
231
+ "Code coverage"].each do |heading|
232
+ td_(:class => "heading") { heading }
233
+ end
234
+ }
235
+ }
236
+ tbody_ {
237
+ color_class_index = 1
238
+ color_classes = %w[light dark]
239
+ file_infos.each do |f|
240
+ color_class_index += 1
241
+ color_class_index %= color_classes.size
242
+ tr_(:class => color_classes[color_class_index]) {
243
+ td_ {
244
+ case f.name
245
+ when "TOTAL" then
246
+ t_ { "TOTAL" }
247
+ else
248
+ a_(:href => mangle_filename(f.name)){ t_ { f.name } }
249
+ end
250
+ }
251
+ [[f.num_lines, "lines_total"],
252
+ [f.num_code_lines, "lines_code"]].each do |value, css_class|
253
+ td_(:class => css_class) { tt_{ value } }
254
+ end
255
+ [[f.total_coverage, "coverage_total"],
256
+ [f.code_coverage, "coverage_code"]].each do |value, css_class|
257
+ value *= 100
258
+ td_ {
259
+ table_(:cellpadding => "0", :cellspacing => "0", :align => "right") {
260
+ tr_ {
261
+ td_ {
262
+ tt_(:class => css_class) { "%3.1f%%" % value }
263
+ x_ "&nbsp;"
264
+ }
265
+ ivalue = value.round
266
+ td_ {
267
+ table_(:class => "percent_graph", :cellpadding => "0",
268
+ :cellspacing => "0", :width => "100") {
269
+ tr_ {
270
+ td_(:class => "covered", :width => ivalue.to_s)
271
+ td_(:class => "uncovered", :width => (100-ivalue).to_s)
272
+ }
273
+ }
274
+ }
275
+ }
276
+ }
277
+ }
278
+ end
279
+ }
280
+ end
281
+ }
282
+ }
283
+ }
284
+ table_text.pretty
285
+ end
286
+
287
+ class SummaryFileInfo # :nodoc:
288
+
289
+ def initialize(obj)
290
+ @o = obj
291
+ end
292
+
293
+ def num_lines
294
+ @o.num_lines
295
+ end
296
+
297
+ def num_code_lines
298
+ @o.num_code_lines
299
+ end
300
+
301
+ def code_coverage
302
+ @o.code_coverage
303
+ end
304
+
305
+ def total_coverage
306
+ @o.total_coverage
307
+ end
308
+
309
+ def name
310
+ "TOTAL"
311
+ end
312
+
313
+ end
314
+
315
+ def create_index(destname)
316
+ files = [SummaryFileInfo.new(self)] + each_file_pair_sorted.map{|k,v| v}
317
+ title = default_title
318
+ output = xhtml_ { html_ {
319
+ head_ {
320
+ if @charset
321
+ meta_("http-equiv".to_sym => "Content-Type",
322
+ :content => "text/html;charset=#{@charset}")
323
+ end
324
+ title_{ title }
325
+ style_(:type => "text/css") { t_{ "body { background-color: #{default_color}; }" } }
326
+ style_(:type => "text/css") { CSS_PROLOG }
327
+ script_(:type => "text/javascript") { h_{ JAVASCRIPT_PROLOG } }
328
+ }
329
+ body_ {
330
+ h3_{
331
+ t_{ title }
332
+ }
333
+ p_ {
334
+ t_{ "Generated on #{Time.new.to_s} with " }
335
+ a_(:href => Rcov::UPSTREAM_URL){ "rcov #{Rcov::VERSION}" }
336
+ }
337
+ p_ { "Threshold: #{@output_threshold}%" } if @output_threshold != 101
338
+ hr_
339
+ x_{ format_overview(*files) }
340
+ hr_
341
+ x_{ blurb }
342
+
343
+ if @show_validator_links
344
+ p_ {
345
+ a_(:href => "http://validator.w3.org/check/referer") {
346
+ img_(:src => "http://www.w3.org/Icons/valid-xhtml11",
347
+ :alt => "Valid XHTML 1.1!", :height => "31", :width => "88")
348
+ }
349
+ a_(:href => "http://jigsaw.w3.org/css-validator/check/referer") {
350
+ img_(:style => "border:0;width:88px;height:31px",
351
+ :src => "http://jigsaw.w3.org/css-validator/images/vcss",
352
+ :alt => "Valid CSS!")
353
+ }
354
+ }
355
+ end
356
+ }
357
+ } }
358
+ lines = output.pretty.to_a
359
+ lines.unshift lines.pop if /DOCTYPE/ =~ lines[-1]
360
+ File.open(destname, "w") do |f|
361
+ f.puts lines
362
+ end
363
+ end
364
+
365
+ def format_lines(file)
366
+ result = ""
367
+ last = nil
368
+ end_of_span = ""
369
+ format_line = "%#{file.num_lines.to_s.size}d"
370
+ file.num_lines.times do |i|
371
+ line = file.lines[i].chomp
372
+ marked = file.coverage[i]
373
+ count = file.counts[i]
374
+ spanclass = span_class(file, marked, count)
375
+ if spanclass != last
376
+ result += end_of_span
377
+ case spanclass
378
+ when nil
379
+ end_of_span = ""
380
+ else
381
+ result += %[<span class="#{spanclass}">]
382
+ end_of_span = "</span>"
383
+ end
384
+ end
385
+ result += %[<a name="line#{i+1}"></a>] + (format_line % (i+1)) +
386
+ " " + create_cross_refs(file.name, i+1, CGI.escapeHTML(line)) + "\n"
387
+ last = spanclass
388
+ end
389
+ result += end_of_span
390
+ "<pre>#{result}</pre>"
391
+ end
392
+
393
+ def create_cross_refs(filename, lineno, linetext)
394
+ return linetext unless @callsite_analyzer && @do_callsites
395
+ ref_blocks = []
396
+ _get_defsites(ref_blocks, filename, lineno, "Calls", linetext) do |ref|
397
+ if ref.file
398
+ where = "at #{normalize_filename(ref.file)}:#{ref.line}"
399
+ else
400
+ where = "(C extension/core)"
401
+ end
402
+ CGI.escapeHTML("%7d %s" %
403
+ [ref.count, "#{ref.klass}##{ref.mid} " + where])
404
+ end
405
+ _get_callsites(ref_blocks, filename, lineno, "Called by", linetext) do |ref|
406
+ r = "%7d %s" % [ref.count,
407
+ "#{normalize_filename(ref.file||'C code')}:#{ref.line} " +
408
+ "in '#{ref.klass}##{ref.mid}'"]
409
+ CGI.escapeHTML(r)
410
+ end
411
+
412
+ create_cross_reference_block(linetext, ref_blocks)
413
+ end
414
+
415
+ def create_cross_reference_block(linetext, ref_blocks)
416
+ return linetext if ref_blocks.empty?
417
+ ret = ""
418
+ @cross_ref_idx ||= 0
419
+ @known_files ||= sorted_file_pairs.map{|fname, finfo| normalize_filename(fname)}
420
+ ret << %[<a class="crossref-toggle" href="#" onclick="toggleCode('XREF-#{@cross_ref_idx+=1}'); return false;">#{linetext}</a>]
421
+ ret << %[<span class="cross-ref" id="XREF-#{@cross_ref_idx}">]
422
+ ret << "\n"
423
+ ref_blocks.each do |refs, toplabel, label_proc|
424
+ unless !toplabel || toplabel.empty?
425
+ ret << %!<span class="cross-ref-title">#{toplabel}</span>\n!
426
+ end
427
+ refs.each do |dst|
428
+ dstfile = normalize_filename(dst.file) if dst.file
429
+ dstline = dst.line
430
+ label = label_proc.call(dst)
431
+ if dst.file && @known_files.include?(dstfile)
432
+ ret << %[<a href="#{mangle_filename(dstfile)}#line#{dstline}">#{label}</a>]
433
+ else
434
+ ret << label
435
+ end
436
+ ret << "\n"
437
+ end
438
+ end
439
+ ret << "</span>"
440
+ end
441
+
442
+ def span_class(sourceinfo, marked, count)
443
+ @span_class_index ^= 1
444
+ case marked
445
+ when true
446
+ "marked#{@span_class_index}"
447
+ when :inferred
448
+ "inferred#{@span_class_index}"
449
+ else
450
+ "uncovered#{@span_class_index}"
451
+ end
452
+ end
453
+
454
+ def create_file(destfile, fileinfo)
455
+ #$stderr.puts "Generating #{destfile.inspect}"
456
+ body = format_overview(fileinfo) + format_lines(fileinfo)
457
+ title = fileinfo.name + " - #{default_title}"
458
+ do_ctable = output_color_table?
459
+ output = xhtml_ { html_ {
460
+ head_ {
461
+ if @charset
462
+ meta_("http-equiv".to_sym => "Content-Type",
463
+ :content => "text/html;charset=#{@charset}")
464
+ end
465
+ title_{ title }
466
+ style_(:type => "text/css") { t_{ "body { background-color: #{default_color}; }" } }
467
+ style_(:type => "text/css") { CSS_PROLOG }
468
+ script_(:type => "text/javascript") { h_ { JAVASCRIPT_PROLOG } }
469
+ style_(:type => "text/css") { h_ { colorscale } }
470
+ }
471
+ body_ {
472
+ h3_{ t_{ default_title } }
473
+ p_ {
474
+ t_{ "Generated on #{Time.new.to_s} with " }
475
+ a_(:href => Rcov::UPSTREAM_URL){ "rcov #{Rcov::VERSION}" }
476
+ }
477
+ hr_
478
+ if do_ctable
479
+ # this kludge needed to ensure .pretty doesn't mangle it
480
+ x_ { <<EOS
481
+ <pre><span class='marked0'>Code reported as executed by Ruby looks like this...
482
+ </span><span class='marked1'>and this: this line is also marked as covered.
483
+ </span><span class='inferred0'>Lines considered as run by rcov, but not reported by Ruby, look like this,
484
+ </span><span class='inferred1'>and this: these lines were inferred by rcov (using simple heuristics).
485
+ </span><span class='uncovered0'>Finally, here&apos;s a line marked as not executed.
486
+ </span></pre>
487
+ EOS
488
+ }
489
+ end
490
+ x_{ body }
491
+ hr_
492
+ x_ { blurb }
493
+
494
+ if @show_validator_links
495
+ p_ {
496
+ a_(:href => "http://validator.w3.org/check/referer") {
497
+ img_(:src => "http://www.w3.org/Icons/valid-xhtml10",
498
+ :alt => "Valid XHTML 1.0!", :height => "31", :width => "88")
499
+ }
500
+ a_(:href => "http://jigsaw.w3.org/css-validator/check/referer") {
501
+ img_(:style => "border:0;width:88px;height:31px",
502
+ :src => "http://jigsaw.w3.org/css-validator/images/vcss",
503
+ :alt => "Valid CSS!")
504
+ }
505
+ }
506
+ end
507
+ }
508
+ } }
509
+ # .pretty needed to make sure DOCTYPE is in a separate line
510
+ lines = output.pretty.to_a
511
+ lines.unshift lines.pop if /DOCTYPE/ =~ lines[-1]
512
+ File.open(destfile, "w") do |f|
513
+ f.puts lines
514
+ end
515
+ end
516
+
517
+ def colorscale
518
+ colorscalebase =<<EOF
519
+ span.run%d {
520
+ background-color: rgb(%d, %d, %d);
521
+ display: block;
522
+ }
523
+ EOF
524
+ cscale = ""
525
+ 101.times do |i|
526
+ if @color
527
+ r, g, b = hsv2rgb(220-(2.2*i).to_i, 0.3, 1)
528
+ r = (r * 255).to_i
529
+ g = (g * 255).to_i
530
+ b = (b * 255).to_i
531
+ else
532
+ r = g = b = 255 - i
533
+ end
534
+ cscale << colorscalebase % [i, r, g, b]
535
+ end
536
+ cscale
537
+ end
538
+
539
+ # thanks to kig @ #ruby-lang for this one
540
+ def hsv2rgb(h,s,v)
541
+ return [v,v,v] if s == 0
542
+ h = h/60.0
543
+ i = h.floor
544
+ f = h-i
545
+ p = v * (1-s)
546
+ q = v * (1-s*f)
547
+ t = v * (1-s*(1-f))
548
+ case i
549
+ when 0
550
+ r = v
551
+ g = t
552
+ b = p
553
+ when 1
554
+ r = q
555
+ g = v
556
+ b = p
557
+ when 2
558
+ r = p
559
+ g = v
560
+ b = t
561
+ when 3
562
+ r = p
563
+ g = q
564
+ b = v
565
+ when 4
566
+ r = t
567
+ g = p
568
+ b = v
569
+ when 5
570
+ r = v
571
+ g = p
572
+ b = q
573
+ end
574
+ [r,g,b]
575
+ end
576
+ end
577
+
578
+ class HTMLProfiling < HTMLCoverage # :nodoc:
579
+
580
+ DEFAULT_OPTS = {:destdir => "profiling"}
581
+ def initialize(opts = {})
582
+ options = DEFAULT_OPTS.clone.update(opts)
583
+ super(options)
584
+ @max_cache = {}
585
+ @median_cache = {}
586
+ end
587
+
588
+ def default_title
589
+ "Bogo-profile information"
590
+ end
591
+
592
+ def default_color
593
+ if @color
594
+ "rgb(179,205,255)"
595
+ else
596
+ "rgb(255, 255, 255)"
597
+ end
598
+ end
599
+
600
+ def output_color_table?
601
+ false
602
+ end
603
+
604
+ def span_class(sourceinfo, marked, count)
605
+ full_scale_range = @fsr # dB
606
+ nz_count = sourceinfo.counts.select{|x| x && x != 0}
607
+ nz_count << 1 # avoid div by 0
608
+ max = @max_cache[sourceinfo] ||= nz_count.max
609
+ #avg = @median_cache[sourceinfo] ||= 1.0 *
610
+ # nz_count.inject{|a,b| a+b} / nz_count.size
611
+ median = @median_cache[sourceinfo] ||= 1.0 * nz_count.sort[nz_count.size/2]
612
+ max ||= 2
613
+ max = 2 if max == 1
614
+ if marked == true
615
+ count = 1 if !count || count == 0
616
+ idx = 50 + 1.0 * (500/full_scale_range) * Math.log(count/median) / Math.log(10)
617
+ idx = idx.to_i
618
+ idx = 0 if idx < 0
619
+ idx = 100 if idx > 100
620
+ "run#{idx}"
621
+ else
622
+ nil
623
+ end
624
+ end
625
+
626
+ end
627
+
628
+ class RubyAnnotation < BaseFormatter # :nodoc:
629
+ DEFAULT_OPTS = { :destdir => "coverage" }
630
+ def initialize(opts = {})
631
+ options = DEFAULT_OPTS.clone.update(opts)
632
+ super(options)
633
+ @dest = options[:destdir]
634
+ @do_callsites = true
635
+ @do_cross_references = true
636
+
637
+ @mangle_filename = Hash.new{|h,base|
638
+ h[base] = Pathname.new(base).cleanpath.to_s.gsub(%r{^\w:[/\\]}, "").gsub(/\./, "_").gsub(/[\\\/]/, "-") + ".rb"
639
+ }
640
+ end
641
+
642
+ def execute
643
+ return if @files.empty?
644
+ FileUtils.mkdir_p @dest
645
+ each_file_pair_sorted do |filename, fileinfo|
646
+ create_file(File.join(@dest, mangle_filename(filename)), fileinfo)
647
+ end
648
+ end
649
+
650
+ private
651
+
652
+ def format_lines(file)
653
+ result = ""
654
+ format_line = "%#{file.num_lines.to_s.size}d"
655
+ file.num_lines.times do |i|
656
+ line = file.lines[i].chomp
657
+ marked = file.coverage[i]
658
+ count = file.counts[i]
659
+ result << create_cross_refs(file.name, i+1, line, marked) + "\n"
660
+ end
661
+ result
662
+ end
663
+
664
+ def create_cross_refs(filename, lineno, linetext, marked)
665
+ return linetext unless @callsite_analyzer && @do_callsites
666
+ ref_blocks = []
667
+ _get_defsites(ref_blocks, filename, lineno, linetext, ">>") do |ref|
668
+ if ref.file
669
+ ref.file.sub!(%r!^./!, '')
670
+ where = "at #{mangle_filename(ref.file)}:#{ref.line}"
671
+ else
672
+ where = "(C extension/core)"
673
+ end
674
+ "#{ref.klass}##{ref.mid} " + where + ""
675
+ end
676
+ _get_callsites(ref_blocks, filename, lineno, linetext, "<<") do |ref| # "
677
+ ref.file.sub!(%r!^./!, '')
678
+ "#{mangle_filename(ref.file||'C code')}:#{ref.line} " +
679
+ "in #{ref.klass}##{ref.mid}"
680
+ end
681
+
682
+ create_cross_reference_block(linetext, ref_blocks, marked)
683
+ end
684
+
685
+ def create_cross_reference_block(linetext, ref_blocks, marked)
686
+ codelen = 75
687
+ if ref_blocks.empty?
688
+ if marked
689
+ return "%-#{codelen}s #o" % linetext
690
+ else
691
+ return linetext
692
+ end
693
+ end
694
+ ret = ""
695
+ @cross_ref_idx ||= 0
696
+ @known_files ||= sorted_file_pairs.map{|fname, finfo| normalize_filename(fname)}
697
+ ret << "%-#{codelen}s # " % linetext
698
+ ref_blocks.each do |refs, toplabel, label_proc|
699
+ unless !toplabel || toplabel.empty?
700
+ ret << toplabel << " "
701
+ end
702
+ refs.each do |dst|
703
+ dstfile = normalize_filename(dst.file) if dst.file
704
+ dstline = dst.line
705
+ label = label_proc.call(dst)
706
+ if dst.file && @known_files.include?(dstfile)
707
+ ret << "[[" << label << "]], "
708
+ else
709
+ ret << label << ", "
710
+ end
711
+ end
712
+ end
713
+
714
+ ret
715
+ end
716
+
717
+ def create_file(destfile, fileinfo)
718
+ body = format_lines(fileinfo)
719
+ File.open(destfile, "w") do |f|
720
+ f.puts body
721
+ f.puts footer(fileinfo)
722
+ end
723
+ end
724
+
725
+ def footer(fileinfo)
726
+ s = "# Total lines : %d\n" % fileinfo.num_lines
727
+ s << "# Lines of code : %d\n" % fileinfo.num_code_lines
728
+ s << "# Total coverage : %3.1f%%\n" % [ fileinfo.total_coverage*100 ]
729
+ s << "# Code coverage : %3.1f%%\n\n" % [ fileinfo.code_coverage*100 ]
730
+ # prevents false positives on Emacs
731
+ s << "# Local " "Variables:\n" "# mode: " "rcov-xref\n" "# End:\n"
732
+ end
733
+ end
734
+
735
+ end