rcov 0.8.1.2.0 → 0.9.3

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