rcov 0.5.0.1 → 0.6.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,11 @@
1
1
  require 'mkmf'
2
2
 
3
- create_makefile("rcovrt")
3
+ dir_config("gcov")
4
+ if ENV["USE_GCOV"] and Config::CONFIG['CC'] =~ /gcc/ and
5
+ have_library("gcov", "__gcov_open")
6
+
7
+ $CFLAGS << " -fprofile-arcs -ftest-coverage"
8
+ create_makefile("rcovrt")
9
+ else
10
+ create_makefile("rcovrt")
11
+ end
data/ext/rcovrt/rcov.c CHANGED
@@ -1,10 +1,11 @@
1
1
 
2
2
  #include <ruby.h>
3
+ #include <env.h>
3
4
  #include <node.h>
4
5
  #include <st.h>
5
6
  #include <stdlib.h>
6
7
 
7
- #define RCOVRT_VERSION_MAJOR 1
8
+ #define RCOVRT_VERSION_MAJOR 2
8
9
  #define RCOVRT_VERSION_MINOR 0
9
10
  #define RCOVRT_VERSION_REV 0
10
11
 
@@ -12,7 +13,6 @@ static VALUE mRcov;
12
13
  static VALUE mRCOV__;
13
14
  static VALUE oSCRIPT_LINES__;
14
15
  static ID id_cover;
15
- static VALUE id_caller = 0;
16
16
  static st_table* coverinfo = 0;
17
17
  static char coverage_hook_set_p;
18
18
  static char callsite_hook_set_p;
@@ -94,22 +94,61 @@ record_method_def_site(VALUE args)
94
94
  return Qnil;
95
95
  }
96
96
 
97
+ static VALUE
98
+ callsite_custom_backtrace(int lev)
99
+ {
100
+ struct FRAME *frame = ruby_frame;
101
+ VALUE ary;
102
+ NODE *n;
103
+ VALUE level;
104
+ VALUE klass;
105
+
106
+ ary = rb_ary_new();
107
+ if (frame->last_func == ID_ALLOCATOR) {
108
+ frame = frame->prev;
109
+ }
110
+ for (; frame && (n = frame->node); frame = frame->prev) {
111
+ if (frame->prev && frame->prev->last_func) {
112
+ if (frame->prev->node == n) continue;
113
+ level = rb_ary_new();
114
+ klass = frame->prev->last_class ? frame->prev->last_class : Qnil;
115
+ if(TYPE(klass) == T_ICLASS) {
116
+ klass = CLASS_OF(klass);
117
+ }
118
+ rb_ary_push(level, klass);
119
+ rb_ary_push(level, ID2SYM(frame->prev->last_func));
120
+ rb_ary_push(level, rb_str_new2(n->nd_file));
121
+ rb_ary_push(level, INT2NUM(nd_line(n)));
122
+ }
123
+ else {
124
+ level = rb_ary_new();
125
+ rb_ary_push(level, Qnil);
126
+ rb_ary_push(level, Qnil);
127
+ rb_ary_push(level, rb_str_new2(n->nd_file));
128
+ rb_ary_push(level, INT2NUM(nd_line(n)));
129
+ }
130
+ rb_ary_push(ary, level);
131
+ if(--lev == 0)
132
+ break;
133
+ }
134
+
135
+ return ary;
136
+ }
97
137
 
98
138
  static void
99
139
  coverage_event_callsite_hook(rb_event_t event, NODE *node, VALUE self,
100
140
  ID mid, VALUE klass)
101
141
  {
102
142
  VALUE caller_ary;
103
- VALUE aref_args[2];
104
143
  VALUE curr_meth;
105
144
  VALUE args[2];
106
145
  int status;
107
146
 
108
- caller_ary = rb_funcall(rb_mKernel, id_caller, 0);
109
- aref_args[0] = INT2FIX(0);
110
- aref_args[1] = INT2NUM(caller_stack_len);
111
- caller_ary = rb_ary_aref(2, aref_args, caller_ary);
147
+ caller_ary = callsite_custom_backtrace(caller_stack_len);
112
148
 
149
+ if(TYPE(klass) == T_ICLASS) {
150
+ klass = CLASS_OF(klass);
151
+ }
113
152
  curr_meth = rb_ary_new();
114
153
  rb_ary_push(curr_meth, klass);
115
154
  rb_ary_push(curr_meth, ID2SYM(mid));
@@ -123,7 +162,7 @@ coverage_event_callsite_hook(rb_event_t event, NODE *node, VALUE self,
123
162
  args.sourcefile = node->nd_file;
124
163
  args.sourceline = nd_line(node) - 1;
125
164
  args.curr_meth = curr_meth;
126
- rb_protect(record_method_def_site, (VALUE)&args, 0);
165
+ rb_protect(record_method_def_site, (VALUE)&args, NULL);
127
166
  }
128
167
  if(status)
129
168
  rb_gv_set("$!", Qnil);
@@ -191,6 +230,70 @@ cov_reset_callsite(VALUE self)
191
230
  *
192
231
  * */
193
232
 
233
+ static struct cov_array *
234
+ coverage_increase_counter_uncached(char *sourcefile, int sourceline,
235
+ char mark_only)
236
+ {
237
+ struct cov_array *carray;
238
+
239
+ if(!st_lookup(coverinfo, (st_data_t)sourcefile, (st_data_t*)&carray)) {
240
+ VALUE arr;
241
+
242
+ arr = rb_hash_aref(oSCRIPT_LINES__, rb_str_new2(sourcefile));
243
+ if(NIL_P(arr))
244
+ return 0;
245
+ rb_check_type(arr, T_ARRAY);
246
+ carray = calloc(1, sizeof(struct cov_array));
247
+ carray->ptr = calloc(RARRAY(arr)->len, sizeof(unsigned int));
248
+ carray->len = RARRAY(arr)->len;
249
+ st_insert(coverinfo, (st_data_t)strdup(sourcefile),
250
+ (st_data_t) carray);
251
+ }
252
+ if(mark_only) {
253
+ if(!carray->ptr[sourceline])
254
+ carray->ptr[sourceline] = 1;
255
+ } else {
256
+ carray->ptr[sourceline]++;
257
+ }
258
+
259
+ return carray;
260
+ }
261
+
262
+
263
+ static void
264
+ coverage_mark_caller()
265
+ {
266
+ struct FRAME *frame = ruby_frame;
267
+ NODE *n;
268
+
269
+ if (frame->last_func == ID_ALLOCATOR) {
270
+ frame = frame->prev;
271
+ }
272
+ for (; frame && (n = frame->node); frame = frame->prev) {
273
+ if (frame->prev && frame->prev->last_func) {
274
+ if (frame->prev->node == n) continue;
275
+ coverage_increase_counter_uncached(n->nd_file, nd_line(n), 1);
276
+ }
277
+ else {
278
+ coverage_increase_counter_uncached(n->nd_file, nd_line(n), 1);
279
+ }
280
+ break;
281
+ }
282
+ }
283
+
284
+
285
+ static void
286
+ coverage_increase_counter_cached(char *sourcefile, int sourceline)
287
+ {
288
+ if(cached_file == sourcefile && cached_array) {
289
+ cached_array->ptr[sourceline]++;
290
+ return;
291
+ }
292
+ cached_file = sourcefile;
293
+ cached_array = coverage_increase_counter_uncached(sourcefile, sourceline, 0);
294
+ }
295
+
296
+
194
297
  static void
195
298
  coverage_event_coverage_hook(rb_event_t event, NODE *node, VALUE self,
196
299
  ID mid, VALUE klass)
@@ -201,34 +304,15 @@ coverage_event_coverage_hook(rb_event_t event, NODE *node, VALUE self,
201
304
  if(event & (RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN | RUBY_EVENT_CLASS))
202
305
  return;
203
306
 
204
- if(!node)
307
+ if(node == NULL)
205
308
  return;
206
309
 
207
310
  sourcefile = node->nd_file;
208
311
  sourceline = nd_line(node) - 1;
209
312
 
210
-
211
- if(cached_file == sourcefile && cached_array) {
212
- cached_array->ptr[sourceline]++;
213
- return;
214
- }
215
-
216
-
217
- if(!st_lookup(coverinfo, (st_data_t)sourcefile, (st_data_t*)&cached_array)) {
218
- VALUE arr;
219
-
220
- arr = rb_hash_aref(oSCRIPT_LINES__, rb_str_new2(sourcefile));
221
- if(NIL_P(arr))
222
- return;
223
- rb_check_type(arr, T_ARRAY);
224
- cached_array = calloc(1, sizeof(struct cov_array));
225
- cached_array->ptr = calloc(RARRAY(arr)->len, sizeof(unsigned int));
226
- cached_array->len = RARRAY(arr)->len;
227
- st_insert(coverinfo, (st_data_t)strdup(sourcefile),
228
- (st_data_t) cached_array);
229
- }
230
- cached_file = sourcefile;
231
- cached_array->ptr[sourceline]++;
313
+ coverage_increase_counter_cached(sourcefile, sourceline);
314
+ if(event == RUBY_EVENT_CALL)
315
+ coverage_mark_caller();
232
316
  }
233
317
 
234
318
 
@@ -359,7 +443,6 @@ Init_rcovrt()
359
443
  ID id_script_lines__ = rb_intern("SCRIPT_LINES__");
360
444
 
361
445
  id_cover = rb_intern("COVER");
362
- id_caller = rb_intern("caller");
363
446
 
364
447
  if(rb_const_defined(rb_cObject, id_rcov))
365
448
  mRcov = rb_const_get(rb_cObject, id_rcov);
data/lib/rcov.rb CHANGED
@@ -79,7 +79,7 @@ end
79
79
  # counts would normally be obtained from a Rcov::CodeCoverageAnalyzer.
80
80
  class FileStatistics
81
81
  attr_reader :name, :lines, :coverage, :counts
82
- def initialize(name, lines, counts)
82
+ def initialize(name, lines, counts, comments_run_by_default = false)
83
83
  @name = name
84
84
  @lines = lines
85
85
  initial_coverage = counts.map{|x| (x || 0) > 0 ? true : false }
@@ -92,7 +92,7 @@ class FileStatistics
92
92
  @multiline_string_start = Array.new(lines.size, false)
93
93
  extend_heredocs
94
94
  find_multiline_strings
95
- precompute_coverage false
95
+ precompute_coverage comments_run_by_default
96
96
  end
97
97
 
98
98
  # Merge code coverage and execution count information.
@@ -108,8 +108,10 @@ class FileStatistics
108
108
  def merge(lines, coverage, counts)
109
109
  coverage.each_with_index do |v, idx|
110
110
  case @coverage[idx]
111
- when :inferred : @coverage[idx] = v || @coverage[idx]
112
- when false : @coverage[idx] ||= v
111
+ when :inferred
112
+ @coverage[idx] = v || @coverage[idx]
113
+ when false
114
+ @coverage[idx] ||= v
113
115
  end
114
116
  end
115
117
  counts.each_with_index{|v, idx| @counts[idx] += v }
@@ -207,12 +209,23 @@ class FileStatistics
207
209
 
208
210
  def precompute_coverage(comments_run_by_default = true)
209
211
  changed = false
212
+ lastidx = lines.size - 1
213
+ if (!is_code?(lastidx) || /^__END__$/ =~ @lines[-1]) && !@coverage[lastidx]
214
+ # mark the last block of comments
215
+ @coverage[lastidx] ||= :inferred
216
+ (lastidx-1).downto(0) do |i|
217
+ break if is_code?(i)
218
+ @coverage[i] ||= :inferred
219
+ end
220
+ end
210
221
  (0...lines.size).each do |i|
211
222
  next if @coverage[i]
212
223
  line = @lines[i]
213
224
  if /^\s*(begin|ensure|else|case)\s*(?:#.*)?$/ =~ line && next_expr_marked?(i) or
214
225
  /^\s*(?:end|\})\s*(?:#.*)?$/ =~ line && prev_expr_marked?(i) or
226
+ /^\s*(?:end\b|\})/ =~ line && prev_expr_marked?(i) && next_expr_marked?(i) or
215
227
  /^\s*rescue\b/ =~ line && next_expr_marked?(i) or
228
+ /(do|\{)\s*(\|[^|]*\|\s*)?(?:#.*)?$/ =~ line && next_expr_marked?(i) or
216
229
  prev_expr_continued?(i) && prev_expr_marked?(i) or
217
230
  comments_run_by_default && !is_code?(i) or
218
231
  /^\s*((\)|\]|\})\s*)+(?:#.*)?$/ =~ line && prev_expr_marked?(i) or
@@ -253,6 +266,8 @@ class FileStatistics
253
266
  loop do
254
267
  scanned_text = scanner.search_full(/<<(-?)(?:(['"`])((?:(?!\2).)+)\2|([A-Z_a-z]\w*))/,
255
268
  true, true)
269
+ # k is the first line after the end delimiter for the last heredoc
270
+ # scanned so far
256
271
  unless scanner.matched?
257
272
  i = k
258
273
  break
@@ -265,23 +280,25 @@ class FileStatistics
265
280
  i = k
266
281
  break
267
282
  end
268
- if @coverage[i]
269
- must_mark = []
270
- end_of_heredoc = (scanner[1] == "-") ? /^\s*#{Regexp.escape(term)}$/ :
271
- /^#{Regexp.escape(term)}$/
272
- loop do
273
- break if j == @lines.size
274
- must_mark << j
275
- if end_of_heredoc =~ @lines[j]
276
- must_mark.each do |n|
277
- @heredoc_start[n] = i
278
- @coverage[n] ||= :inferred
279
- end
280
- k = (j += 1)
281
- break
283
+ must_mark = []
284
+ end_of_heredoc = (scanner[1] == "-") ?
285
+ /^\s*#{Regexp.escape(term)}$/ : /^#{Regexp.escape(term)}$/
286
+ loop do
287
+ break if j == @lines.size
288
+ must_mark << j
289
+ if end_of_heredoc =~ @lines[j]
290
+ must_mark.each do |n|
291
+ @heredoc_start[n] = i
282
292
  end
283
- j += 1
293
+ if (must_mark + [i]).any?{|lineidx| @coverage[lineidx]}
294
+ @coverage[i] ||= :inferred
295
+ must_mark.each{|lineidx| @coverage[lineidx] ||= :inferred}
284
296
  end
297
+ # move the "first line after heredocs" index
298
+ k = (j += 1)
299
+ break
300
+ end
301
+ j += 1
285
302
  end
286
303
  end
287
304
 
@@ -644,19 +661,15 @@ class CodeCoverageAnalyzer < DifferentialAnalyzer
644
661
 
645
662
 
646
663
  def refine_coverage_info(lines, covers)
647
- line_info = []
648
664
  marked_info = []
649
665
  count_info = []
650
- 0.upto(lines.size - 1) do |c|
651
- line = lines[c].chomp
652
- marked = false
653
- marked = true if covers[c] && covers[c] > 0
654
- line_info << line
655
- marked_info << marked
656
- count_info << (covers[c] || 0)
666
+ lines.size.times do |i|
667
+ c = covers[i]
668
+ marked_info << ((c && c > 0) ? true : false)
669
+ count_info << (c || 0)
657
670
  end
658
671
 
659
- script_lines_workaround(line_info, marked_info, count_info)
672
+ script_lines_workaround(lines, marked_info, count_info)
660
673
  end
661
674
 
662
675
  # Try to detect repeated data, based on observed repetitions in line_info:
@@ -744,30 +757,40 @@ class CallSiteAnalyzer < DifferentialAnalyzer
744
757
  # Object representing a method call site.
745
758
  # It corresponds to a part of the callstack starting from the context that
746
759
  # called the method.
747
- class CallSite < Struct.new(:description)
760
+ class CallSite < Struct.new(:backtrace)
748
761
  # The depth of a CallSite is the number of stack frames
749
762
  # whose information is included in the CallSite object.
750
763
  def depth
751
- description.size
764
+ backtrace.size
752
765
  end
753
766
 
754
767
  # File where the method call originated.
768
+ # Might return +nil+ or "" if it is not meaningful (C extensions, etc).
755
769
  def file(level = 0)
756
- desc = description[level]
757
- desc ? desc[/[^:]*/] : nil
770
+ stack_frame = backtrace[level]
771
+ stack_frame ? stack_frame[2] : nil
758
772
  end
759
773
 
760
774
  # Line where the method call originated.
775
+ # Might return +nil+ or 0 if it is not meaningful (C extensions, etc).
761
776
  def line(level = 0)
762
- desc = description[level]
763
- desc ? desc[/:(\d+)/, 1].to_i : nil
777
+ stack_frame = backtrace[level]
778
+ stack_frame ? stack_frame[3] : nil
764
779
  end
765
780
 
766
781
  # Name of the method where the call originated.
767
782
  # Returns +nil+ if the call originated in +toplevel+.
783
+ # Might return +nil+ if it could not be determined.
768
784
  def calling_method(level = 0)
769
- desc = description[level]
770
- desc ? desc[/:in `(.*)'/, 1] : nil
785
+ stack_frame = backtrace[level]
786
+ stack_frame ? stack_frame[1] : nil
787
+ end
788
+
789
+ # Name of the class holding the method where the call originated.
790
+ # Might return +nil+ if it could not be determined.
791
+ def calling_class(level = 0)
792
+ stack_frame = backtrace[level]
793
+ stack_frame ? stack_frame[0] : nil
771
794
  end
772
795
  end
773
796
 
@@ -814,7 +837,9 @@ class CallSiteAnalyzer < DifferentialAnalyzer
814
837
  return nil unless rawsites
815
838
  ret = {}
816
839
  # could be a job for inject but it's slow and I don't mind the extra loc
817
- rawsites.each_pair{|csite_info, count| ret[CallSite.new(csite_info)] = count }
840
+ rawsites.each_pair do |backtrace, count|
841
+ ret[CallSite.new(backtrace)] = count
842
+ end
818
843
  ret
819
844
  end
820
845
 
@@ -826,9 +851,9 @@ class CallSiteAnalyzer < DifferentialAnalyzer
826
851
  # analyzer.defsite("Foo", "f1")
827
852
  # analyzer.defsite("#<class:Foo>", "g1")
828
853
  def defsite(classname_or_fullname, methodname = nil)
829
- raw = raw_data_relative[1][expand_name(classname_or_fullname, methodname)]
830
- return nil unless raw
831
- DefSite.new(*raw)
854
+ file, line = raw_data_relative[1][expand_name(classname_or_fullname, methodname)]
855
+ return nil unless file && line
856
+ DefSite.new(file, line)
832
857
  end
833
858
 
834
859
  private
@@ -857,9 +882,9 @@ class CallSiteAnalyzer < DifferentialAnalyzer
857
882
  raw.each_pair do |(klass, method), hash|
858
883
  begin
859
884
  key = [klass.to_s, method.to_s]
860
- ret1[key] = hash.clone # no deep cloning needed
885
+ ret1[key] = hash.clone #Marshal.load(Marshal.dump(hash))
861
886
  ret2[key] = method_def_site[[klass, method]]
862
- rescue Exception
887
+ #rescue Exception
863
888
  end
864
889
  end
865
890
 
data/lib/rcov/lowlevel.rb CHANGED
@@ -84,7 +84,7 @@ One Click Installer and mswin32 builds) at http://eigenclass.org/hiki.rb?rcov .
84
84
  DEFSITES[[klass.to_s, id.to_s]] = [file, line]
85
85
  rescue Exception
86
86
  end
87
- caller_arr = caller[1,1]
87
+ caller_arr = self.format_backtrace_array(caller[1,1])
88
88
  begin
89
89
  hash = CALLSITES[[klass.to_s, id.to_s]] ||= {}
90
90
  hash[caller_arr] ||= 0
@@ -131,6 +131,14 @@ One Click Installer and mswin32 builds) at http://eigenclass.org/hiki.rb?rcov .
131
131
  def self.generate_callsite_info # :nodoc:
132
132
  [CALLSITES, DEFSITES]
133
133
  end
134
+
135
+ def self.format_backtrace_array(backtrace)
136
+ backtrace.map do |line|
137
+ md = /^([^:]*)(?::(\d+)(?::in `(.*)'))?/.match(line)
138
+ raise "Bad backtrace format" unless md
139
+ [nil, md[3] ? md[3].to_sym : nil, md[1], (md[2] || '').to_i]
140
+ end
141
+ end
134
142
  end
135
143
  end # RCOV__
136
144