rcov 0.5.0.1-mswin32 → 0.6.0.1-mswin32

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