gigpark-rcov 0.8.6
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 +111 -0
- data/LICENSE +53 -0
- data/Rakefile +78 -0
- data/THANKS +96 -0
- data/bin/rcov +508 -0
- data/doc/readme_for_api.markdown +22 -0
- data/doc/readme_for_emacs.markdown +52 -0
- data/doc/readme_for_rake.markdown +51 -0
- data/doc/readme_for_vim.markdown +34 -0
- data/editor-extensions/rcov.el +131 -0
- data/editor-extensions/rcov.vim +38 -0
- data/ext/rcovrt/1.8/callsite.c +216 -0
- data/ext/rcovrt/1.8/rcovrt.c +287 -0
- data/ext/rcovrt/1.9/callsite.c +234 -0
- data/ext/rcovrt/1.9/rcovrt.c +264 -0
- data/ext/rcovrt/extconf.rb +21 -0
- data/lib/rcov.rb +33 -0
- data/lib/rcov/call_site_analyzer.rb +225 -0
- data/lib/rcov/code_coverage_analyzer.rb +268 -0
- data/lib/rcov/coverage_info.rb +36 -0
- data/lib/rcov/differential_analyzer.rb +116 -0
- data/lib/rcov/file_statistics.rb +334 -0
- data/lib/rcov/formatters.rb +13 -0
- data/lib/rcov/formatters/base_formatter.rb +168 -0
- data/lib/rcov/formatters/failure_report.rb +15 -0
- data/lib/rcov/formatters/full_text_report.rb +48 -0
- data/lib/rcov/formatters/html_coverage.rb +244 -0
- data/lib/rcov/formatters/html_erb_template.rb +45 -0
- data/lib/rcov/formatters/text_coverage_diff.rb +193 -0
- data/lib/rcov/formatters/text_report.rb +32 -0
- data/lib/rcov/formatters/text_summary.rb +11 -0
- data/lib/rcov/lowlevel.rb +146 -0
- data/lib/rcov/rcovtask.rb +155 -0
- data/lib/rcov/templates/detail.html.erb +78 -0
- data/lib/rcov/templates/index.html.erb +76 -0
- data/lib/rcov/templates/screen.css +168 -0
- data/lib/rcov/version.rb +10 -0
- data/setup.rb +1588 -0
- data/test/assets/sample_01.rb +7 -0
- data/test/assets/sample_02.rb +5 -0
- data/test/assets/sample_03.rb +20 -0
- data/test/assets/sample_04.rb +10 -0
- data/test/assets/sample_05-new.rb +17 -0
- data/test/assets/sample_05-old.rb +13 -0
- data/test/assets/sample_05.rb +17 -0
- data/test/call_site_analyzer_test.rb +171 -0
- data/test/code_coverage_analyzer_test.rb +184 -0
- data/test/file_statistics_test.rb +471 -0
- data/test/functional_test.rb +89 -0
- data/test/turn_off_rcovrt.rb +4 -0
- metadata +109 -0
@@ -0,0 +1,264 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include <ruby/st.h>
|
3
|
+
#include <stdlib.h>
|
4
|
+
#include <assert.h>
|
5
|
+
|
6
|
+
#define COVERAGE_DEBUG_EVENTS 0
|
7
|
+
|
8
|
+
#define RCOVRT_VERSION_MAJOR 2
|
9
|
+
#define RCOVRT_VERSION_MINOR 0
|
10
|
+
#define RCOVRT_VERSION_REV 0
|
11
|
+
|
12
|
+
static VALUE mRcov;
|
13
|
+
static VALUE mRCOV__;
|
14
|
+
static VALUE oSCRIPT_LINES__;
|
15
|
+
static ID id_cover;
|
16
|
+
static st_table* coverinfo = 0;
|
17
|
+
static char coverage_hook_set_p;
|
18
|
+
|
19
|
+
struct cov_array {
|
20
|
+
unsigned int len;
|
21
|
+
unsigned int *ptr;
|
22
|
+
};
|
23
|
+
|
24
|
+
static struct cov_array *cached_array = 0;
|
25
|
+
static char *cached_file = 0;
|
26
|
+
|
27
|
+
static struct cov_array * coverage_increase_counter_uncached(char *sourcefile, unsigned int sourceline, char mark_only) {
|
28
|
+
struct cov_array *carray = NULL;
|
29
|
+
|
30
|
+
if(sourcefile == NULL) {
|
31
|
+
/* "can't happen", just ignore and avoid segfault */
|
32
|
+
return NULL;
|
33
|
+
}
|
34
|
+
else if(!st_lookup(coverinfo, (st_data_t)sourcefile, (st_data_t*)&carray)) {
|
35
|
+
VALUE arr;
|
36
|
+
|
37
|
+
arr = rb_hash_aref(oSCRIPT_LINES__, rb_str_new2(sourcefile));
|
38
|
+
if(NIL_P(arr))
|
39
|
+
return 0;
|
40
|
+
rb_check_type(arr, T_ARRAY);
|
41
|
+
carray = calloc(1, sizeof(struct cov_array));
|
42
|
+
carray->ptr = calloc(RARRAY_LEN(arr), sizeof(unsigned int));
|
43
|
+
carray->len = RARRAY_LEN(arr);
|
44
|
+
st_insert(coverinfo, (st_data_t)strdup(sourcefile), (st_data_t) carray);
|
45
|
+
}
|
46
|
+
else {
|
47
|
+
/* recovered carray, sanity check */
|
48
|
+
assert(carray && "failed to create valid carray");
|
49
|
+
}
|
50
|
+
|
51
|
+
if(mark_only) {
|
52
|
+
if(!carray->ptr[sourceline])
|
53
|
+
carray->ptr[sourceline] = 1;
|
54
|
+
}
|
55
|
+
else {
|
56
|
+
if (carray && carray->len > sourceline) {
|
57
|
+
carray->ptr[sourceline]++;
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
return carray;
|
62
|
+
}
|
63
|
+
|
64
|
+
static void coverage_mark_caller() {
|
65
|
+
coverage_increase_counter_uncached(rb_sourcefile(), rb_sourceline(), 1);
|
66
|
+
}
|
67
|
+
|
68
|
+
static void coverage_increase_counter_cached(char *sourcefile, int sourceline) {
|
69
|
+
if(cached_file == sourcefile && cached_array && cached_array->len > sourceline) {
|
70
|
+
cached_array->ptr[sourceline]++;
|
71
|
+
return;
|
72
|
+
}
|
73
|
+
cached_file = sourcefile;
|
74
|
+
cached_array = coverage_increase_counter_uncached(sourcefile, sourceline, 0);
|
75
|
+
}
|
76
|
+
|
77
|
+
static void coverage_event_coverage_hook(rb_event_flag_t event, VALUE node, VALUE self, ID mid, VALUE klass) {
|
78
|
+
char *sourcefile;
|
79
|
+
unsigned int sourceline;
|
80
|
+
static unsigned int in_hook = 0;
|
81
|
+
|
82
|
+
if(in_hook) {
|
83
|
+
return;
|
84
|
+
}
|
85
|
+
|
86
|
+
in_hook++;
|
87
|
+
|
88
|
+
#if COVERAGE_DEBUG_EVENTS
|
89
|
+
do {
|
90
|
+
int status;
|
91
|
+
VALUE old_exception;
|
92
|
+
old_exception = rb_gv_get("$!");
|
93
|
+
rb_protect(rb_inspect, klass, &status);
|
94
|
+
if(!status) {
|
95
|
+
printf("EVENT: %d %s %s %s %d\n", event,
|
96
|
+
klass ? RSTRING(rb_inspect(klass))->ptr : "",
|
97
|
+
mid ? (mid == ID_ALLOCATOR ? "ID_ALLOCATOR" : rb_id2name(mid))
|
98
|
+
: "unknown",
|
99
|
+
node ? node->nd_file : "", node ? nd_line(node) : 0);
|
100
|
+
}
|
101
|
+
else {
|
102
|
+
printf("EVENT: %d %s %s %d\n", event,
|
103
|
+
mid ? (mid == ID_ALLOCATOR ? "ID_ALLOCATOR" : rb_id2name(mid))
|
104
|
+
: "unknown",
|
105
|
+
node ? node->nd_file : "", node ? nd_line(node) : 0);
|
106
|
+
}
|
107
|
+
rb_gv_set("$!", old_exception);
|
108
|
+
} while (0);
|
109
|
+
#endif
|
110
|
+
|
111
|
+
if(event & RUBY_EVENT_C_CALL) {
|
112
|
+
coverage_mark_caller();
|
113
|
+
}
|
114
|
+
if(event & (RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN | RUBY_EVENT_CLASS)) {
|
115
|
+
in_hook--;
|
116
|
+
return;
|
117
|
+
}
|
118
|
+
|
119
|
+
sourcefile = rb_sourcefile();
|
120
|
+
sourceline = rb_sourceline();
|
121
|
+
|
122
|
+
if (0 == sourceline || 0 == sourcefile) {
|
123
|
+
in_hook--;
|
124
|
+
return;
|
125
|
+
}
|
126
|
+
|
127
|
+
coverage_increase_counter_cached(sourcefile, sourceline);
|
128
|
+
if(event & RUBY_EVENT_CALL)
|
129
|
+
coverage_mark_caller();
|
130
|
+
in_hook--;
|
131
|
+
}
|
132
|
+
|
133
|
+
static VALUE cov_install_coverage_hook(VALUE self) {
|
134
|
+
if(!coverage_hook_set_p) {
|
135
|
+
if(!coverinfo)
|
136
|
+
coverinfo = st_init_strtable();
|
137
|
+
coverage_hook_set_p = 1;
|
138
|
+
/* TODO: allow C_CALL too, since it's supported already
|
139
|
+
* the overhead is around ~30%, tested on typo */
|
140
|
+
VALUE holder = 0;
|
141
|
+
rb_add_event_hook(coverage_event_coverage_hook,
|
142
|
+
RUBY_EVENT_ALL & ~RUBY_EVENT_C_CALL &
|
143
|
+
~RUBY_EVENT_C_RETURN & ~RUBY_EVENT_CLASS, holder);
|
144
|
+
return Qtrue;
|
145
|
+
}
|
146
|
+
else
|
147
|
+
return Qfalse;
|
148
|
+
}
|
149
|
+
|
150
|
+
static int populate_cover(st_data_t key, st_data_t value, st_data_t cover) {
|
151
|
+
VALUE rcover;
|
152
|
+
VALUE rkey;
|
153
|
+
VALUE rval;
|
154
|
+
struct cov_array *carray;
|
155
|
+
unsigned int i;
|
156
|
+
|
157
|
+
rcover = (VALUE)cover;
|
158
|
+
carray = (struct cov_array *) value;
|
159
|
+
rkey = rb_str_new2((char*) key);
|
160
|
+
rval = rb_ary_new2(carray->len);
|
161
|
+
for(i = 0; i < carray->len; i++)
|
162
|
+
rb_ary_push(rval, UINT2NUM(carray->ptr[i]));
|
163
|
+
|
164
|
+
rb_hash_aset(rcover, rkey, rval);
|
165
|
+
|
166
|
+
return ST_CONTINUE;
|
167
|
+
}
|
168
|
+
|
169
|
+
static int free_table(st_data_t key, st_data_t value, st_data_t ignored) {
|
170
|
+
struct cov_array *carray;
|
171
|
+
|
172
|
+
carray = (struct cov_array *) value;
|
173
|
+
free((char *)key);
|
174
|
+
free(carray->ptr);
|
175
|
+
free(carray);
|
176
|
+
|
177
|
+
return ST_CONTINUE;
|
178
|
+
}
|
179
|
+
|
180
|
+
static VALUE cov_remove_coverage_hook(VALUE self) {
|
181
|
+
if(!coverage_hook_set_p)
|
182
|
+
return Qfalse;
|
183
|
+
else {
|
184
|
+
rb_remove_event_hook(coverage_event_coverage_hook);
|
185
|
+
coverage_hook_set_p = 0;
|
186
|
+
return Qtrue;
|
187
|
+
}
|
188
|
+
}
|
189
|
+
|
190
|
+
static VALUE cov_generate_coverage_info(VALUE self) {
|
191
|
+
VALUE cover;
|
192
|
+
|
193
|
+
if(rb_const_defined_at(mRCOV__, id_cover)) {
|
194
|
+
rb_mod_remove_const(mRCOV__, ID2SYM(id_cover));
|
195
|
+
}
|
196
|
+
|
197
|
+
cover = rb_hash_new();
|
198
|
+
if(coverinfo)
|
199
|
+
st_foreach(coverinfo, populate_cover, cover);
|
200
|
+
rb_define_const(mRCOV__, "COVER", cover);
|
201
|
+
|
202
|
+
return cover;
|
203
|
+
}
|
204
|
+
|
205
|
+
static VALUE cov_reset_coverage(VALUE self) {
|
206
|
+
if(coverage_hook_set_p) {
|
207
|
+
rb_raise(rb_eRuntimeError, "Cannot reset the coverage info in the middle of a traced run.");
|
208
|
+
return Qnil;
|
209
|
+
}
|
210
|
+
|
211
|
+
cached_array = 0;
|
212
|
+
cached_file = 0;
|
213
|
+
st_foreach(coverinfo, free_table, Qnil);
|
214
|
+
st_free_table(coverinfo);
|
215
|
+
coverinfo = 0;
|
216
|
+
|
217
|
+
return Qnil;
|
218
|
+
}
|
219
|
+
|
220
|
+
static VALUE cov_ABI(VALUE self) {
|
221
|
+
VALUE ret;
|
222
|
+
|
223
|
+
ret = rb_ary_new();
|
224
|
+
rb_ary_push(ret, INT2FIX(RCOVRT_VERSION_MAJOR));
|
225
|
+
rb_ary_push(ret, INT2FIX(RCOVRT_VERSION_MINOR));
|
226
|
+
rb_ary_push(ret, INT2FIX(RCOVRT_VERSION_REV));
|
227
|
+
|
228
|
+
return ret;
|
229
|
+
}
|
230
|
+
|
231
|
+
void Init_rcovrt() {
|
232
|
+
ID id_rcov = rb_intern("Rcov");
|
233
|
+
ID id_coverage__ = rb_intern("RCOV__");
|
234
|
+
ID id_script_lines__ = rb_intern("SCRIPT_LINES__");
|
235
|
+
|
236
|
+
id_cover = rb_intern("COVER");
|
237
|
+
|
238
|
+
if(rb_const_defined(rb_cObject, id_rcov))
|
239
|
+
mRcov = rb_const_get(rb_cObject, id_rcov);
|
240
|
+
else
|
241
|
+
mRcov = rb_define_module("Rcov");
|
242
|
+
|
243
|
+
if(rb_const_defined(mRcov, id_coverage__))
|
244
|
+
mRCOV__ = rb_const_get_at(mRcov, id_coverage__);
|
245
|
+
else
|
246
|
+
mRCOV__ = rb_define_module_under(mRcov, "RCOV__");
|
247
|
+
|
248
|
+
if(rb_const_defined(rb_cObject, id_script_lines__))
|
249
|
+
oSCRIPT_LINES__ = rb_const_get(rb_cObject, rb_intern("SCRIPT_LINES__"));
|
250
|
+
else {
|
251
|
+
oSCRIPT_LINES__ = rb_hash_new();
|
252
|
+
rb_const_set(rb_cObject, id_script_lines__, oSCRIPT_LINES__);
|
253
|
+
}
|
254
|
+
|
255
|
+
coverage_hook_set_p = 0;
|
256
|
+
|
257
|
+
rb_define_singleton_method(mRCOV__, "install_coverage_hook", cov_install_coverage_hook, 0);
|
258
|
+
rb_define_singleton_method(mRCOV__, "remove_coverage_hook", cov_remove_coverage_hook, 0);
|
259
|
+
rb_define_singleton_method(mRCOV__, "generate_coverage_info", cov_generate_coverage_info, 0);
|
260
|
+
rb_define_singleton_method(mRCOV__, "reset_coverage", cov_reset_coverage, 0);
|
261
|
+
rb_define_singleton_method(mRCOV__, "ABI", cov_ABI, 0);
|
262
|
+
|
263
|
+
Init_rcov_callsite();
|
264
|
+
}
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'mkmf'
|
2
|
+
|
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
|
+
if RUBY_VERSION =~ /1.9/
|
9
|
+
$CFLAGS << ' -DRUBY_19_COMPATIBILITY'
|
10
|
+
create_makefile("rcovrt", "1.9/")
|
11
|
+
else
|
12
|
+
create_makefile("rcovrt", "1.8/")
|
13
|
+
end
|
14
|
+
else
|
15
|
+
if RUBY_VERSION =~ /1.9/
|
16
|
+
$CFLAGS << ' -DRUBY_19_COMPATIBILITY'
|
17
|
+
create_makefile("rcovrt", "1.9/")
|
18
|
+
else
|
19
|
+
create_makefile("rcovrt", "1.8/")
|
20
|
+
end
|
21
|
+
end
|
data/lib/rcov.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# rcov Copyright (c) 2004-2006 Mauricio Fernandez <mfp@acm.org>
|
2
|
+
#
|
3
|
+
# See LICENSE for licensing information.
|
4
|
+
|
5
|
+
# NOTE: if you're reading this in the XHTML code coverage report generated by
|
6
|
+
# rcov, you'll notice that only code inside methods is reported as covered,
|
7
|
+
# very much like what happens when you run it with --test-unit-only.
|
8
|
+
# This is due to the fact that we're running rcov on itself: the code below is
|
9
|
+
# already loaded before coverage tracing is activated, so only code inside
|
10
|
+
# methods is actually executed under rcov's inspection.
|
11
|
+
|
12
|
+
require 'rcov/version'
|
13
|
+
require 'rcov/formatters'
|
14
|
+
require 'rcov/coverage_info'
|
15
|
+
require 'rcov/file_statistics'
|
16
|
+
require 'rcov/differential_analyzer'
|
17
|
+
require 'rcov/code_coverage_analyzer'
|
18
|
+
require 'rcov/call_site_analyzer'
|
19
|
+
|
20
|
+
SCRIPT_LINES__ = {} unless defined? SCRIPT_LINES__
|
21
|
+
|
22
|
+
module Rcov
|
23
|
+
# TODO: Move to Ruby 1.8.6 Backport module
|
24
|
+
unless RUBY_VERSION =~ /1.9/
|
25
|
+
class ::String
|
26
|
+
def lines
|
27
|
+
map
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
autoload :RCOV__, "rcov/lowlevel.rb"
|
33
|
+
end
|
@@ -0,0 +1,225 @@
|
|
1
|
+
module Rcov
|
2
|
+
# A CallSiteAnalyzer can be used to obtain information about:
|
3
|
+
# * where a method is defined ("+defsite+")
|
4
|
+
# * where a method was called from ("+callsite+")
|
5
|
+
#
|
6
|
+
# == Example
|
7
|
+
# <tt>example.rb</tt>:
|
8
|
+
# class X
|
9
|
+
# def f1; f2 end
|
10
|
+
# def f2; 1 + 1 end
|
11
|
+
# def f3; f1 end
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# analyzer = Rcov::CallSiteAnalyzer.new
|
15
|
+
# x = X.new
|
16
|
+
# analyzer.run_hooked do
|
17
|
+
# x.f1
|
18
|
+
# end
|
19
|
+
# # ....
|
20
|
+
#
|
21
|
+
# analyzer.run_hooked do
|
22
|
+
# x.f3
|
23
|
+
# # the information generated in this run is aggregated
|
24
|
+
# # to the previously recorded one
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# analyzer.analyzed_classes # => ["X", ... ]
|
28
|
+
# analyzer.methods_for_class("X") # => ["f1", "f2", "f3"]
|
29
|
+
# analyzer.defsite("X#f1") # => DefSite object
|
30
|
+
# analyzer.callsites("X#f2") # => hash with CallSite => count
|
31
|
+
# # associations
|
32
|
+
# defsite = analyzer.defsite("X#f1")
|
33
|
+
# defsite.file # => "example.rb"
|
34
|
+
# defsite.line # => 2
|
35
|
+
#
|
36
|
+
# You can have several CallSiteAnalyzer objects at a time, and it is
|
37
|
+
# possible to nest the #run_hooked / #install_hook/#remove_hook blocks: each
|
38
|
+
# analyzer will manage its data separately. Note however that no special
|
39
|
+
# provision is taken to ignore code executed "inside" the CallSiteAnalyzer
|
40
|
+
# class.
|
41
|
+
#
|
42
|
+
# +defsite+ information is only available for methods that were called under
|
43
|
+
# the inspection of the CallSiteAnalyzer, i.o.w. you will only have +defsite+
|
44
|
+
# information for those methods for which callsite information is
|
45
|
+
# available.
|
46
|
+
class CallSiteAnalyzer < DifferentialAnalyzer
|
47
|
+
# A method definition site.
|
48
|
+
class DefSite < Struct.new(:file, :line)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Object representing a method call site.
|
52
|
+
# It corresponds to a part of the callstack starting from the context that
|
53
|
+
# called the method.
|
54
|
+
class CallSite < Struct.new(:backtrace)
|
55
|
+
# The depth of a CallSite is the number of stack frames
|
56
|
+
# whose information is included in the CallSite object.
|
57
|
+
def depth
|
58
|
+
backtrace.size
|
59
|
+
end
|
60
|
+
|
61
|
+
# File where the method call originated.
|
62
|
+
# Might return +nil+ or "" if it is not meaningful (C extensions, etc).
|
63
|
+
def file(level = 0)
|
64
|
+
stack_frame = backtrace[level]
|
65
|
+
stack_frame ? stack_frame[2] : nil
|
66
|
+
end
|
67
|
+
|
68
|
+
# Line where the method call originated.
|
69
|
+
# Might return +nil+ or 0 if it is not meaningful (C extensions, etc).
|
70
|
+
def line(level = 0)
|
71
|
+
stack_frame = backtrace[level]
|
72
|
+
stack_frame ? stack_frame[3] : nil
|
73
|
+
end
|
74
|
+
|
75
|
+
# Name of the method where the call originated.
|
76
|
+
# Returns +nil+ if the call originated in +toplevel+.
|
77
|
+
# Might return +nil+ if it could not be determined.
|
78
|
+
def calling_method(level = 0)
|
79
|
+
stack_frame = backtrace[level]
|
80
|
+
stack_frame ? stack_frame[1] : nil
|
81
|
+
end
|
82
|
+
|
83
|
+
# Name of the class holding the method where the call originated.
|
84
|
+
# Might return +nil+ if it could not be determined.
|
85
|
+
def calling_class(level = 0)
|
86
|
+
stack_frame = backtrace[level]
|
87
|
+
stack_frame ? stack_frame[0] : nil
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
@hook_level = 0
|
92
|
+
# defined this way instead of attr_accessor so that it's covered
|
93
|
+
def self.hook_level # :nodoc:
|
94
|
+
@hook_level
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.hook_level=(x) # :nodoc:
|
98
|
+
@hook_level = x
|
99
|
+
end
|
100
|
+
|
101
|
+
def initialize
|
102
|
+
super(:install_callsite_hook, :remove_callsite_hook,
|
103
|
+
:reset_callsite)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Classes whose methods have been called.
|
107
|
+
# Returns an array of strings describing the classes (just klass.to_s for
|
108
|
+
# each of them). Singleton classes are rendered as:
|
109
|
+
# #<Class:MyNamespace::MyClass>
|
110
|
+
def analyzed_classes
|
111
|
+
raw_data_relative.first.keys.map{|klass, meth| klass}.uniq.sort
|
112
|
+
end
|
113
|
+
|
114
|
+
# Methods that were called for the given class. See #analyzed_classes for
|
115
|
+
# the notation used for singleton classes.
|
116
|
+
# Returns an array of strings or +nil+
|
117
|
+
def methods_for_class(classname)
|
118
|
+
a = raw_data_relative.first.keys.select{|kl,_| kl == classname}.map{|_,meth| meth}.sort
|
119
|
+
a.empty? ? nil : a
|
120
|
+
end
|
121
|
+
alias_method :analyzed_methods, :methods_for_class
|
122
|
+
|
123
|
+
# Returns a hash with <tt>CallSite => call count</tt> associations or +nil+
|
124
|
+
# Can be called in two ways:
|
125
|
+
# analyzer.callsites("Foo#f1") # instance method
|
126
|
+
# analyzer.callsites("Foo.g1") # singleton method of the class
|
127
|
+
# or
|
128
|
+
# analyzer.callsites("Foo", "f1")
|
129
|
+
# analyzer.callsites("#<class:Foo>", "g1")
|
130
|
+
def callsites(classname_or_fullname, methodname = nil)
|
131
|
+
rawsites = raw_data_relative.first[expand_name(classname_or_fullname, methodname)]
|
132
|
+
return nil unless rawsites
|
133
|
+
ret = {}
|
134
|
+
# could be a job for inject but it's slow and I don't mind the extra loc
|
135
|
+
rawsites.each_pair do |backtrace, count|
|
136
|
+
ret[CallSite.new(backtrace)] = count
|
137
|
+
end
|
138
|
+
ret
|
139
|
+
end
|
140
|
+
|
141
|
+
# Returns a DefSite object corresponding to the given method
|
142
|
+
# Can be called in two ways:
|
143
|
+
# analyzer.defsite("Foo#f1") # instance method
|
144
|
+
# analyzer.defsite("Foo.g1") # singleton method of the class
|
145
|
+
# or
|
146
|
+
# analyzer.defsite("Foo", "f1")
|
147
|
+
# analyzer.defsite("#<class:Foo>", "g1")
|
148
|
+
def defsite(classname_or_fullname, methodname = nil)
|
149
|
+
file, line = raw_data_relative[1][expand_name(classname_or_fullname, methodname)]
|
150
|
+
return nil unless file && line
|
151
|
+
DefSite.new(file, line)
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
def expand_name(classname_or_fullname, methodname = nil)
|
157
|
+
if methodname.nil?
|
158
|
+
case classname_or_fullname
|
159
|
+
when /(.*)#(.*)/ then classname, methodname = $1, $2
|
160
|
+
when /(.*)\.(.*)/ then classname, methodname = "#<Class:#{$1}>", $2
|
161
|
+
else
|
162
|
+
raise ArgumentError, "Incorrect method name"
|
163
|
+
end
|
164
|
+
|
165
|
+
return [classname, methodname]
|
166
|
+
end
|
167
|
+
|
168
|
+
[classname_or_fullname, methodname]
|
169
|
+
end
|
170
|
+
|
171
|
+
def data_default; [{}, {}] end
|
172
|
+
|
173
|
+
def raw_data_absolute
|
174
|
+
raw, method_def_site = RCOV__.generate_callsite_info
|
175
|
+
ret1 = {}
|
176
|
+
ret2 = {}
|
177
|
+
raw.each_pair do |(klass, method), hash|
|
178
|
+
begin
|
179
|
+
key = [klass.to_s, method.to_s]
|
180
|
+
ret1[key] = hash.clone #Marshal.load(Marshal.dump(hash))
|
181
|
+
ret2[key] = method_def_site[[klass, method]]
|
182
|
+
#rescue Exception
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
[ret1, ret2]
|
187
|
+
end
|
188
|
+
|
189
|
+
def aggregate_data(aggregated_data, delta)
|
190
|
+
callsites1, defsites1 = aggregated_data
|
191
|
+
callsites2, defsites2 = delta
|
192
|
+
|
193
|
+
callsites2.each_pair do |(klass, method), hash|
|
194
|
+
dest_hash = (callsites1[[klass, method]] ||= {})
|
195
|
+
hash.each_pair do |callsite, count|
|
196
|
+
dest_hash[callsite] ||= 0
|
197
|
+
dest_hash[callsite] += count
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
defsites1.update(defsites2)
|
202
|
+
end
|
203
|
+
|
204
|
+
def compute_raw_data_difference(first, last)
|
205
|
+
difference = {}
|
206
|
+
default = Hash.new(0)
|
207
|
+
|
208
|
+
callsites1, defsites1 = *first
|
209
|
+
callsites2, defsites2 = *last
|
210
|
+
|
211
|
+
callsites2.each_pair do |(klass, method), hash|
|
212
|
+
old_hash = callsites1[[klass, method]] || default
|
213
|
+
hash.each_pair do |callsite, count|
|
214
|
+
diff = hash[callsite] - (old_hash[callsite] || 0)
|
215
|
+
if diff > 0
|
216
|
+
difference[[klass, method]] ||= {}
|
217
|
+
difference[[klass, method]][callsite] = diff
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
[difference, defsites1.update(defsites2)]
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|