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.
- 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/lib/rcovrt.so +0 -0
- 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
|
|