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
data/ext/rcovrt/rcovrt.c DELETED
@@ -1,329 +0,0 @@
1
-
2
- #include <ruby.h>
3
- #include <env.h>
4
- #include <node.h>
5
- #include <st.h>
6
- #include <stdlib.h>
7
- #include <assert.h>
8
-
9
- #define COVERAGE_DEBUG_EVENTS 0
10
-
11
- #define RCOVRT_VERSION_MAJOR 2
12
- #define RCOVRT_VERSION_MINOR 0
13
- #define RCOVRT_VERSION_REV 0
14
-
15
- static VALUE mRcov;
16
- static VALUE mRCOV__;
17
- static VALUE oSCRIPT_LINES__;
18
- static ID id_cover;
19
- static st_table* coverinfo = 0;
20
- static char coverage_hook_set_p;
21
-
22
- struct cov_array {
23
- unsigned int len;
24
- unsigned int *ptr;
25
- };
26
-
27
- static struct cov_array *cached_array = 0;
28
- static char *cached_file = 0;
29
-
30
-
31
- /*
32
- *
33
- * coverage hook and associated functions
34
- *
35
- * */
36
-
37
- static struct cov_array *
38
- coverage_increase_counter_uncached(char *sourcefile, unsigned int sourceline,
39
- char mark_only)
40
- {
41
- struct cov_array *carray = NULL;
42
-
43
- if(sourcefile == NULL) {
44
- /* "can't happen", just ignore and avoid segfault */
45
- return NULL;
46
- } else if(!st_lookup(coverinfo, (st_data_t)sourcefile, (st_data_t*)&carray)) {
47
- VALUE arr;
48
-
49
- arr = rb_hash_aref(oSCRIPT_LINES__, rb_str_new2(sourcefile));
50
- if(NIL_P(arr))
51
- return 0;
52
- rb_check_type(arr, T_ARRAY);
53
- carray = calloc(1, sizeof(struct cov_array));
54
- carray->ptr = calloc(RARRAY(arr)->len, sizeof(unsigned int));
55
- carray->len = RARRAY(arr)->len;
56
- st_insert(coverinfo, (st_data_t)strdup(sourcefile),
57
- (st_data_t) carray);
58
- } else {
59
- /* recovered carray, sanity check */
60
- assert(carray && "failed to create valid carray");
61
- }
62
-
63
- if(mark_only) {
64
- if(!carray->ptr[sourceline])
65
- carray->ptr[sourceline] = 1;
66
- } else {
67
- carray->ptr[sourceline]++;
68
- }
69
-
70
- return carray;
71
- }
72
-
73
-
74
- static void
75
- coverage_mark_caller()
76
- {
77
- struct FRAME *frame = ruby_frame;
78
- NODE *n;
79
-
80
- if (frame->last_func == ID_ALLOCATOR) {
81
- frame = frame->prev;
82
- }
83
- for (; frame && (n = frame->node); frame = frame->prev) {
84
- if (frame->prev && frame->prev->last_func) {
85
- if (frame->prev->node == n) {
86
- if (frame->prev->last_func == frame->last_func) continue;
87
- }
88
- coverage_increase_counter_uncached(n->nd_file, nd_line(n) - 1, 1);
89
- }
90
- else {
91
- coverage_increase_counter_uncached(n->nd_file, nd_line(n) - 1, 1);
92
- }
93
- break;
94
- }
95
- }
96
-
97
-
98
- static void
99
- coverage_increase_counter_cached(char *sourcefile, int sourceline)
100
- {
101
- if(cached_file == sourcefile && cached_array) {
102
- cached_array->ptr[sourceline]++;
103
- return;
104
- }
105
- cached_file = sourcefile;
106
- cached_array = coverage_increase_counter_uncached(sourcefile, sourceline, 0);
107
- }
108
-
109
-
110
- static void
111
- coverage_event_coverage_hook(rb_event_t event, NODE *node, VALUE self,
112
- ID mid, VALUE klass)
113
- {
114
- char *sourcefile;
115
- unsigned int sourceline;
116
- static unsigned int in_hook = 0;
117
-
118
- if(in_hook) {
119
- return;
120
- }
121
-
122
- in_hook++;
123
-
124
- #if COVERAGE_DEBUG_EVENTS
125
- do {
126
- int status;
127
- VALUE old_exception;
128
- old_exception = rb_gv_get("$!");
129
- rb_protect(rb_inspect, klass, &status);
130
- if(!status) {
131
- printf("EVENT: %d %s %s %s %d\n", event,
132
- klass ? RSTRING(rb_inspect(klass))->ptr : "",
133
- mid ? (mid == ID_ALLOCATOR ? "ID_ALLOCATOR" : rb_id2name(mid))
134
- : "unknown",
135
- node ? node->nd_file : "", node ? nd_line(node) : 0);
136
- } else {
137
- printf("EVENT: %d %s %s %d\n", event,
138
- mid ? (mid == ID_ALLOCATOR ? "ID_ALLOCATOR" : rb_id2name(mid))
139
- : "unknown",
140
- node ? node->nd_file : "", node ? nd_line(node) : 0);
141
- }
142
- rb_gv_set("$!", old_exception);
143
- } while (0);
144
- #endif
145
-
146
- if(event & RUBY_EVENT_C_CALL) {
147
- coverage_mark_caller();
148
- }
149
- if(event & (RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN | RUBY_EVENT_CLASS)) {
150
- in_hook--;
151
- return;
152
- }
153
-
154
- if(node == NULL) {
155
- in_hook--;
156
- return;
157
- }
158
-
159
- sourcefile = node->nd_file;
160
- sourceline = nd_line(node) - 1;
161
-
162
- coverage_increase_counter_cached(sourcefile, sourceline);
163
- if(event & RUBY_EVENT_CALL)
164
- coverage_mark_caller();
165
- in_hook--;
166
- }
167
-
168
-
169
- static VALUE
170
- cov_install_coverage_hook(VALUE self)
171
- {
172
- if(!coverage_hook_set_p) {
173
- if(!coverinfo)
174
- coverinfo = st_init_strtable();
175
- coverage_hook_set_p = 1;
176
- /* TODO: allow C_CALL too, since it's supported already
177
- * the overhead is around ~30%, tested on typo */
178
- rb_add_event_hook(coverage_event_coverage_hook,
179
- RUBY_EVENT_ALL & ~RUBY_EVENT_C_CALL &
180
- ~RUBY_EVENT_C_RETURN & ~RUBY_EVENT_CLASS);
181
-
182
- return Qtrue;
183
- }
184
- else
185
- return Qfalse;
186
- }
187
-
188
-
189
- static int
190
- populate_cover(st_data_t key, st_data_t value, st_data_t cover)
191
- {
192
- VALUE rcover;
193
- VALUE rkey;
194
- VALUE rval;
195
- struct cov_array *carray;
196
- unsigned int i;
197
-
198
- rcover = (VALUE)cover;
199
- carray = (struct cov_array *) value;
200
- rkey = rb_str_new2((char*) key);
201
- rval = rb_ary_new2(carray->len);
202
- for(i = 0; i < carray->len; i++)
203
- RARRAY(rval)->ptr[i] = UINT2NUM(carray->ptr[i]);
204
- RARRAY(rval)->len = carray->len;
205
-
206
- rb_hash_aset(rcover, rkey, rval);
207
-
208
- return ST_CONTINUE;
209
- }
210
-
211
-
212
- static int
213
- free_table(st_data_t key, st_data_t value, st_data_t ignored)
214
- {
215
- struct cov_array *carray;
216
-
217
- carray = (struct cov_array *) value;
218
- free((char *)key);
219
- free(carray->ptr);
220
- free(carray);
221
-
222
- return ST_CONTINUE;
223
- }
224
-
225
-
226
- static VALUE
227
- cov_remove_coverage_hook(VALUE self)
228
- {
229
- if(!coverage_hook_set_p)
230
- return Qfalse;
231
- else {
232
- rb_remove_event_hook(coverage_event_coverage_hook);
233
- coverage_hook_set_p = 0;
234
- return Qtrue;
235
- }
236
- }
237
-
238
-
239
- static VALUE
240
- cov_generate_coverage_info(VALUE self)
241
- {
242
- VALUE cover;
243
-
244
- if(rb_const_defined_at(mRCOV__, id_cover)) {
245
- rb_mod_remove_const(mRCOV__, ID2SYM(id_cover));
246
- }
247
-
248
- cover = rb_hash_new();
249
- if(coverinfo)
250
- st_foreach(coverinfo, populate_cover, cover);
251
- rb_define_const(mRCOV__, "COVER", cover);
252
-
253
- return cover;
254
- }
255
-
256
-
257
- static VALUE
258
- cov_reset_coverage(VALUE self)
259
- {
260
- if(coverage_hook_set_p) {
261
- rb_raise(rb_eRuntimeError,
262
- "Cannot reset the coverage info in the middle of a traced run.");
263
- return Qnil;
264
- }
265
-
266
- cached_array = 0;
267
- cached_file = 0;
268
- st_foreach(coverinfo, free_table, Qnil);
269
- st_free_table(coverinfo);
270
- coverinfo = 0;
271
-
272
- return Qnil;
273
- }
274
-
275
-
276
- static VALUE
277
- cov_ABI(VALUE self)
278
- {
279
- VALUE ret;
280
-
281
- ret = rb_ary_new();
282
- rb_ary_push(ret, INT2FIX(RCOVRT_VERSION_MAJOR));
283
- rb_ary_push(ret, INT2FIX(RCOVRT_VERSION_MINOR));
284
- rb_ary_push(ret, INT2FIX(RCOVRT_VERSION_REV));
285
-
286
- return ret;
287
- }
288
-
289
-
290
- void
291
- Init_rcovrt()
292
- {
293
- ID id_rcov = rb_intern("Rcov");
294
- ID id_coverage__ = rb_intern("RCOV__");
295
- ID id_script_lines__ = rb_intern("SCRIPT_LINES__");
296
-
297
- id_cover = rb_intern("COVER");
298
-
299
- if(rb_const_defined(rb_cObject, id_rcov))
300
- mRcov = rb_const_get(rb_cObject, id_rcov);
301
- else
302
- mRcov = rb_define_module("Rcov");
303
-
304
- if(rb_const_defined(mRcov, id_coverage__))
305
- mRCOV__ = rb_const_get_at(mRcov, id_coverage__);
306
- else
307
- mRCOV__ = rb_define_module_under(mRcov, "RCOV__");
308
-
309
- if(rb_const_defined(rb_cObject, id_script_lines__))
310
- oSCRIPT_LINES__ = rb_const_get(rb_cObject, rb_intern("SCRIPT_LINES__"));
311
- else {
312
- oSCRIPT_LINES__ = rb_hash_new();
313
- rb_const_set(rb_cObject, id_script_lines__, oSCRIPT_LINES__);
314
- }
315
-
316
- coverage_hook_set_p = 0;
317
-
318
- rb_define_singleton_method(mRCOV__, "install_coverage_hook",
319
- cov_install_coverage_hook, 0);
320
- rb_define_singleton_method(mRCOV__, "remove_coverage_hook",
321
- cov_remove_coverage_hook, 0);
322
- rb_define_singleton_method(mRCOV__, "generate_coverage_info",
323
- cov_generate_coverage_info, 0);
324
- rb_define_singleton_method(mRCOV__, "reset_coverage", cov_reset_coverage, 0);
325
- rb_define_singleton_method(mRCOV__, "ABI", cov_ABI, 0);
326
-
327
- Init_rcov_callsite();
328
- }
329
- /* vim: set sw=8 expandtab: */
data/lib/rcov/rant.rb DELETED
@@ -1,87 +0,0 @@
1
-
2
- require 'rant/rantlib'
3
-
4
- module Rant # :nodoc:
5
- module Generators # :nodoc:
6
- class Rcov # :nodoc:
7
- def self.rant_gen(app, ch, args, &block)
8
- if !args || args.empty?
9
- self.new(app, ch, &block)
10
- elsif args.size == 1
11
- name, pre = app.normalize_task_arg(args.first, ch)
12
- self.new(app, ch, name, pre, &block)
13
- else
14
- app.abort_at(ch,
15
- "Rcov takes only one additional argument, " +
16
- "which should be like one given to the `task' command.")
17
- end
18
- end
19
-
20
- attr_accessor :verbose
21
- attr_accessor :libs
22
- attr_accessor :test_dirs
23
- attr_accessor :pattern
24
- attr_accessor :test_files
25
- attr_accessor :rcov_opts
26
- # Directory where to put the generated XHTML reports
27
- attr_accessor :output_dir
28
-
29
- def initialize(app, cinf, name = :rcov, prerequisites = [], &block)
30
- @rac = app
31
- @name = name
32
- @pre = prerequisites
33
- #@block = block
34
- @verbose = nil
35
- cf = cinf[:file]
36
- @libs = []
37
- libdir = File.join(File.dirname(File.expand_path(cf)), 'lib')
38
- @libs << libdir if test(?d, libdir)
39
- @rcov_opts = ["--text-report"]
40
- @test_dirs = []
41
- @pattern = nil
42
- @test_files = nil
43
- yield self if block_given?
44
- @pattern = "test*.rb" if @pattern.nil? && @test_files.nil?
45
- @output_dir ||= "coverage"
46
-
47
- @pre ||= []
48
- # define the task
49
- app.task(:__caller__ => cinf, @name => @pre) { |t|
50
- args = []
51
- if @libs && !@libs.empty?
52
- args << "-I#{@libs.join File::PATH_SEPARATOR}"
53
- end
54
- if rcov_path = ENV['RCOVPATH']
55
- args << rcov_path
56
- else
57
- args << "-S" << "rcov"
58
- end
59
- args.concat rcov_opts
60
- args << "-o" << @output_dir
61
- if test(?d, "test")
62
- @test_dirs << "test"
63
- elsif test(?d, "tests")
64
- @test_dirs << "tests"
65
- end
66
- args.concat filelist
67
- app.context.sys.ruby args
68
- }
69
- end
70
-
71
- def filelist
72
- return @rac.sys[@rac.var['TEST']] if @rac.var['TEST']
73
- filelist = @test_files || []
74
- if filelist.empty?
75
- if @test_dirs && !@test_dirs.empty?
76
- @test_dirs.each { |dir|
77
- filelist.concat(@rac.sys[File.join(dir, @pattern)])
78
- }
79
- else
80
- filelist.concat(@rac.sys[@pattern]) if @pattern
81
- end
82
- end
83
- filelist
84
- end
85
- end # class Rcov
86
- end # module Generators
87
- end # module Rant
data/lib/rcov/report.rb DELETED
@@ -1,1236 +0,0 @@
1
- # rcov Copyright (c) 2004-2006 Mauricio Fernandez <mfp@acm.org>
2
- # See LEGAL and LICENSE for additional licensing information.
3
-
4
- require 'pathname'
5
- module Rcov
6
-
7
- # Try to fix bugs in the REXML shipped with Ruby 1.8.6
8
- # They affect Mac OSX 10.5.1 users and motivates endless bug reports.
9
- begin
10
- require 'rexml/formatters/transitive'
11
- require 'rexml/formatter/pretty'
12
- rescue LoadError
13
- end
14
-
15
- if RUBY_VERSION == "1.8.6" && defined? REXML::Formatters::Transitive
16
- class REXML::Document
17
- remove_method :write rescue nil
18
- def write( output=$stdout, indent=-1, trans=false, ie_hack=false )
19
- if xml_decl.encoding != "UTF-8" && !output.kind_of?(Output)
20
- output = Output.new( output, xml_decl.encoding )
21
- end
22
- formatter = if indent > -1
23
- #if trans
24
- REXML::Formatters::Transitive.new( indent )
25
- #else
26
- # REXML::Formatters::Pretty.new( indent, ie_hack )
27
- #end
28
- else
29
- REXML::Formatters::Default.new( ie_hack )
30
- end
31
- formatter.write( self, output )
32
- end
33
- end
34
-
35
- class REXML::Formatters::Transitive
36
- remove_method :write_element rescue nil
37
- def write_element( node, output )
38
- output << "<#{node.expanded_name}"
39
-
40
- node.attributes.each_attribute do |attr|
41
- output << " "
42
- attr.write( output )
43
- end unless node.attributes.empty?
44
-
45
- if node.children.empty?
46
- output << "/>"
47
- else
48
- output << ">"
49
- # If compact and all children are text, and if the formatted output
50
- # is less than the specified width, then try to print everything on
51
- # one line
52
- skip = false
53
- @level += @indentation
54
- node.children.each { |child|
55
- write( child, output )
56
- }
57
- @level -= @indentation
58
- output << "</#{node.expanded_name}>"
59
- end
60
- output << "\n"
61
- output << ' '*@level
62
- end
63
- end
64
- end
65
-
66
- class Formatter # :nodoc:
67
- require 'pathname'
68
- ignore_files = [
69
- /\A#{Regexp.escape(Pathname.new(Config::CONFIG["libdir"]).cleanpath.to_s)}/,
70
- /\btc_[^.]*.rb/,
71
- /_test\.rb\z/,
72
- /\btest\//,
73
- /\bvendor\//,
74
- /\A#{Regexp.escape(__FILE__)}\z/]
75
- DEFAULT_OPTS = {:ignore => ignore_files, :sort => :name, :sort_reverse => false,
76
- :output_threshold => 101, :dont_ignore => [],
77
- :callsite_analyzer => nil, :comments_run_by_default => false}
78
- def initialize(opts = {})
79
- options = DEFAULT_OPTS.clone.update(opts)
80
- @files = {}
81
- @ignore_files = options[:ignore]
82
- @dont_ignore_files = options[:dont_ignore]
83
- @sort_criterium = case options[:sort]
84
- when :loc : lambda{|fname, finfo| finfo.num_code_lines}
85
- when :coverage : lambda{|fname, finfo| finfo.code_coverage}
86
- else lambda{|fname, finfo| fname}
87
- end
88
- @sort_reverse = options[:sort_reverse]
89
- @output_threshold = options[:output_threshold]
90
- @callsite_analyzer = options[:callsite_analyzer]
91
- @comments_run_by_default = options[:comments_run_by_default]
92
- @callsite_index = nil
93
-
94
- @mangle_filename = Hash.new{|h,base|
95
- h[base] = Pathname.new(base).cleanpath.to_s.gsub(%r{^\w:[/\\]}, "").gsub(/\./, "_").gsub(/[\\\/]/, "-") + ".html"
96
- }
97
- end
98
-
99
- def add_file(filename, lines, coverage, counts)
100
- old_filename = filename
101
- filename = normalize_filename(filename)
102
- SCRIPT_LINES__[filename] = SCRIPT_LINES__[old_filename]
103
- if @ignore_files.any?{|x| x === filename} &&
104
- !@dont_ignore_files.any?{|x| x === filename}
105
- return nil
106
- end
107
- if @files[filename]
108
- @files[filename].merge(lines, coverage, counts)
109
- else
110
- @files[filename] = FileStatistics.new(filename, lines, counts,
111
- @comments_run_by_default)
112
- end
113
- end
114
-
115
- def normalize_filename(filename)
116
- File.expand_path(filename).gsub(/^#{Regexp.escape(Dir.getwd)}\//, '')
117
- end
118
-
119
- def mangle_filename(base)
120
- @mangle_filename[base]
121
- end
122
-
123
- def each_file_pair_sorted(&b)
124
- return sorted_file_pairs unless block_given?
125
- sorted_file_pairs.each(&b)
126
- end
127
-
128
- def sorted_file_pairs
129
- pairs = @files.sort_by do |fname, finfo|
130
- @sort_criterium.call(fname, finfo)
131
- end.select{|_, finfo| 100 * finfo.code_coverage < @output_threshold}
132
- @sort_reverse ? pairs.reverse : pairs
133
- end
134
-
135
- def total_coverage
136
- lines = 0
137
- total = 0.0
138
- @files.each do |k,f|
139
- total += f.num_lines * f.total_coverage
140
- lines += f.num_lines
141
- end
142
- return 0 if lines == 0
143
- total / lines
144
- end
145
-
146
- def code_coverage
147
- lines = 0
148
- total = 0.0
149
- @files.each do |k,f|
150
- total += f.num_code_lines * f.code_coverage
151
- lines += f.num_code_lines
152
- end
153
- return 0 if lines == 0
154
- total / lines
155
- end
156
-
157
- def num_code_lines
158
- lines = 0
159
- @files.each{|k, f| lines += f.num_code_lines }
160
- lines
161
- end
162
-
163
- def num_lines
164
- lines = 0
165
- @files.each{|k, f| lines += f.num_lines }
166
- lines
167
- end
168
-
169
- private
170
- def cross_references_for(filename, lineno)
171
- return nil unless @callsite_analyzer
172
- @callsite_index ||= build_callsite_index
173
- @callsite_index[normalize_filename(filename)][lineno]
174
- end
175
-
176
- def reverse_cross_references_for(filename, lineno)
177
- return nil unless @callsite_analyzer
178
- @callsite_reverse_index ||= build_reverse_callsite_index
179
- @callsite_reverse_index[normalize_filename(filename)][lineno]
180
- end
181
-
182
- def build_callsite_index
183
- index = Hash.new{|h,k| h[k] = {}}
184
- @callsite_analyzer.analyzed_classes.each do |classname|
185
- @callsite_analyzer.analyzed_methods(classname).each do |methname|
186
- defsite = @callsite_analyzer.defsite(classname, methname)
187
- index[normalize_filename(defsite.file)][defsite.line] =
188
- @callsite_analyzer.callsites(classname, methname)
189
- end
190
- end
191
- index
192
- end
193
-
194
- def build_reverse_callsite_index
195
- index = Hash.new{|h,k| h[k] = {}}
196
- @callsite_analyzer.analyzed_classes.each do |classname|
197
- @callsite_analyzer.analyzed_methods(classname).each do |methname|
198
- callsites = @callsite_analyzer.callsites(classname, methname)
199
- defsite = @callsite_analyzer.defsite(classname, methname)
200
- callsites.each_pair do |callsite, count|
201
- next unless callsite.file
202
- fname = normalize_filename(callsite.file)
203
- (index[fname][callsite.line] ||= []) << [classname, methname, defsite, count]
204
- end
205
- end
206
- end
207
- index
208
- end
209
-
210
- class XRefHelper < Struct.new(:file, :line, :klass, :mid, :count) # :nodoc:
211
- end
212
-
213
- def _get_defsites(ref_blocks, filename, lineno, linetext, label, &format_call_ref)
214
- if @do_cross_references and
215
- (rev_xref = reverse_cross_references_for(filename, lineno))
216
- refs = rev_xref.map do |classname, methodname, defsite, count|
217
- XRefHelper.new(defsite.file, defsite.line, classname, methodname, count)
218
- end.sort_by{|r| r.count}.reverse
219
- ref_blocks << [refs, label, format_call_ref]
220
- end
221
- end
222
-
223
- def _get_callsites(ref_blocks, filename, lineno, linetext, label, &format_called_ref)
224
- if @do_callsites and
225
- (refs = cross_references_for(filename, lineno))
226
- refs = refs.sort_by{|k,count| count}.map do |ref, count|
227
- XRefHelper.new(ref.file, ref.line, ref.calling_class, ref.calling_method, count)
228
- end.reverse
229
- ref_blocks << [refs, label, format_called_ref]
230
- end
231
- end
232
- end
233
-
234
- class TextSummary < Formatter # :nodoc:
235
- def execute
236
- puts summary
237
- end
238
-
239
- def summary
240
- "%.1f%% %d file(s) %d Lines %d LOC" % [code_coverage * 100,
241
- @files.size, num_lines, num_code_lines]
242
- end
243
- end
244
-
245
- class TextReport < TextSummary # :nodoc:
246
- def execute
247
- print_lines
248
- print_header
249
- print_lines
250
- each_file_pair_sorted do |fname, finfo|
251
- name = fname.size < 52 ? fname : "..." + fname[-48..-1]
252
- print_info(name, finfo.num_lines, finfo.num_code_lines,
253
- finfo.code_coverage)
254
- end
255
- print_lines
256
- print_info("Total", num_lines, num_code_lines, code_coverage)
257
- print_lines
258
- puts summary
259
- end
260
-
261
- def print_info(name, lines, loc, coverage)
262
- puts "|%-51s | %5d | %5d | %5.1f%% |" % [name, lines, loc, 100 * coverage]
263
- end
264
-
265
- def print_lines
266
- puts "+----------------------------------------------------+-------+-------+--------+"
267
- end
268
-
269
- def print_header
270
- puts "| File | Lines | LOC | COV |"
271
- end
272
- end
273
-
274
- class FullTextReport < Formatter # :nodoc:
275
- DEFAULT_OPTS = {:textmode => :coverage}
276
- def initialize(opts = {})
277
- options = DEFAULT_OPTS.clone.update(opts)
278
- @textmode = options[:textmode]
279
- @color = options[:color]
280
- super(options)
281
- end
282
-
283
- def execute
284
- each_file_pair_sorted do |filename, fileinfo|
285
- puts "=" * 80
286
- puts filename
287
- puts "=" * 80
288
- lines = SCRIPT_LINES__[filename]
289
- unless lines
290
- # try to get the source code from the global code coverage
291
- # analyzer
292
- re = /#{Regexp.escape(filename)}\z/
293
- if $rcov_code_coverage_analyzer and
294
- (data = $rcov_code_coverage_analyzer.data_matching(re))
295
- lines = data[0]
296
- end
297
- end
298
- (lines || []).each_with_index do |line, i|
299
- case @textmode
300
- when :counts
301
- puts "%-70s| %6d" % [line.chomp[0,70], fileinfo.counts[i]]
302
- when :gcc
303
- puts "%s:%d:%s" % [filename, i+1, line.chomp] unless fileinfo.coverage[i]
304
- when :coverage
305
- if @color
306
- prefix = fileinfo.coverage[i] ? "\e[32;40m" : "\e[31;40m"
307
- puts "#{prefix}%s\e[37;40m" % line.chomp
308
- else
309
- prefix = fileinfo.coverage[i] ? " " : "!! "
310
- puts "#{prefix}#{line}"
311
- end
312
- end
313
- end
314
- end
315
- end
316
- end
317
-
318
- class TextCoverageDiff < Formatter # :nodoc:
319
- FORMAT_VERSION = [0, 1, 0]
320
- DEFAULT_OPTS = {:textmode => :coverage_diff,
321
- :coverage_diff_mode => :record,
322
- :coverage_diff_file => "coverage.info",
323
- :diff_cmd => "diff", :comments_run_by_default => true}
324
- def SERIALIZER
325
- # mfp> this was going to be YAML but I caught it failing at basic
326
- # round-tripping, turning "\n" into "" and corrupting the data, so
327
- # it must be Marshal for now
328
- Marshal
329
- end
330
-
331
- def initialize(opts = {})
332
- options = DEFAULT_OPTS.clone.update(opts)
333
- @textmode = options[:textmode]
334
- @color = options[:color]
335
- @mode = options[:coverage_diff_mode]
336
- @state_file = options[:coverage_diff_file]
337
- @diff_cmd = options[:diff_cmd]
338
- @gcc_output = options[:gcc_output]
339
- super(options)
340
- end
341
-
342
- def execute
343
- case @mode
344
- when :record
345
- record_state
346
- when :compare
347
- compare_state
348
- else
349
- raise "Unknown TextCoverageDiff mode: #{mode.inspect}."
350
- end
351
- end
352
-
353
- def record_state
354
- state = {}
355
- each_file_pair_sorted do |filename, fileinfo|
356
- state[filename] = {:lines => SCRIPT_LINES__[filename],
357
- :coverage => fileinfo.coverage.to_a,
358
- :counts => fileinfo.counts}
359
- end
360
- File.open(@state_file, "w") do |f|
361
- self.SERIALIZER.dump([FORMAT_VERSION, state], f)
362
- end
363
- rescue
364
- $stderr.puts <<-EOF
365
- Couldn't save coverage data to #{@state_file}.
366
- EOF
367
- end # '
368
-
369
- require 'tempfile'
370
- def compare_state
371
- return unless verify_diff_available
372
- begin
373
- format, prev_state = File.open(@state_file){|f| self.SERIALIZER.load(f) }
374
- rescue
375
- $stderr.puts <<-EOF
376
- Couldn't load coverage data from #{@state_file}.
377
- EOF
378
- return # '
379
- end
380
- if !(Array === format) or
381
- FORMAT_VERSION[0] != format[0] || FORMAT_VERSION[1] < format[1]
382
- $stderr.puts <<-EOF
383
- Couldn't load coverage data from #{@state_file}.
384
- The file is saved in the format #{format.inspect[0..20]}.
385
- This rcov executable understands #{FORMAT_VERSION.inspect}.
386
- EOF
387
- return # '
388
- end
389
- each_file_pair_sorted do |filename, fileinfo|
390
- old_data = Tempfile.new("#{mangle_filename(filename)}-old")
391
- new_data = Tempfile.new("#{mangle_filename(filename)}-new")
392
- if prev_state.has_key? filename
393
- old_code, old_cov = prev_state[filename].values_at(:lines, :coverage)
394
- old_code.each_with_index do |line, i|
395
- prefix = old_cov[i] ? " " : "!! "
396
- old_data.write "#{prefix}#{line}"
397
- end
398
- else
399
- old_data.write ""
400
- end
401
- old_data.close
402
- SCRIPT_LINES__[filename].each_with_index do |line, i|
403
- prefix = fileinfo.coverage[i] ? " " : "!! "
404
- new_data.write "#{prefix}#{line}"
405
- end
406
- new_data.close
407
-
408
- diff = `#{@diff_cmd} -u "#{old_data.path}" "#{new_data.path}"`
409
- new_uncovered_hunks = process_unified_diff(filename, diff)
410
- old_data.close!
411
- new_data.close!
412
- display_hunks(filename, new_uncovered_hunks)
413
- end
414
- end
415
-
416
- def display_hunks(filename, hunks)
417
- return if hunks.empty?
418
- puts
419
- puts "=" * 80
420
- puts <<EOF
421
- !!!!! Uncovered code introduced in #{filename}
422
-
423
- EOF
424
- hunks.each do |offset, lines|
425
- if @gcc_output
426
- lines.each_with_index do |line,i|
427
- lineno = offset + i
428
- flag = (/^!! / !~ line) ? "-" : ":"
429
- prefix = "#{filename}#{flag}#{lineno}#{flag}"
430
- puts "#{prefix}#{line[3..-1]}"
431
- end
432
- elsif @color
433
- puts "### #{filename}:#{offset}"
434
- lines.each do |line|
435
- prefix = (/^!! / !~ line) ? "\e[32;40m" : "\e[31;40m"
436
- puts "#{prefix}#{line[3..-1].chomp}\e[37;40m"
437
- end
438
- else
439
- puts "### #{filename}:#{offset}"
440
- puts lines
441
- end
442
- end
443
- end
444
-
445
- def verify_diff_available
446
- old_stderr = STDERR.dup
447
- old_stdout = STDOUT.dup
448
- # TODO: should use /dev/null or NUL(?), but I don't want to add the
449
- # win32 check right now
450
- new_stderr = Tempfile.new("rcov_check_diff")
451
- STDERR.reopen new_stderr.path
452
- STDOUT.reopen new_stderr.path
453
-
454
- retval = system "#{@diff_cmd} --version"
455
- unless retval
456
- old_stderr.puts <<EOF
457
-
458
- The '#{@diff_cmd}' executable seems not to be available.
459
- You can specify which diff executable should be used with --diff-cmd.
460
- If your system doesn't have one, you might want to use Diff::LCS's:
461
- gem install diff-lcs
462
- and use --diff-cmd=ldiff.
463
- EOF
464
- return false
465
- end
466
- true
467
- ensure
468
- STDOUT.reopen old_stdout
469
- STDERR.reopen old_stderr
470
- new_stderr.close!
471
- end
472
-
473
- HUNK_HEADER = /@@ -\d+,\d+ \+(\d+),(\d+) @@/
474
- def process_unified_diff(filename, diff)
475
- current_hunk = []
476
- current_hunk_start = 0
477
- keep_current_hunk = false
478
- state = :init
479
- interesting_hunks = []
480
- diff.each_with_index do |line, i|
481
- #puts "#{state} %5d #{line}" % i
482
- case state
483
- when :init
484
- if md = HUNK_HEADER.match(line)
485
- current_hunk = []
486
- current_hunk_start = md[1].to_i
487
- state = :body
488
- end
489
- when :body
490
- case line
491
- when HUNK_HEADER
492
- new_start = $1.to_i
493
- if keep_current_hunk
494
- interesting_hunks << [current_hunk_start, current_hunk]
495
- end
496
- current_hunk_start = new_start
497
- current_hunk = []
498
- keep_current_hunk = false
499
- when /^-/
500
- # ignore
501
- when /^\+!! /
502
- keep_current_hunk = true
503
- current_hunk << line[1..-1]
504
- else
505
- current_hunk << line[1..-1]
506
- end
507
- end
508
- end
509
- if keep_current_hunk
510
- interesting_hunks << [current_hunk_start, current_hunk]
511
- end
512
-
513
- interesting_hunks
514
- end
515
- end
516
-
517
-
518
- class HTMLCoverage < Formatter # :nodoc:
519
- include XX::XHTML
520
- include XX::XMLish
521
- require 'fileutils'
522
- JAVASCRIPT_PROLOG = <<-EOS
523
-
524
- // <![CDATA[
525
- function toggleCode( id ) {
526
- if ( document.getElementById )
527
- elem = document.getElementById( id );
528
- else if ( document.all )
529
- elem = eval( "document.all." + id );
530
- else
531
- return false;
532
-
533
- elemStyle = elem.style;
534
-
535
- if ( elemStyle.display != "block" ) {
536
- elemStyle.display = "block"
537
- } else {
538
- elemStyle.display = "none"
539
- }
540
-
541
- return true;
542
- }
543
-
544
- // Make cross-references hidden by default
545
- document.writeln( "<style type=\\"text/css\\">span.cross-ref { display: none }</style>" )
546
- // ]]>
547
- EOS
548
-
549
- CSS_PROLOG = <<-EOS
550
- span.cross-ref-title {
551
- font-size: 140%;
552
- }
553
- span.cross-ref a {
554
- text-decoration: none;
555
- }
556
- span.cross-ref {
557
- background-color:#f3f7fa;
558
- border: 1px dashed #333;
559
- margin: 1em;
560
- padding: 0.5em;
561
- overflow: hidden;
562
- }
563
- a.crossref-toggle {
564
- text-decoration: none;
565
- }
566
- span.marked0 {
567
- background-color: rgb(185, 210, 200);
568
- display: block;
569
- }
570
- span.marked1 {
571
- background-color: rgb(190, 215, 205);
572
- display: block;
573
- }
574
- span.inferred0 {
575
- background-color: rgb(175, 200, 200);
576
- display: block;
577
- }
578
- span.inferred1 {
579
- background-color: rgb(180, 205, 205);
580
- display: block;
581
- }
582
- span.uncovered0 {
583
- background-color: rgb(225, 110, 110);
584
- display: block;
585
- }
586
- span.uncovered1 {
587
- background-color: rgb(235, 120, 120);
588
- display: block;
589
- }
590
- span.overview {
591
- border-bottom: 8px solid black;
592
- }
593
- div.overview {
594
- border-bottom: 8px solid black;
595
- }
596
- body {
597
- font-family: verdana, arial, helvetica;
598
- }
599
-
600
- div.footer {
601
- font-size: 68%;
602
- margin-top: 1.5em;
603
- }
604
-
605
- h1, h2, h3, h4, h5, h6 {
606
- margin-bottom: 0.5em;
607
- }
608
-
609
- h5 {
610
- margin-top: 0.5em;
611
- }
612
-
613
- .hidden {
614
- display: none;
615
- }
616
-
617
- div.separator {
618
- height: 10px;
619
- }
620
- /* Commented out for better readability, esp. on IE */
621
- /*
622
- table tr td, table tr th {
623
- font-size: 68%;
624
- }
625
-
626
- td.value table tr td {
627
- font-size: 11px;
628
- }
629
- */
630
-
631
- table.percent_graph {
632
- height: 12px;
633
- border: #808080 1px solid;
634
- empty-cells: show;
635
- }
636
-
637
- table.percent_graph td.covered {
638
- height: 10px;
639
- background: #00f000;
640
- }
641
-
642
- table.percent_graph td.uncovered {
643
- height: 10px;
644
- background: #e00000;
645
- }
646
-
647
- table.percent_graph td.NA {
648
- height: 10px;
649
- background: #eaeaea;
650
- }
651
-
652
- table.report {
653
- border-collapse: collapse;
654
- width: 100%;
655
- }
656
-
657
- table.report td.heading {
658
- background: #dcecff;
659
- border: #d0d0d0 1px solid;
660
- font-weight: bold;
661
- text-align: center;
662
- }
663
-
664
- table.report td.heading:hover {
665
- background: #c0ffc0;
666
- }
667
-
668
- table.report td.text {
669
- border: #d0d0d0 1px solid;
670
- }
671
-
672
- table.report td.value,
673
- table.report td.lines_total,
674
- table.report td.lines_code {
675
- text-align: right;
676
- border: #d0d0d0 1px solid;
677
- }
678
- table.report tr.light {
679
- background-color: rgb(240, 240, 245);
680
- }
681
- table.report tr.dark {
682
- background-color: rgb(230, 230, 235);
683
- }
684
- EOS
685
-
686
- DEFAULT_OPTS = {:color => false, :fsr => 30, :destdir => "coverage",
687
- :callsites => false, :cross_references => false,
688
- :validator_links => true, :charset => nil
689
- }
690
- def initialize(opts = {})
691
- options = DEFAULT_OPTS.clone.update(opts)
692
- super(options)
693
- @dest = options[:destdir]
694
- @color = options[:color]
695
- @fsr = options[:fsr]
696
- @do_callsites = options[:callsites]
697
- @do_cross_references = options[:cross_references]
698
- @span_class_index = 0
699
- @show_validator_links = options[:validator_links]
700
- @charset = options[:charset]
701
- end
702
-
703
- def execute
704
- return if @files.empty?
705
- FileUtils.mkdir_p @dest
706
- create_index(File.join(@dest, "index.html"))
707
- each_file_pair_sorted do |filename, fileinfo|
708
- create_file(File.join(@dest, mangle_filename(filename)), fileinfo)
709
- end
710
- end
711
-
712
- private
713
-
714
- def blurb
715
- xmlish_ {
716
- p_ {
717
- t_{ "Generated using the " }
718
- a_(:href => "http://eigenclass.org/hiki.rb?rcov") {
719
- t_{ "rcov code coverage analysis tool for Ruby" }
720
- }
721
- t_{ " version #{Rcov::VERSION}." }
722
- }
723
- }.pretty
724
- end
725
-
726
- def output_color_table?
727
- true
728
- end
729
-
730
- def default_color
731
- "rgb(240, 240, 245)"
732
- end
733
-
734
- def default_title
735
- "C0 code coverage information"
736
- end
737
-
738
- def format_overview(*file_infos)
739
- table_text = xmlish_ {
740
- table_(:class => "report") {
741
- thead_ {
742
- tr_ {
743
- ["Name", "Total lines", "Lines of code", "Total coverage",
744
- "Code coverage"].each do |heading|
745
- td_(:class => "heading") { heading }
746
- end
747
- }
748
- }
749
- tbody_ {
750
- color_class_index = 1
751
- color_classes = %w[light dark]
752
- file_infos.each do |f|
753
- color_class_index += 1
754
- color_class_index %= color_classes.size
755
- tr_(:class => color_classes[color_class_index]) {
756
- td_ {
757
- case f.name
758
- when "TOTAL":
759
- t_ { "TOTAL" }
760
- else
761
- a_(:href => mangle_filename(f.name)){ t_ { f.name } }
762
- end
763
- }
764
- [[f.num_lines, "lines_total"],
765
- [f.num_code_lines, "lines_code"]].each do |value, css_class|
766
- td_(:class => css_class) { tt_{ value } }
767
- end
768
- [[f.total_coverage, "coverage_total"],
769
- [f.code_coverage, "coverage_code"]].each do |value, css_class|
770
- value *= 100
771
- td_ {
772
- table_(:cellpadding => "0", :cellspacing => "0", :align => "right") {
773
- tr_ {
774
- td_ {
775
- tt_(:class => css_class) { "%3.1f%%" % value }
776
- x_ "&nbsp;"
777
- }
778
- ivalue = value.round
779
- td_ {
780
- table_(:class => "percent_graph", :cellpadding => "0",
781
- :cellspacing => "0", :width => "100") {
782
- tr_ {
783
- td_(:class => "covered", :width => ivalue.to_s)
784
- td_(:class => "uncovered", :width => (100-ivalue).to_s)
785
- }
786
- }
787
- }
788
- }
789
- }
790
- }
791
- end
792
- }
793
- end
794
- }
795
- }
796
- }
797
- table_text.pretty
798
- end
799
-
800
- class SummaryFileInfo # :nodoc:
801
- def initialize(obj); @o = obj end
802
- %w[num_lines num_code_lines code_coverage total_coverage].each do |m|
803
- define_method(m){ @o.send(m) }
804
- end
805
- def name; "TOTAL" end
806
- end
807
-
808
- def create_index(destname)
809
- files = [SummaryFileInfo.new(self)] + each_file_pair_sorted.map{|k,v| v}
810
- title = default_title
811
- output = xhtml_ { html_ {
812
- head_ {
813
- if @charset
814
- meta_("http-equiv".to_sym => "Content-Type",
815
- :content => "text/html;charset=#{@charset}")
816
- end
817
- title_{ title }
818
- style_(:type => "text/css") { t_{ "body { background-color: #{default_color}; }" } }
819
- style_(:type => "text/css") { CSS_PROLOG }
820
- script_(:type => "text/javascript") { h_{ JAVASCRIPT_PROLOG } }
821
- }
822
- body_ {
823
- h3_{
824
- t_{ title }
825
- }
826
- p_ {
827
- t_{ "Generated on #{Time.new.to_s} with " }
828
- a_(:href => Rcov::UPSTREAM_URL){ "rcov #{Rcov::VERSION}" }
829
- }
830
- p_ { "Threshold: #{@output_threshold}%" } if @output_threshold != 101
831
- hr_
832
- x_{ format_overview(*files) }
833
- hr_
834
- x_{ blurb }
835
-
836
- if @show_validator_links
837
- p_ {
838
- a_(:href => "http://validator.w3.org/check/referer") {
839
- img_(:src => "http://www.w3.org/Icons/valid-xhtml11",
840
- :alt => "Valid XHTML 1.1!", :height => "31", :width => "88")
841
- }
842
- a_(:href => "http://jigsaw.w3.org/css-validator/check/referer") {
843
- img_(:style => "border:0;width:88px;height:31px",
844
- :src => "http://jigsaw.w3.org/css-validator/images/vcss",
845
- :alt => "Valid CSS!")
846
- }
847
- }
848
- end
849
- }
850
- } }
851
- lines = output.pretty.to_a
852
- lines.unshift lines.pop if /DOCTYPE/ =~ lines[-1]
853
- File.open(destname, "w") do |f|
854
- f.puts lines
855
- end
856
- end
857
-
858
- def format_lines(file)
859
- result = ""
860
- last = nil
861
- end_of_span = ""
862
- format_line = "%#{file.num_lines.to_s.size}d"
863
- file.num_lines.times do |i|
864
- line = file.lines[i].chomp
865
- marked = file.coverage[i]
866
- count = file.counts[i]
867
- spanclass = span_class(file, marked, count)
868
- if spanclass != last
869
- result += end_of_span
870
- case spanclass
871
- when nil
872
- end_of_span = ""
873
- else
874
- result += %[<span class="#{spanclass}">]
875
- end_of_span = "</span>"
876
- end
877
- end
878
- result += %[<a name="line#{i+1}"></a>] + (format_line % (i+1)) +
879
- " " + create_cross_refs(file.name, i+1, CGI.escapeHTML(line)) + "\n"
880
- last = spanclass
881
- end
882
- result += end_of_span
883
- "<pre>#{result}</pre>"
884
- end
885
-
886
- def create_cross_refs(filename, lineno, linetext)
887
- return linetext unless @callsite_analyzer && @do_callsites
888
- ref_blocks = []
889
- _get_defsites(ref_blocks, filename, lineno, "Calls", linetext) do |ref|
890
- if ref.file
891
- where = "at #{normalize_filename(ref.file)}:#{ref.line}"
892
- else
893
- where = "(C extension/core)"
894
- end
895
- CGI.escapeHTML("%7d %s" %
896
- [ref.count, "#{ref.klass}##{ref.mid} " + where])
897
- end
898
- _get_callsites(ref_blocks, filename, lineno, "Called by", linetext) do |ref|
899
- r = "%7d %s" % [ref.count,
900
- "#{normalize_filename(ref.file||'C code')}:#{ref.line} " +
901
- "in '#{ref.klass}##{ref.mid}'"]
902
- CGI.escapeHTML(r)
903
- end
904
-
905
- create_cross_reference_block(linetext, ref_blocks)
906
- end
907
-
908
- def create_cross_reference_block(linetext, ref_blocks)
909
- return linetext if ref_blocks.empty?
910
- ret = ""
911
- @cross_ref_idx ||= 0
912
- @known_files ||= sorted_file_pairs.map{|fname, finfo| normalize_filename(fname)}
913
- ret << %[<a class="crossref-toggle" href="#" onclick="toggleCode('XREF-#{@cross_ref_idx+=1}'); return false;">#{linetext}</a>]
914
- ret << %[<span class="cross-ref" id="XREF-#{@cross_ref_idx}">]
915
- ret << "\n"
916
- ref_blocks.each do |refs, toplabel, label_proc|
917
- unless !toplabel || toplabel.empty?
918
- ret << %!<span class="cross-ref-title">#{toplabel}</span>\n!
919
- end
920
- refs.each do |dst|
921
- dstfile = normalize_filename(dst.file) if dst.file
922
- dstline = dst.line
923
- label = label_proc.call(dst)
924
- if dst.file && @known_files.include?(dstfile)
925
- ret << %[<a href="#{mangle_filename(dstfile)}#line#{dstline}">#{label}</a>]
926
- else
927
- ret << label
928
- end
929
- ret << "\n"
930
- end
931
- end
932
- ret << "</span>"
933
- end
934
-
935
- def span_class(sourceinfo, marked, count)
936
- @span_class_index ^= 1
937
- case marked
938
- when true
939
- "marked#{@span_class_index}"
940
- when :inferred
941
- "inferred#{@span_class_index}"
942
- else
943
- "uncovered#{@span_class_index}"
944
- end
945
- end
946
-
947
- def create_file(destfile, fileinfo)
948
- #$stderr.puts "Generating #{destfile.inspect}"
949
- body = format_overview(fileinfo) + format_lines(fileinfo)
950
- title = fileinfo.name + " - #{default_title}"
951
- do_ctable = output_color_table?
952
- output = xhtml_ { html_ {
953
- head_ {
954
- if @charset
955
- meta_("http-equiv".to_sym => "Content-Type",
956
- :content => "text/html;charset=#{@charset}")
957
- end
958
- title_{ title }
959
- style_(:type => "text/css") { t_{ "body { background-color: #{default_color}; }" } }
960
- style_(:type => "text/css") { CSS_PROLOG }
961
- script_(:type => "text/javascript") { h_ { JAVASCRIPT_PROLOG } }
962
- style_(:type => "text/css") { h_ { colorscale } }
963
- }
964
- body_ {
965
- h3_{ t_{ default_title } }
966
- p_ {
967
- t_{ "Generated on #{Time.new.to_s} with " }
968
- a_(:href => Rcov::UPSTREAM_URL){ "rcov #{Rcov::VERSION}" }
969
- }
970
- hr_
971
- if do_ctable
972
- # this kludge needed to ensure .pretty doesn't mangle it
973
- x_ { <<EOS
974
- <pre><span class='marked0'>Code reported as executed by Ruby looks like this...
975
- </span><span class='marked1'>and this: this line is also marked as covered.
976
- </span><span class='inferred0'>Lines considered as run by rcov, but not reported by Ruby, look like this,
977
- </span><span class='inferred1'>and this: these lines were inferred by rcov (using simple heuristics).
978
- </span><span class='uncovered0'>Finally, here&apos;s a line marked as not executed.
979
- </span></pre>
980
- EOS
981
- }
982
- end
983
- x_{ body }
984
- hr_
985
- x_ { blurb }
986
-
987
- if @show_validator_links
988
- p_ {
989
- a_(:href => "http://validator.w3.org/check/referer") {
990
- img_(:src => "http://www.w3.org/Icons/valid-xhtml10",
991
- :alt => "Valid XHTML 1.0!", :height => "31", :width => "88")
992
- }
993
- a_(:href => "http://jigsaw.w3.org/css-validator/check/referer") {
994
- img_(:style => "border:0;width:88px;height:31px",
995
- :src => "http://jigsaw.w3.org/css-validator/images/vcss",
996
- :alt => "Valid CSS!")
997
- }
998
- }
999
- end
1000
- }
1001
- } }
1002
- # .pretty needed to make sure DOCTYPE is in a separate line
1003
- lines = output.pretty.to_a
1004
- lines.unshift lines.pop if /DOCTYPE/ =~ lines[-1]
1005
- File.open(destfile, "w") do |f|
1006
- f.puts lines
1007
- end
1008
- end
1009
-
1010
- def colorscale
1011
- colorscalebase =<<EOF
1012
- span.run%d {
1013
- background-color: rgb(%d, %d, %d);
1014
- display: block;
1015
- }
1016
- EOF
1017
- cscale = ""
1018
- 101.times do |i|
1019
- if @color
1020
- r, g, b = hsv2rgb(220-(2.2*i).to_i, 0.3, 1)
1021
- r = (r * 255).to_i
1022
- g = (g * 255).to_i
1023
- b = (b * 255).to_i
1024
- else
1025
- r = g = b = 255 - i
1026
- end
1027
- cscale << colorscalebase % [i, r, g, b]
1028
- end
1029
- cscale
1030
- end
1031
-
1032
- # thanks to kig @ #ruby-lang for this one
1033
- def hsv2rgb(h,s,v)
1034
- return [v,v,v] if s == 0
1035
- h = h/60.0
1036
- i = h.floor
1037
- f = h-i
1038
- p = v * (1-s)
1039
- q = v * (1-s*f)
1040
- t = v * (1-s*(1-f))
1041
- case i
1042
- when 0
1043
- r = v
1044
- g = t
1045
- b = p
1046
- when 1
1047
- r = q
1048
- g = v
1049
- b = p
1050
- when 2
1051
- r = p
1052
- g = v
1053
- b = t
1054
- when 3
1055
- r = p
1056
- g = q
1057
- b = v
1058
- when 4
1059
- r = t
1060
- g = p
1061
- b = v
1062
- when 5
1063
- r = v
1064
- g = p
1065
- b = q
1066
- end
1067
- [r,g,b]
1068
- end
1069
- end
1070
-
1071
- class HTMLProfiling < HTMLCoverage # :nodoc:
1072
-
1073
- DEFAULT_OPTS = {:destdir => "profiling"}
1074
- def initialize(opts = {})
1075
- options = DEFAULT_OPTS.clone.update(opts)
1076
- super(options)
1077
- @max_cache = {}
1078
- @median_cache = {}
1079
- end
1080
-
1081
- def default_title
1082
- "Bogo-profile information"
1083
- end
1084
-
1085
- def default_color
1086
- if @color
1087
- "rgb(179,205,255)"
1088
- else
1089
- "rgb(255, 255, 255)"
1090
- end
1091
- end
1092
-
1093
- def output_color_table?
1094
- false
1095
- end
1096
-
1097
- def span_class(sourceinfo, marked, count)
1098
- full_scale_range = @fsr # dB
1099
- nz_count = sourceinfo.counts.select{|x| x && x != 0}
1100
- nz_count << 1 # avoid div by 0
1101
- max = @max_cache[sourceinfo] ||= nz_count.max
1102
- #avg = @median_cache[sourceinfo] ||= 1.0 *
1103
- # nz_count.inject{|a,b| a+b} / nz_count.size
1104
- median = @median_cache[sourceinfo] ||= 1.0 * nz_count.sort[nz_count.size/2]
1105
- max ||= 2
1106
- max = 2 if max == 1
1107
- if marked == true
1108
- count = 1 if !count || count == 0
1109
- idx = 50 + 1.0 * (500/full_scale_range) * Math.log(count/median) /
1110
- Math.log(10)
1111
- idx = idx.to_i
1112
- idx = 0 if idx < 0
1113
- idx = 100 if idx > 100
1114
- "run#{idx}"
1115
- else
1116
- nil
1117
- end
1118
- end
1119
- end
1120
-
1121
- class RubyAnnotation < Formatter # :nodoc:
1122
- DEFAULT_OPTS = { :destdir => "coverage" }
1123
- def initialize(opts = {})
1124
- options = DEFAULT_OPTS.clone.update(opts)
1125
- super(options)
1126
- @dest = options[:destdir]
1127
- @do_callsites = true
1128
- @do_cross_references = true
1129
-
1130
- @mangle_filename = Hash.new{|h,base|
1131
- h[base] = Pathname.new(base).cleanpath.to_s.gsub(%r{^\w:[/\\]}, "").gsub(/\./, "_").gsub(/[\\\/]/, "-") + ".rb"
1132
- }
1133
- end
1134
-
1135
- def execute
1136
- return if @files.empty?
1137
- FileUtils.mkdir_p @dest
1138
- each_file_pair_sorted do |filename, fileinfo|
1139
- create_file(File.join(@dest, mangle_filename(filename)), fileinfo)
1140
- end
1141
- end
1142
-
1143
- private
1144
-
1145
- def format_lines(file)
1146
- result = ""
1147
- format_line = "%#{file.num_lines.to_s.size}d"
1148
- file.num_lines.times do |i|
1149
- line = file.lines[i].chomp
1150
- marked = file.coverage[i]
1151
- count = file.counts[i]
1152
- result << create_cross_refs(file.name, i+1, line, marked) + "\n"
1153
- end
1154
- result
1155
- end
1156
-
1157
- def create_cross_refs(filename, lineno, linetext, marked)
1158
- return linetext unless @callsite_analyzer && @do_callsites
1159
- ref_blocks = []
1160
- _get_defsites(ref_blocks, filename, lineno, linetext, ">>") do |ref|
1161
- if ref.file
1162
- ref.file.sub!(%r!^./!, '')
1163
- where = "at #{mangle_filename(ref.file)}:#{ref.line}"
1164
- else
1165
- where = "(C extension/core)"
1166
- end
1167
- "#{ref.klass}##{ref.mid} " + where + ""
1168
- end
1169
- _get_callsites(ref_blocks, filename, lineno, linetext, "<<") do |ref| # "
1170
- ref.file.sub!(%r!^./!, '')
1171
- "#{mangle_filename(ref.file||'C code')}:#{ref.line} " +
1172
- "in #{ref.klass}##{ref.mid}"
1173
- end
1174
-
1175
- create_cross_reference_block(linetext, ref_blocks, marked)
1176
- end
1177
-
1178
- def create_cross_reference_block(linetext, ref_blocks, marked)
1179
- codelen = 75
1180
- if ref_blocks.empty?
1181
- if marked
1182
- return "%-#{codelen}s #o" % linetext
1183
- else
1184
- return linetext
1185
- end
1186
- end
1187
- ret = ""
1188
- @cross_ref_idx ||= 0
1189
- @known_files ||= sorted_file_pairs.map{|fname, finfo| normalize_filename(fname)}
1190
- ret << "%-#{codelen}s # " % linetext
1191
- ref_blocks.each do |refs, toplabel, label_proc|
1192
- unless !toplabel || toplabel.empty?
1193
- ret << toplabel << " "
1194
- end
1195
- refs.each do |dst|
1196
- dstfile = normalize_filename(dst.file) if dst.file
1197
- dstline = dst.line
1198
- label = label_proc.call(dst)
1199
- if dst.file && @known_files.include?(dstfile)
1200
- ret << "[[" << label << "]], "
1201
- else
1202
- ret << label << ", "
1203
- end
1204
- end
1205
- end
1206
-
1207
- ret
1208
- end
1209
-
1210
- def create_file(destfile, fileinfo)
1211
- #$stderr.puts "Generating #{destfile.inspect}"
1212
- body = format_lines(fileinfo)
1213
- File.open(destfile, "w") do |f|
1214
- f.puts body
1215
- f.puts footer(fileinfo)
1216
- end
1217
- end
1218
-
1219
- def footer(fileinfo)
1220
- s = "# Total lines : %d\n" % fileinfo.num_lines
1221
- s << "# Lines of code : %d\n" % fileinfo.num_code_lines
1222
- s << "# Total coverage : %3.1f%%\n" % [ fileinfo.total_coverage*100 ]
1223
- s << "# Code coverage : %3.1f%%\n\n" % [ fileinfo.code_coverage*100 ]
1224
- # prevents false positives on Emacs
1225
- s << "# Local " "Variables:\n" "# mode: " "rcov-xref\n" "# End:\n"
1226
- end
1227
- end
1228
-
1229
-
1230
- end # Rcov
1231
-
1232
- # vi: set sw=4:
1233
- # Here is Emacs setting. DO NOT REMOVE!
1234
- # Local Variables:
1235
- # ruby-indent-level: 4
1236
- # End: