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.
- data/BLURB +35 -1
- data/CHANGES +28 -0
- data/README.en +2 -0
- data/README.vim +47 -0
- data/Rakefile +6 -5
- data/Rantfile +2 -1
- data/THANKS +14 -0
- data/bin/rcov +124 -743
- data/ext/rcovrt/extconf.rb +9 -1
- data/ext/rcovrt/rcov.c +115 -32
- data/lib/rcov.rb +67 -42
- data/lib/rcov/lowlevel.rb +9 -1
- data/lib/rcov/rant.rb +5 -3
- data/lib/rcov/rcovtask.rb +3 -3
- data/lib/rcov/report.rb +1005 -0
- data/lib/rcov/version.rb +3 -3
- data/test/test_CallSiteAnalyzer.rb +41 -18
- data/test/test_CodeCoverageAnalyzer.rb +2 -2
- data/test/test_FileStatistics.rb +72 -2
- metadata +6 -2
data/ext/rcovrt/extconf.rb
CHANGED
@@ -1,3 +1,11 @@
|
|
1
1
|
require 'mkmf'
|
2
2
|
|
3
|
-
|
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
|
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 =
|
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,
|
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(
|
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(
|
212
|
-
|
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
|
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
|
112
|
-
|
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
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
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
|
-
|
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
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
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(
|
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(:
|
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
|
-
|
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
|
-
|
757
|
-
|
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
|
-
|
763
|
-
|
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
|
-
|
770
|
-
|
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
|
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
|
-
|
830
|
-
return nil unless
|
831
|
-
DefSite.new(
|
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 #
|
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
|
|