rcov 0.8.1.2.0 → 0.9.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. data/BLURB +2 -40
  2. data/LICENSE +2 -5
  3. data/Rakefile +32 -106
  4. data/THANKS +14 -0
  5. data/bin/rcov +277 -1090
  6. data/doc/readme_for_api.markdown +22 -0
  7. data/doc/readme_for_emacs.markdown +52 -0
  8. data/doc/readme_for_rake.markdown +51 -0
  9. data/doc/readme_for_vim.markdown +34 -0
  10. data/{rcov.el → editor-extensions/rcov.el} +0 -0
  11. data/{rcov.vim → editor-extensions/rcov.vim} +0 -0
  12. data/ext/rcovrt/1.8/callsite.c +216 -0
  13. data/ext/rcovrt/1.8/rcovrt.c +287 -0
  14. data/ext/rcovrt/1.9/callsite.c +234 -0
  15. data/ext/rcovrt/1.9/rcovrt.c +264 -0
  16. data/ext/rcovrt/extconf.rb +12 -2
  17. data/lib/rcov.rb +13 -968
  18. data/lib/rcov/call_site_analyzer.rb +225 -0
  19. data/lib/rcov/code_coverage_analyzer.rb +268 -0
  20. data/lib/rcov/coverage_info.rb +36 -0
  21. data/lib/rcov/differential_analyzer.rb +116 -0
  22. data/lib/rcov/file_statistics.rb +334 -0
  23. data/lib/rcov/formatters.rb +13 -0
  24. data/lib/rcov/formatters/base_formatter.rb +173 -0
  25. data/lib/rcov/formatters/failure_report.rb +15 -0
  26. data/lib/rcov/formatters/full_text_report.rb +48 -0
  27. data/lib/rcov/formatters/html_coverage.rb +274 -0
  28. data/lib/rcov/formatters/html_erb_template.rb +62 -0
  29. data/lib/rcov/formatters/text_coverage_diff.rb +193 -0
  30. data/lib/rcov/formatters/text_report.rb +32 -0
  31. data/lib/rcov/formatters/text_summary.rb +11 -0
  32. data/lib/rcov/lowlevel.rb +16 -17
  33. data/lib/rcov/rcovtask.rb +21 -22
  34. data/lib/rcov/templates/detail.html.erb +64 -0
  35. data/lib/rcov/templates/index.html.erb +93 -0
  36. data/lib/rcov/templates/jquery-1.3.2.min.js +19 -0
  37. data/lib/rcov/templates/jquery.tablesorter.min.js +15 -0
  38. data/lib/rcov/templates/print.css +12 -0
  39. data/lib/rcov/templates/rcov.js +42 -0
  40. data/lib/rcov/templates/screen.css +270 -0
  41. data/lib/rcov/version.rb +5 -8
  42. data/setup.rb +5 -2
  43. data/test/{sample_01.rb → assets/sample_01.rb} +0 -0
  44. data/test/{sample_02.rb → assets/sample_02.rb} +0 -0
  45. data/test/{sample_03.rb → assets/sample_03.rb} +0 -0
  46. data/test/{sample_04.rb → assets/sample_04.rb} +0 -0
  47. data/test/{sample_05-new.rb → assets/sample_05-new.rb} +0 -0
  48. data/test/{sample_05-old.rb → assets/sample_05-old.rb} +0 -0
  49. data/test/{sample_05.rb → assets/sample_05.rb} +0 -0
  50. data/test/{test_CallSiteAnalyzer.rb → call_site_analyzer_test.rb} +57 -81
  51. data/test/{test_CodeCoverageAnalyzer.rb → code_coverage_analyzer_test.rb} +71 -35
  52. data/test/{test_FileStatistics.rb → file_statistics_test.rb} +34 -36
  53. data/test/{test_functional.rb → functional_test.rb} +21 -35
  54. metadata +91 -69
  55. data/CHANGES +0 -177
  56. data/LEGAL +0 -36
  57. data/README.API +0 -42
  58. data/README.emacs +0 -64
  59. data/README.en +0 -130
  60. data/README.rake +0 -62
  61. data/README.rant +0 -68
  62. data/README.vim +0 -47
  63. data/Rantfile +0 -76
  64. data/ext/rcovrt/callsite.c +0 -242
  65. data/ext/rcovrt/rcovrt.c +0 -329
  66. data/lib/rcov/rant.rb +0 -87
  67. data/lib/rcov/report.rb +0 -1236
  68. data/mingw-rbconfig.rb +0 -174
@@ -0,0 +1,13 @@
1
+ require 'rcov/formatters/html_erb_template'
2
+ require 'rcov/formatters/base_formatter'
3
+ require 'rcov/formatters/text_summary'
4
+ require 'rcov/formatters/text_report'
5
+ require 'rcov/formatters/text_coverage_diff'
6
+ require 'rcov/formatters/full_text_report'
7
+ require 'rcov/formatters/html_coverage'
8
+ require 'rcov/formatters/failure_report'
9
+
10
+ module Rcov
11
+ module Formatters
12
+ end
13
+ end
@@ -0,0 +1,173 @@
1
+ module Rcov
2
+ class BaseFormatter # :nodoc:
3
+ require 'pathname'
4
+
5
+ if RUBY_PLATFORM =~ /java/
6
+ ignore_files = [ /\btc_[^.]*.rb/, /_test\.rb\z/, /\btest\//, /\bvendor\//, /\A#{Regexp.escape(__FILE__)}\z/]
7
+ else
8
+ require 'mkmf'
9
+ ignore_files = [/\A#{Regexp.escape(Pathname.new(::Config::CONFIG['libdir']).cleanpath.to_s)}/, /\btc_[^.]*.rb/, /_test\.rb\z/, /\btest\//, /\bvendor\//, /\A#{Regexp.escape(__FILE__)}\z/]
10
+ end
11
+
12
+ DEFAULT_OPTS = { :ignore => ignore_files, :sort => :name, :sort_reverse => false,
13
+ :output_threshold => 101, :dont_ignore => [], :callsite_analyzer => nil, \
14
+ :comments_run_by_default => false }
15
+
16
+ def initialize(opts = {})
17
+ options = DEFAULT_OPTS.clone.update(opts)
18
+ @failure_threshold = options[:failure_threshold]
19
+ @files = {}
20
+ @ignore_files = options[:ignore]
21
+ @dont_ignore_files = options[:dont_ignore]
22
+ @sort_criterium = case options[:sort]
23
+ when :loc then lambda{|fname, finfo| finfo.num_code_lines}
24
+ when :coverage then lambda{|fname, finfo| finfo.code_coverage}
25
+ else lambda { |fname, finfo| fname }
26
+ end
27
+ @sort_reverse = options[:sort_reverse]
28
+ @output_threshold = options[:output_threshold]
29
+ @callsite_analyzer = options[:callsite_analyzer]
30
+ @comments_run_by_default = options[:comments_run_by_default]
31
+ @callsite_index = nil
32
+
33
+ @mangle_filename = Hash.new{|h,base|
34
+ h[base] = Pathname.new(base).cleanpath.to_s.gsub(%r{^\w:[/\\]}, "").gsub(/\./, "_").gsub(/[\\\/]/, "-") + ".html"
35
+ }
36
+ end
37
+
38
+ def add_file(filename, lines, coverage, counts)
39
+ old_filename = filename
40
+ filename = normalize_filename(filename)
41
+ SCRIPT_LINES__[filename] = SCRIPT_LINES__[old_filename]
42
+ if @ignore_files.any?{|x| x === filename} &&
43
+ !@dont_ignore_files.any?{|x| x === filename}
44
+ return nil
45
+ end
46
+ if @files[filename]
47
+ @files[filename].merge(lines, coverage, counts)
48
+ else
49
+ @files[filename] = FileStatistics.new(filename, lines, counts,
50
+ @comments_run_by_default)
51
+ end
52
+ end
53
+
54
+ def normalize_filename(filename)
55
+ File.expand_path(filename).gsub(/^#{Regexp.escape(Dir.getwd)}\//, '')
56
+ end
57
+
58
+ def mangle_filename(base)
59
+ @mangle_filename[base]
60
+ end
61
+
62
+ def each_file_pair_sorted(&b)
63
+ return sorted_file_pairs unless block_given?
64
+ sorted_file_pairs.each(&b)
65
+ end
66
+
67
+ def sorted_file_pairs
68
+ pairs = @files.sort_by do |fname, finfo|
69
+ @sort_criterium.call(fname, finfo)
70
+ end.select{|_, finfo| 100 * finfo.code_coverage < @output_threshold}
71
+ @sort_reverse ? pairs.reverse : pairs
72
+ end
73
+
74
+ def total_coverage
75
+ lines = 0
76
+ total = 0.0
77
+ @files.each do |k,f|
78
+ total += f.num_lines * f.total_coverage
79
+ lines += f.num_lines
80
+ end
81
+ return 0 if lines == 0
82
+ total / lines
83
+ end
84
+
85
+ def code_coverage
86
+ lines = 0
87
+ total = 0.0
88
+ @files.each do |k,f|
89
+ total += f.num_code_lines * f.code_coverage
90
+ lines += f.num_code_lines
91
+ end
92
+ return 0 if lines == 0
93
+ total / lines
94
+ end
95
+
96
+ def num_code_lines
97
+ lines = 0
98
+ @files.each{|k, f| lines += f.num_code_lines }
99
+ lines
100
+ end
101
+
102
+ def num_lines
103
+ lines = 0
104
+ @files.each{|k, f| lines += f.num_lines }
105
+ lines
106
+ end
107
+
108
+ private
109
+
110
+ def cross_references_for(filename, lineno)
111
+ return nil unless @callsite_analyzer
112
+ @callsite_index ||= build_callsite_index
113
+ @callsite_index[normalize_filename(filename)][lineno]
114
+ end
115
+
116
+ def reverse_cross_references_for(filename, lineno)
117
+ return nil unless @callsite_analyzer
118
+ @callsite_reverse_index ||= build_reverse_callsite_index
119
+ @callsite_reverse_index[normalize_filename(filename)][lineno]
120
+ end
121
+
122
+ def build_callsite_index
123
+ index = Hash.new{|h,k| h[k] = {}}
124
+ @callsite_analyzer.analyzed_classes.each do |classname|
125
+ @callsite_analyzer.analyzed_methods(classname).each do |methname|
126
+ defsite = @callsite_analyzer.defsite(classname, methname)
127
+ index[normalize_filename(defsite.file)][defsite.line] =
128
+ @callsite_analyzer.callsites(classname, methname)
129
+ end
130
+ end
131
+ index
132
+ end
133
+
134
+ def build_reverse_callsite_index
135
+ index = Hash.new{|h,k| h[k] = {}}
136
+ @callsite_analyzer.analyzed_classes.each do |classname|
137
+ @callsite_analyzer.analyzed_methods(classname).each do |methname|
138
+ callsites = @callsite_analyzer.callsites(classname, methname)
139
+ defsite = @callsite_analyzer.defsite(classname, methname)
140
+ callsites.each_pair do |callsite, count|
141
+ next unless callsite.file
142
+ fname = normalize_filename(callsite.file)
143
+ (index[fname][callsite.line] ||= []) << [classname, methname, defsite, count]
144
+ end
145
+ end
146
+ end
147
+ index
148
+ end
149
+
150
+ class XRefHelper < Struct.new(:file, :line, :klass, :mid, :count) # :nodoc:
151
+ end
152
+
153
+ def _get_defsites(ref_blocks, filename, lineno, linetext, label, &format_call_ref)
154
+ if @do_cross_references and
155
+ (rev_xref = reverse_cross_references_for(filename, lineno))
156
+ refs = rev_xref.map do |classname, methodname, defsite, count|
157
+ XRefHelper.new(defsite.file, defsite.line, classname, methodname, count)
158
+ end.sort_by{|r| r.count}.reverse
159
+ ref_blocks << [refs, label, format_call_ref]
160
+ end
161
+ end
162
+
163
+ def _get_callsites(ref_blocks, filename, lineno, linetext, label, &format_called_ref)
164
+ if @do_callsites and
165
+ (refs = cross_references_for(filename, lineno))
166
+ refs = refs.sort_by{|k,count| count}.map do |ref, count|
167
+ XRefHelper.new(ref.file, ref.line, ref.calling_class, ref.calling_method, count)
168
+ end.reverse
169
+ ref_blocks << [refs, label, format_called_ref]
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,15 @@
1
+ module Rcov
2
+ class FailureReport < TextSummary # :nodoc:
3
+ def execute
4
+ puts summary
5
+ coverage = code_coverage * 100
6
+ if coverage < @failure_threshold
7
+ puts "You failed to satisfy the coverage theshold of #{@failure_threshold}%"
8
+ exit(1)
9
+ end
10
+ if (coverage - @failure_threshold) > 3
11
+ puts "Your coverage has significantly increased over your threshold of #{@failure_threshold}. Please increase it."
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,48 @@
1
+ module Rcov
2
+ class FullTextReport < BaseFormatter # :nodoc:
3
+ DEFAULT_OPTS = {:textmode => :coverage}
4
+
5
+ def initialize(opts = {})
6
+ options = DEFAULT_OPTS.clone.update(opts)
7
+ @textmode = options[:textmode]
8
+ @color = options[:color]
9
+ super(options)
10
+ end
11
+
12
+ def execute
13
+ each_file_pair_sorted do |filename, fileinfo|
14
+ puts "=" * 80
15
+ puts filename
16
+ puts "=" * 80
17
+ lines = SCRIPT_LINES__[filename]
18
+
19
+ unless lines
20
+ # try to get the source code from the global code coverage
21
+ # analyzer
22
+ re = /#{Regexp.escape(filename)}\z/
23
+ if $rcov_code_coverage_analyzer and
24
+ (data = $rcov_code_coverage_analyzer.data_matching(re))
25
+ lines = data[0]
26
+ end
27
+ end
28
+
29
+ (lines || []).each_with_index do |line, i|
30
+ case @textmode
31
+ when :counts
32
+ puts "%-70s| %6d" % [line.chomp[0,70], fileinfo.counts[i]]
33
+ when :gcc
34
+ puts "%s:%d:%s" % [filename, i+1, line.chomp] unless fileinfo.coverage[i]
35
+ when :coverage
36
+ if @color
37
+ prefix = fileinfo.coverage[i] ? "\e[32;40m" : "\e[31;40m"
38
+ puts "#{prefix}%s\e[37;40m" % line.chomp
39
+ else
40
+ prefix = fileinfo.coverage[i] ? " " : "!! "
41
+ puts "#{prefix}#{line}"
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,274 @@
1
+ module Rcov
2
+ class HTMLCoverage < BaseFormatter # :nodoc:
3
+ require 'fileutils'
4
+
5
+ DEFAULT_OPTS = {:color => false, :fsr => 30, :destdir => "coverage",
6
+ :callsites => false, :cross_references => false,
7
+ :charset => nil }
8
+
9
+ def initialize(opts = {})
10
+ options = DEFAULT_OPTS.clone.update(opts)
11
+ super(options)
12
+ @dest = options[:destdir]
13
+ @css = options[:css]
14
+ @color = options[:color]
15
+ @fsr = options[:fsr]
16
+ @do_callsites = options[:callsites]
17
+ @do_cross_references = options[:cross_references]
18
+ @span_class_index = 0
19
+ @charset = options[:charset]
20
+ end
21
+
22
+ def execute
23
+ return if @files.empty?
24
+ FileUtils.mkdir_p @dest
25
+
26
+ # Copy collaterals
27
+ ['screen.css','print.css','rcov.js','jquery-1.3.2.min.js','jquery.tablesorter.min.js'].each do |_file|
28
+ _src = File.expand_path("#{File.dirname(__FILE__)}/../templates/#{_file}")
29
+ FileUtils.cp(_src, File.join(@dest, "#{_file}"))
30
+ end
31
+
32
+ # Copy custom CSS, if any
33
+ if @css
34
+ begin
35
+ _src = File.expand_path("#{@dest}/../#{@css}")
36
+ FileUtils.cp(_src, File.join(@dest, "custom.css"))
37
+ rescue
38
+ @css = nil
39
+ end
40
+ end
41
+
42
+ create_index(File.join(@dest, "index.html"))
43
+
44
+ each_file_pair_sorted do |filename, fileinfo|
45
+ create_file(File.join(@dest, mangle_filename(filename)), fileinfo)
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ class SummaryFileInfo # :nodoc:
52
+ def initialize(obj)
53
+ @o = obj
54
+ end
55
+
56
+ def num_lines
57
+ @o.num_lines
58
+ end
59
+
60
+ def num_code_lines
61
+ @o.num_code_lines
62
+ end
63
+
64
+ def code_coverage
65
+ @o.code_coverage
66
+ end
67
+
68
+ def code_coverage_for_report
69
+ code_coverage * 100
70
+ end
71
+
72
+ def total_coverage
73
+ @o.total_coverage
74
+ end
75
+
76
+ def total_coverage_for_report
77
+ total_coverage * 100
78
+ end
79
+
80
+ def name
81
+ "TOTAL"
82
+ end
83
+ end
84
+
85
+ def create_index(destname)
86
+
87
+ doc = Rcov::Formatters::HtmlErbTemplate.new('index.html.erb',
88
+ :project_name => project_name,
89
+ :generated_on => Time.now,
90
+ :css => @css,
91
+ :rcov => Rcov,
92
+ :formatter => self,
93
+ :output_threshold => @output_threshold,
94
+ :total => SummaryFileInfo.new(self),
95
+ :files => each_file_pair_sorted.map{|k,v| v}
96
+ )
97
+ File.open(destname, "w") { |f| f.puts doc.render }
98
+ end
99
+
100
+ def create_file(destfile, fileinfo)
101
+ doc = Rcov::Formatters::HtmlErbTemplate.new('detail.html.erb',
102
+ :project_name => project_name,
103
+ :page_title => fileinfo.name,
104
+ :css => @css,
105
+ :generated_on => Time.now,
106
+ :rcov => Rcov,
107
+ :formatter => self,
108
+ :output_threshold => @output_threshold,
109
+ :fileinfo => fileinfo
110
+ )
111
+ File.open(destfile, "w") { |f| f.puts doc.render }
112
+ end
113
+
114
+ private
115
+
116
+ def project_name
117
+ Dir.pwd.split('/')[-1].split(/[^a-zA-Z0-9]/).map{|i| i.gsub(/[^a-zA-Z0-9]/,'').capitalize} * " " || ""
118
+ end
119
+
120
+ end
121
+
122
+ class HTMLProfiling < HTMLCoverage # :nodoc:
123
+ DEFAULT_OPTS = {:destdir => "profiling"}
124
+ def initialize(opts = {})
125
+ options = DEFAULT_OPTS.clone.update(opts)
126
+ super(options)
127
+ @max_cache = {}
128
+ @median_cache = {}
129
+ end
130
+
131
+ def default_title
132
+ "Bogo-profile information"
133
+ end
134
+
135
+ def default_color
136
+ if @color
137
+ "rgb(179,205,255)"
138
+ else
139
+ "rgb(255, 255, 255)"
140
+ end
141
+ end
142
+
143
+ def output_color_table?
144
+ false
145
+ end
146
+
147
+ def span_class(sourceinfo, marked, count)
148
+ full_scale_range = @fsr # dB
149
+ nz_count = sourceinfo.counts.select{|x| x && x != 0}
150
+ nz_count << 1 # avoid div by 0
151
+ max = @max_cache[sourceinfo] ||= nz_count.max
152
+ #avg = @median_cache[sourceinfo] ||= 1.0 *
153
+ # nz_count.inject{|a,b| a+b} / nz_count.size
154
+ median = @median_cache[sourceinfo] ||= 1.0 * nz_count.sort[nz_count.size/2]
155
+ max ||= 2
156
+ max = 2 if max == 1
157
+ if marked == true
158
+ count = 1 if !count || count == 0
159
+ idx = 50 + 1.0 * (500/full_scale_range) * Math.log(count/median) / Math.log(10)
160
+ idx = idx.to_i
161
+ idx = 0 if idx < 0
162
+ idx = 100 if idx > 100
163
+ "run#{idx}"
164
+ else
165
+ nil
166
+ end
167
+ end
168
+ end
169
+
170
+ class RubyAnnotation < BaseFormatter # :nodoc:
171
+ DEFAULT_OPTS = { :destdir => "coverage" }
172
+ def initialize(opts = {})
173
+ options = DEFAULT_OPTS.clone.update(opts)
174
+ super(options)
175
+ @dest = options[:destdir]
176
+ @do_callsites = true
177
+ @do_cross_references = true
178
+
179
+ @mangle_filename = Hash.new{ |h,base|
180
+ h[base] = Pathname.new(base).cleanpath.to_s.gsub(%r{^\w:[/\\]}, "").gsub(/\./, "_").gsub(/[\\\/]/, "-") + ".rb"
181
+ }
182
+ end
183
+
184
+ def execute
185
+ return if @files.empty?
186
+ FileUtils.mkdir_p @dest
187
+ each_file_pair_sorted do |filename, fileinfo|
188
+ create_file(File.join(@dest, mangle_filename(filename)), fileinfo)
189
+ end
190
+ end
191
+
192
+ private
193
+
194
+ def format_lines(file)
195
+ result = ""
196
+ format_line = "%#{file.num_lines.to_s.size}d"
197
+ file.num_lines.times do |i|
198
+ line = file.lines[i].chomp
199
+ marked = file.coverage[i]
200
+ count = file.counts[i]
201
+ result << create_cross_refs(file.name, i+1, line, marked) + "\n"
202
+ end
203
+ result
204
+ end
205
+
206
+ def create_cross_refs(filename, lineno, linetext, marked)
207
+ return linetext unless @callsite_analyzer && @do_callsites
208
+ ref_blocks = []
209
+ _get_defsites(ref_blocks, filename, lineno, linetext, ">>") do |ref|
210
+ if ref.file
211
+ ref.file.sub!(%r!^./!, '')
212
+ where = "at #{mangle_filename(ref.file)}:#{ref.line}"
213
+ else
214
+ where = "(C extension/core)"
215
+ end
216
+ "#{ref.klass}##{ref.mid} " + where + ""
217
+ end
218
+ _get_callsites(ref_blocks, filename, lineno, linetext, "<<") do |ref| # "
219
+ ref.file.sub!(%r!^./!, '')
220
+ "#{mangle_filename(ref.file||'C code')}:#{ref.line} " + "in #{ref.klass}##{ref.mid}"
221
+ end
222
+
223
+ create_cross_reference_block(linetext, ref_blocks, marked)
224
+ end
225
+
226
+ def create_cross_reference_block(linetext, ref_blocks, marked)
227
+ codelen = 75
228
+ if ref_blocks.empty?
229
+ if marked
230
+ return "%-#{codelen}s #o" % linetext
231
+ else
232
+ return linetext
233
+ end
234
+ end
235
+ ret = ""
236
+ @cross_ref_idx ||= 0
237
+ @known_files ||= sorted_file_pairs.map{|fname, finfo| normalize_filename(fname)}
238
+ ret << "%-#{codelen}s # " % linetext
239
+ ref_blocks.each do |refs, toplabel, label_proc|
240
+ unless !toplabel || toplabel.empty?
241
+ ret << toplabel << " "
242
+ end
243
+ refs.each do |dst|
244
+ dstfile = normalize_filename(dst.file) if dst.file
245
+ dstline = dst.line
246
+ label = label_proc.call(dst)
247
+ if dst.file && @known_files.include?(dstfile)
248
+ ret << "[[" << label << "]], "
249
+ else
250
+ ret << label << ", "
251
+ end
252
+ end
253
+ end
254
+ ret
255
+ end
256
+
257
+ def create_file(destfile, fileinfo)
258
+ #body = format_lines(fileinfo)
259
+ #File.open(destfile, "w") do |f|
260
+ #f.puts body
261
+ #f.puts footer(fileinfo)
262
+ #end
263
+ end
264
+
265
+ def footer(fileinfo)
266
+ s = "# Total lines : %d\n" % fileinfo.num_lines
267
+ s << "# Lines of code : %d\n" % fileinfo.num_code_lines
268
+ s << "# Total coverage : %3.1f%%\n" % [ fileinfo.total_coverage*100 ]
269
+ s << "# Code coverage : %3.1f%%\n\n" % [ fileinfo.code_coverage*100 ]
270
+ # prevents false positives on Emacs
271
+ s << "# Local " "Variables:\n" "# mode: " "rcov-xref\n" "# End:\n"
272
+ end
273
+ end
274
+ end