rcov 0.8.1.2.0 → 0.9.3
Sign up to get free protection for your applications and to get access to all the features.
- data/BLURB +2 -40
- data/LICENSE +2 -5
- data/Rakefile +32 -106
- data/THANKS +14 -0
- data/bin/rcov +277 -1090
- 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/{rcov.el → editor-extensions/rcov.el} +0 -0
- data/{rcov.vim → editor-extensions/rcov.vim} +0 -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 +12 -2
- data/lib/rcov.rb +13 -968
- 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 +173 -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 +274 -0
- data/lib/rcov/formatters/html_erb_template.rb +62 -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 +16 -17
- data/lib/rcov/rcovtask.rb +21 -22
- data/lib/rcov/templates/detail.html.erb +64 -0
- data/lib/rcov/templates/index.html.erb +93 -0
- data/lib/rcov/templates/jquery-1.3.2.min.js +19 -0
- data/lib/rcov/templates/jquery.tablesorter.min.js +15 -0
- data/lib/rcov/templates/print.css +12 -0
- data/lib/rcov/templates/rcov.js +42 -0
- data/lib/rcov/templates/screen.css +270 -0
- data/lib/rcov/version.rb +5 -8
- data/setup.rb +5 -2
- data/test/{sample_01.rb → assets/sample_01.rb} +0 -0
- data/test/{sample_02.rb → assets/sample_02.rb} +0 -0
- data/test/{sample_03.rb → assets/sample_03.rb} +0 -0
- data/test/{sample_04.rb → assets/sample_04.rb} +0 -0
- data/test/{sample_05-new.rb → assets/sample_05-new.rb} +0 -0
- data/test/{sample_05-old.rb → assets/sample_05-old.rb} +0 -0
- data/test/{sample_05.rb → assets/sample_05.rb} +0 -0
- data/test/{test_CallSiteAnalyzer.rb → call_site_analyzer_test.rb} +57 -81
- data/test/{test_CodeCoverageAnalyzer.rb → code_coverage_analyzer_test.rb} +71 -35
- data/test/{test_FileStatistics.rb → file_statistics_test.rb} +34 -36
- data/test/{test_functional.rb → functional_test.rb} +21 -35
- metadata +91 -69
- data/CHANGES +0 -177
- data/LEGAL +0 -36
- data/README.API +0 -42
- data/README.emacs +0 -64
- data/README.en +0 -130
- data/README.rake +0 -62
- data/README.rant +0 -68
- data/README.vim +0 -47
- data/Rantfile +0 -76
- data/ext/rcovrt/callsite.c +0 -242
- data/ext/rcovrt/rcovrt.c +0 -329
- data/lib/rcov/rant.rb +0 -87
- data/lib/rcov/report.rb +0 -1236
- data/mingw-rbconfig.rb +0 -174
@@ -0,0 +1,22 @@
|
|
1
|
+
# RCov
|
2
|
+
|
3
|
+
RCov is a:
|
4
|
+
|
5
|
+
1. tool for code coverage analysis for Ruby
|
6
|
+
2. library for collecting code coverage and execution count information introspectively
|
7
|
+
|
8
|
+
If you want to use the command line tool, the output from `rcov -h` is self explanatory. If you want to automate the execution of RCov via Rake take a look at [readme for rake]("http://github.com/relevance/rcov/blob/master/doc/readme_for_rake.markdown"). If you want to use the associated library, read on.
|
9
|
+
|
10
|
+
## Usage of the RCov runtime/library
|
11
|
+
|
12
|
+
RCov is primarily a tool for code coverage analysis, but since 0.4.0 it exposes some of its code so that you can build on top of its heuristics for code analysis and its capabilities for coverage information and execution count gathering. The main classes of interest are `Rcov::FileStatistics`, `Rcov::CodeCoverageAnalyzer` and `Rcov::CallSiteAnalyzer`.
|
13
|
+
|
14
|
+
* `Rcov::FileStatistics` can use some heuristics to determine which parts of the file are executable and which are mere comments.
|
15
|
+
|
16
|
+
* `Rcov::CodeCoverageAnalyzer` is used to gather code coverage and execution count information inside a running Ruby program.
|
17
|
+
|
18
|
+
* `Rcov::CallSiteAnalyzer` is used to obtain information about where methods are defined and who calls them.
|
19
|
+
|
20
|
+
The parts of RCov's runtime meant to be reused (i.e. the external API) are documented with RDoc. Those not meant to be used are clearly marked as so or were deliberately removed from the present documentation.
|
21
|
+
|
22
|
+
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# rcov.el
|
2
|
+
|
3
|
+
`rcov.el` allows you to use rcov from Emacs conveniently.
|
4
|
+
|
5
|
+
* Run unit tests and jump to uncovered code by <code>C-x `</code>
|
6
|
+
* Run unit tests and save the current coverage status.
|
7
|
+
* Run unit tests and jump to uncovered code introduced since the last run.
|
8
|
+
* View cross-reference annotated code.
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Copy <tt>rcov.el</tt> to the appropriate directory, which is in load-path then require it.
|
13
|
+
|
14
|
+
`(require 'rcov)`
|
15
|
+
|
16
|
+
## Usage
|
17
|
+
|
18
|
+
There are some commands to run RCov in Emacs. All of them will display RCov window, whose `major-mode` is `compilation-mode`. This allow you to jump to uncovered code using C-x `. rcov-command-line, rcovsave-command-line, and rcovdiff-command-line define command line to run rcov. If you do not use RCov from Rake, you must modify them.
|
19
|
+
|
20
|
+
### Finding uncovered code
|
21
|
+
|
22
|
+
Type the following while editing your program:
|
23
|
+
|
24
|
+
`M-x rcov`
|
25
|
+
|
26
|
+
### Setting the reference point
|
27
|
+
|
28
|
+
RCov's `--text-coverage-diff` mode compares the current coverage status against the saved one. It therefore needs that information to be recorded before you write new code (typically right after you perform a commit) in order to have something to compare against. You can save the current status with the `--save` option. Type the following to save the current status in Emacs:
|
29
|
+
|
30
|
+
`M-x rcovsave`
|
31
|
+
|
32
|
+
If you do not use RCov from Rake, you must modify `rcovsave-command-line` variable.
|
33
|
+
|
34
|
+
### Finding new uncovered code
|
35
|
+
|
36
|
+
Type the following to save the current status in Emacs:
|
37
|
+
|
38
|
+
`M-x rcovdiff`
|
39
|
+
|
40
|
+
### Viewing cross-reference annotated code
|
41
|
+
|
42
|
+
If you read cross-reference annotated code, issue
|
43
|
+
|
44
|
+
`rake rcov RCOVOPTS='-a'`
|
45
|
+
|
46
|
+
at the beginning. This command creates a `coverage` directory and many `*.rb` files in it. Filenames of these Ruby scripts are converted from original path. You can browse them by normally `C-x C-f`. You can think of `-a` option as `--xrefs` option and output format is Ruby script. After `find-file-ed` annotated script, the `major-mode` is `rcov-xref-mode`,
|
47
|
+
which is derived from `ruby-mode` and specializes navigation.
|
48
|
+
|
49
|
+
* `Tab` and `M-Tab` goes forward/backward links.
|
50
|
+
* `Ret` follows selected link.
|
51
|
+
|
52
|
+
This feature is useful to read third-party code or to follow control flow.
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# Code coverage analysis automation with Rake
|
2
|
+
|
3
|
+
Since 0.4.0, RCov features a `Rcov::RcovTask` task for rake
|
4
|
+
which can be used to automate test coverage analysis. Basic usage is as
|
5
|
+
follows:
|
6
|
+
<pre><code>
|
7
|
+
require 'rcov/rcovtask'
|
8
|
+
Rcov::RcovTask.new do |t|
|
9
|
+
t.test_files = FileList['test/test*.rb']
|
10
|
+
# t.verbose = true # uncomment to see the executed command
|
11
|
+
end
|
12
|
+
</pre></code>
|
13
|
+
|
14
|
+
This will create by default a task named `rcov`, and also a task to remove the output directory where the XHTML report is generated. The latter will be named `clobber_rcov`, and will be added to the main `clobber` target.
|
15
|
+
|
16
|
+
## Passing command line options to RCov
|
17
|
+
|
18
|
+
You can provide a description, change the name of the generated tasks (the one used to generate the report(s) and the `clobber_` one) and pass options to RCov:
|
19
|
+
<pre><code>
|
20
|
+
desc "Analyze code coverage of the unit tests."
|
21
|
+
Rcov::RcovTask.new(:coverage) do |t|
|
22
|
+
t.test_files = FileList['test/test*.rb']
|
23
|
+
t.verbose = true
|
24
|
+
## get a text report on stdout when rake is run:
|
25
|
+
t.rcov_opts << "--text-report"
|
26
|
+
## only report files under 80% coverage
|
27
|
+
t.rcov_opts << "--threshold 80"
|
28
|
+
end
|
29
|
+
</pre></code>
|
30
|
+
|
31
|
+
This will generate a `coverage` task and the associated `clobber_coverage` task to remove the directory the report is dumped to (`coverage` by default). You can specify a different destination directory, which comes handy if you have several `RcovTask`s; the `clobber_*` will take care of removing that directory:
|
32
|
+
<pre><code>
|
33
|
+
desc "Analyze code coverage for the FileStatistics class."
|
34
|
+
Rcov::RcovTask.new(:rcov_sourcefile) do |t|
|
35
|
+
t.test_files = FileList['test/test_FileStatistics.rb']
|
36
|
+
t.verbose = true
|
37
|
+
t.rcov_opts << "--test-unit-only"
|
38
|
+
t.output_dir = "coverage.sourcefile"
|
39
|
+
end
|
40
|
+
|
41
|
+
Rcov::RcovTask.new(:rcov_ccanalyzer) do |t|
|
42
|
+
t.test_files = FileList['test/test_CodeCoverageAnalyzer.rb']
|
43
|
+
t.verbose = true
|
44
|
+
t.rcov_opts << "--test-unit-only"
|
45
|
+
t.output_dir = "coverage.ccanalyzer"
|
46
|
+
end
|
47
|
+
</pre></code>
|
48
|
+
|
49
|
+
## Options passed through the `rake` command line
|
50
|
+
|
51
|
+
You can override the options defined in the RcovTask by passing the new options at the time you invoke rake. The documentation for the `Rcov::RcovTask` explains how this can be done.
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# rcov.vim
|
2
|
+
|
3
|
+
`rcov.vim` allows you to run unit tests from vim and enter quickfix mode in order to jump to uncovered code introduced since the last run.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
Copy `rcov.vim` to the appropriate `compiler` directory (typically `$HOME/.vim/compiler`).
|
7
|
+
|
8
|
+
### Usage
|
9
|
+
|
10
|
+
#### Setting the reference point
|
11
|
+
|
12
|
+
RCov's `--text-coverage-diff` mode compares the current coverage status against the saved one. It therefore needs that information to be recorded before you write new code (typically right after you perform a commit) in order to have something to compare against. You can save the current status with the `--save` option. If you're running RCov from Rake, you can do something like
|
13
|
+
|
14
|
+
`rake rcov_units RCOVOPTS="-T --save --rails"`
|
15
|
+
|
16
|
+
in order to take the current status as the reference point.
|
17
|
+
|
18
|
+
#### Finding new uncovered code
|
19
|
+
|
20
|
+
Type the following in command mode while editing your program:
|
21
|
+
|
22
|
+
`:compiler rcov`
|
23
|
+
|
24
|
+
`rcov.vim` assumes RCov can be invoked with a rake task (see [readme for rake]("http://github.com/relevance/rcov/blob/master/doc/readme_for_rake.markdown") for information on how to create it).
|
25
|
+
|
26
|
+
You can then execute +rcov+ and enter quickfix mode by typing
|
27
|
+
|
28
|
+
`:make <taskname>`
|
29
|
+
|
30
|
+
where taskname is the +rcov+ task you want to use; if you didn't override the default name in the Rakefile, just
|
31
|
+
|
32
|
+
`:make rcov`
|
33
|
+
|
34
|
+
will do. Vim will then enter quickfix mode, allowing you to jump to the areas that were not covered since the last time you saved the coverage data.
|
File without changes
|
File without changes
|
@@ -0,0 +1,216 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include <env.h>
|
3
|
+
#include <node.h>
|
4
|
+
#include <st.h>
|
5
|
+
#include <stdlib.h>
|
6
|
+
|
7
|
+
static char callsite_hook_set_p;
|
8
|
+
|
9
|
+
typedef struct {
|
10
|
+
char *sourcefile;
|
11
|
+
unsigned int sourceline;
|
12
|
+
VALUE curr_meth;
|
13
|
+
} type_def_site;
|
14
|
+
|
15
|
+
static VALUE caller_info = 0;
|
16
|
+
static VALUE method_def_site_info = 0;
|
17
|
+
|
18
|
+
static caller_stack_len = 1;
|
19
|
+
|
20
|
+
static VALUE record_callsite_info(VALUE args) {
|
21
|
+
VALUE caller_ary;
|
22
|
+
VALUE curr_meth;
|
23
|
+
VALUE count_hash;
|
24
|
+
VALUE count;
|
25
|
+
VALUE *pargs = (VALUE *)args;
|
26
|
+
|
27
|
+
caller_ary = pargs[0];
|
28
|
+
curr_meth = pargs[1];
|
29
|
+
count_hash = rb_hash_aref(caller_info, curr_meth);
|
30
|
+
|
31
|
+
if(TYPE(count_hash) != T_HASH) {
|
32
|
+
/* Qnil, anything else should be impossible unless somebody's been
|
33
|
+
* messing with ObjectSpace */
|
34
|
+
count_hash = rb_hash_new();
|
35
|
+
rb_hash_aset(caller_info, curr_meth, count_hash);
|
36
|
+
}
|
37
|
+
|
38
|
+
count = rb_hash_aref(count_hash, caller_ary);
|
39
|
+
|
40
|
+
if(count == Qnil)
|
41
|
+
count = INT2FIX(0);
|
42
|
+
|
43
|
+
count = INT2FIX(FIX2UINT(count) + 1);
|
44
|
+
rb_hash_aset(count_hash, caller_ary, count);
|
45
|
+
/*
|
46
|
+
printf("CALLSITE: %s -> %s %d\n", RSTRING(rb_inspect(curr_meth))->ptr,
|
47
|
+
RSTRING(rb_inspect(caller_ary))->ptr, FIX2INT(count));
|
48
|
+
*/
|
49
|
+
|
50
|
+
return Qnil;
|
51
|
+
}
|
52
|
+
|
53
|
+
static VALUE record_method_def_site(VALUE args) {
|
54
|
+
type_def_site *pargs = (type_def_site *)args;
|
55
|
+
VALUE def_site_info;
|
56
|
+
VALUE hash;
|
57
|
+
|
58
|
+
if(RTEST(rb_hash_aref(method_def_site_info, pargs->curr_meth)))
|
59
|
+
return Qnil;
|
60
|
+
def_site_info = rb_ary_new();
|
61
|
+
rb_ary_push(def_site_info, rb_str_new2(pargs->sourcefile));
|
62
|
+
rb_ary_push(def_site_info, INT2NUM(pargs->sourceline+1));
|
63
|
+
rb_hash_aset(method_def_site_info, pargs->curr_meth, def_site_info);
|
64
|
+
/*
|
65
|
+
printf("DEFSITE: %s:%d for %s\n", pargs->sourcefile, pargs->sourceline+1,
|
66
|
+
RSTRING(rb_inspect(pargs->curr_meth))->ptr);
|
67
|
+
*/
|
68
|
+
|
69
|
+
return Qnil;
|
70
|
+
}
|
71
|
+
|
72
|
+
static VALUE callsite_custom_backtrace(int lev) {
|
73
|
+
struct FRAME *frame = ruby_frame;
|
74
|
+
VALUE ary;
|
75
|
+
NODE *n;
|
76
|
+
VALUE level;
|
77
|
+
VALUE klass;
|
78
|
+
|
79
|
+
ary = rb_ary_new();
|
80
|
+
|
81
|
+
if (frame->last_func == ID_ALLOCATOR) {
|
82
|
+
frame = frame->prev;
|
83
|
+
}
|
84
|
+
|
85
|
+
for (; frame && (n = frame->node); frame = frame->prev) {
|
86
|
+
if (frame->prev && frame->prev->last_func) {
|
87
|
+
if (frame->prev->node == n) continue;
|
88
|
+
level = rb_ary_new();
|
89
|
+
klass = frame->prev->last_class ? frame->prev->last_class : Qnil;
|
90
|
+
if(TYPE(klass) == T_ICLASS) {
|
91
|
+
klass = CLASS_OF(klass);
|
92
|
+
}
|
93
|
+
rb_ary_push(level, klass);
|
94
|
+
rb_ary_push(level, ID2SYM(frame->prev->last_func));
|
95
|
+
rb_ary_push(level, rb_str_new2(n->nd_file));
|
96
|
+
rb_ary_push(level, INT2NUM(nd_line(n)));
|
97
|
+
}
|
98
|
+
else {
|
99
|
+
level = rb_ary_new();
|
100
|
+
rb_ary_push(level, Qnil);
|
101
|
+
rb_ary_push(level, Qnil);
|
102
|
+
rb_ary_push(level, rb_str_new2(n->nd_file));
|
103
|
+
rb_ary_push(level, INT2NUM(nd_line(n)));
|
104
|
+
}
|
105
|
+
rb_ary_push(ary, level);
|
106
|
+
if(--lev == 0)
|
107
|
+
break;
|
108
|
+
}
|
109
|
+
|
110
|
+
return ary;
|
111
|
+
}
|
112
|
+
|
113
|
+
static void coverage_event_callsite_hook(rb_event_t event, NODE *node, VALUE self, ID mid, VALUE klass) {
|
114
|
+
VALUE caller_ary;
|
115
|
+
VALUE curr_meth;
|
116
|
+
VALUE args[2];
|
117
|
+
int status;
|
118
|
+
|
119
|
+
caller_ary = callsite_custom_backtrace(caller_stack_len);
|
120
|
+
|
121
|
+
if(TYPE(klass) == T_ICLASS) {
|
122
|
+
klass = CLASS_OF(klass);
|
123
|
+
}
|
124
|
+
|
125
|
+
curr_meth = rb_ary_new();
|
126
|
+
rb_ary_push(curr_meth, klass);
|
127
|
+
rb_ary_push(curr_meth, ID2SYM(mid));
|
128
|
+
|
129
|
+
args[0] = caller_ary;
|
130
|
+
args[1] = curr_meth;
|
131
|
+
rb_protect(record_callsite_info, (VALUE)args, &status);
|
132
|
+
|
133
|
+
if(!status && node) {
|
134
|
+
type_def_site args;
|
135
|
+
|
136
|
+
args.sourcefile = node->nd_file;
|
137
|
+
args.sourceline = nd_line(node) - 1;
|
138
|
+
args.curr_meth = curr_meth;
|
139
|
+
rb_protect(record_method_def_site, (VALUE)&args, NULL);
|
140
|
+
}
|
141
|
+
|
142
|
+
if(status)
|
143
|
+
rb_gv_set("$!", Qnil);
|
144
|
+
}
|
145
|
+
|
146
|
+
static VALUE cov_install_callsite_hook(VALUE self) {
|
147
|
+
if(!callsite_hook_set_p) {
|
148
|
+
if(TYPE(caller_info) != T_HASH)
|
149
|
+
caller_info = rb_hash_new();
|
150
|
+
callsite_hook_set_p = 1;
|
151
|
+
rb_add_event_hook(coverage_event_callsite_hook, RUBY_EVENT_CALL);
|
152
|
+
|
153
|
+
return Qtrue;
|
154
|
+
}
|
155
|
+
else
|
156
|
+
return Qfalse;
|
157
|
+
}
|
158
|
+
|
159
|
+
static VALUE cov_remove_callsite_hook(VALUE self) {
|
160
|
+
if(!callsite_hook_set_p)
|
161
|
+
return Qfalse;
|
162
|
+
else {
|
163
|
+
rb_remove_event_hook(coverage_event_callsite_hook);
|
164
|
+
callsite_hook_set_p = 0;
|
165
|
+
return Qtrue;
|
166
|
+
}
|
167
|
+
}
|
168
|
+
|
169
|
+
static VALUE cov_generate_callsite_info(VALUE self) {
|
170
|
+
VALUE ret;
|
171
|
+
|
172
|
+
ret = rb_ary_new();
|
173
|
+
rb_ary_push(ret, caller_info);
|
174
|
+
rb_ary_push(ret, method_def_site_info);
|
175
|
+
return ret;
|
176
|
+
}
|
177
|
+
|
178
|
+
static VALUE cov_reset_callsite(VALUE self) {
|
179
|
+
if(callsite_hook_set_p) {
|
180
|
+
rb_raise(rb_eRuntimeError, "Cannot reset the callsite info in the middle of a traced run.");
|
181
|
+
return Qnil;
|
182
|
+
}
|
183
|
+
|
184
|
+
caller_info = rb_hash_new();
|
185
|
+
method_def_site_info = rb_hash_new();
|
186
|
+
return Qnil;
|
187
|
+
}
|
188
|
+
|
189
|
+
void Init_rcov_callsite() {
|
190
|
+
VALUE mRcov;
|
191
|
+
VALUE mRCOV__;
|
192
|
+
ID id_rcov = rb_intern("Rcov");
|
193
|
+
ID id_coverage__ = rb_intern("RCOV__");
|
194
|
+
ID id_script_lines__ = rb_intern("SCRIPT_LINES__");
|
195
|
+
|
196
|
+
if(rb_const_defined(rb_cObject, id_rcov))
|
197
|
+
mRcov = rb_const_get(rb_cObject, id_rcov);
|
198
|
+
else
|
199
|
+
mRcov = rb_define_module("Rcov");
|
200
|
+
|
201
|
+
if(rb_const_defined(mRcov, id_coverage__))
|
202
|
+
mRCOV__ = rb_const_get_at(mRcov, id_coverage__);
|
203
|
+
else
|
204
|
+
mRCOV__ = rb_define_module_under(mRcov, "RCOV__");
|
205
|
+
|
206
|
+
callsite_hook_set_p = 0;
|
207
|
+
caller_info = rb_hash_new();
|
208
|
+
method_def_site_info = rb_hash_new();
|
209
|
+
rb_gc_register_address(&caller_info);
|
210
|
+
rb_gc_register_address(&method_def_site_info);
|
211
|
+
|
212
|
+
rb_define_singleton_method(mRCOV__, "install_callsite_hook", cov_install_callsite_hook, 0);
|
213
|
+
rb_define_singleton_method(mRCOV__, "remove_callsite_hook", cov_remove_callsite_hook, 0);
|
214
|
+
rb_define_singleton_method(mRCOV__, "generate_callsite_info", cov_generate_callsite_info, 0);
|
215
|
+
rb_define_singleton_method(mRCOV__, "reset_callsite", cov_reset_callsite, 0);
|
216
|
+
}
|
@@ -0,0 +1,287 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include <env.h>
|
3
|
+
#include <node.h>
|
4
|
+
#include <st.h>
|
5
|
+
#include <stdlib.h>
|
6
|
+
#include <assert.h>
|
7
|
+
|
8
|
+
#define COVERAGE_DEBUG_EVENTS 0
|
9
|
+
|
10
|
+
#define RCOVRT_VERSION_MAJOR 2
|
11
|
+
#define RCOVRT_VERSION_MINOR 0
|
12
|
+
#define RCOVRT_VERSION_REV 0
|
13
|
+
|
14
|
+
static VALUE mRcov;
|
15
|
+
static VALUE mRCOV__;
|
16
|
+
static VALUE oSCRIPT_LINES__;
|
17
|
+
static ID id_cover;
|
18
|
+
static st_table* coverinfo = 0;
|
19
|
+
static char coverage_hook_set_p;
|
20
|
+
|
21
|
+
struct cov_array {
|
22
|
+
unsigned int len;
|
23
|
+
unsigned int *ptr;
|
24
|
+
};
|
25
|
+
|
26
|
+
static struct cov_array *cached_array = 0;
|
27
|
+
static char *cached_file = 0;
|
28
|
+
|
29
|
+
static struct cov_array * coverage_increase_counter_uncached(char *sourcefile, unsigned int sourceline, char mark_only) {
|
30
|
+
struct cov_array *carray = NULL;
|
31
|
+
|
32
|
+
if(sourcefile == NULL) {
|
33
|
+
/* "can't happen", just ignore and avoid segfault */
|
34
|
+
return NULL;
|
35
|
+
}
|
36
|
+
else if(!st_lookup(coverinfo, (st_data_t)sourcefile, (st_data_t*)&carray)) {
|
37
|
+
VALUE arr;
|
38
|
+
|
39
|
+
arr = rb_hash_aref(oSCRIPT_LINES__, rb_str_new2(sourcefile));
|
40
|
+
if(NIL_P(arr))
|
41
|
+
return 0;
|
42
|
+
rb_check_type(arr, T_ARRAY);
|
43
|
+
carray = calloc(1, sizeof(struct cov_array));
|
44
|
+
carray->ptr = calloc(RARRAY(arr)->len, sizeof(unsigned int));
|
45
|
+
carray->len = RARRAY(arr)->len;
|
46
|
+
st_insert(coverinfo, (st_data_t)strdup(sourcefile), (st_data_t) carray);
|
47
|
+
}
|
48
|
+
else {
|
49
|
+
/* recovered carray, sanity check */
|
50
|
+
assert(carray && "failed to create valid carray");
|
51
|
+
}
|
52
|
+
|
53
|
+
if(mark_only) {
|
54
|
+
if(!carray->ptr[sourceline])
|
55
|
+
carray->ptr[sourceline] = 1;
|
56
|
+
} else {
|
57
|
+
if (carray && carray->len > sourceline) {
|
58
|
+
carray->ptr[sourceline]++;
|
59
|
+
}
|
60
|
+
}
|
61
|
+
|
62
|
+
return carray;
|
63
|
+
}
|
64
|
+
|
65
|
+
static void coverage_mark_caller() {
|
66
|
+
struct FRAME *frame = ruby_frame;
|
67
|
+
NODE *n;
|
68
|
+
|
69
|
+
if (frame->last_func == ID_ALLOCATOR) {
|
70
|
+
frame = frame->prev;
|
71
|
+
}
|
72
|
+
for (; frame && (n = frame->node); frame = frame->prev) {
|
73
|
+
if (frame->prev && frame->prev->last_func) {
|
74
|
+
if (frame->prev->node == n) {
|
75
|
+
if (frame->prev->last_func == frame->last_func) continue;
|
76
|
+
}
|
77
|
+
coverage_increase_counter_uncached(n->nd_file, nd_line(n) - 1, 1);
|
78
|
+
}
|
79
|
+
else {
|
80
|
+
coverage_increase_counter_uncached(n->nd_file, nd_line(n) - 1, 1);
|
81
|
+
}
|
82
|
+
break;
|
83
|
+
}
|
84
|
+
}
|
85
|
+
|
86
|
+
static void coverage_increase_counter_cached(char *sourcefile, int sourceline) {
|
87
|
+
if(cached_file == sourcefile && cached_array && cached_array->len > sourceline) {
|
88
|
+
cached_array->ptr[sourceline]++;
|
89
|
+
return;
|
90
|
+
}
|
91
|
+
cached_file = sourcefile;
|
92
|
+
cached_array = coverage_increase_counter_uncached(sourcefile, sourceline, 0);
|
93
|
+
}
|
94
|
+
|
95
|
+
static void coverage_event_coverage_hook(rb_event_t event, NODE *node, VALUE self, ID mid, VALUE klass) {
|
96
|
+
char *sourcefile;
|
97
|
+
unsigned int sourceline;
|
98
|
+
static unsigned int in_hook = 0;
|
99
|
+
|
100
|
+
if(in_hook) {
|
101
|
+
return;
|
102
|
+
}
|
103
|
+
|
104
|
+
in_hook++;
|
105
|
+
|
106
|
+
#if COVERAGE_DEBUG_EVENTS
|
107
|
+
do {
|
108
|
+
int status;
|
109
|
+
VALUE old_exception;
|
110
|
+
old_exception = rb_gv_get("$!");
|
111
|
+
rb_protect(rb_inspect, klass, &status);
|
112
|
+
if(!status) {
|
113
|
+
printf("EVENT: %d %s %s %s %d\n", event,
|
114
|
+
klass ? RSTRING(rb_inspect(klass))->ptr : "",
|
115
|
+
mid ? (mid == ID_ALLOCATOR ? "ID_ALLOCATOR" : rb_id2name(mid))
|
116
|
+
: "unknown",
|
117
|
+
node ? node->nd_file : "", node ? nd_line(node) : 0);
|
118
|
+
}
|
119
|
+
else {
|
120
|
+
printf("EVENT: %d %s %s %d\n", event,
|
121
|
+
mid ? (mid == ID_ALLOCATOR ? "ID_ALLOCATOR" : rb_id2name(mid))
|
122
|
+
: "unknown",
|
123
|
+
node ? node->nd_file : "", node ? nd_line(node) : 0);
|
124
|
+
}
|
125
|
+
|
126
|
+
rb_gv_set("$!", old_exception);
|
127
|
+
} while (0);
|
128
|
+
#endif
|
129
|
+
|
130
|
+
if(event & RUBY_EVENT_C_CALL) {
|
131
|
+
coverage_mark_caller();
|
132
|
+
}
|
133
|
+
|
134
|
+
if(event & (RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN | RUBY_EVENT_CLASS)) {
|
135
|
+
in_hook--;
|
136
|
+
return;
|
137
|
+
}
|
138
|
+
|
139
|
+
if(node == NULL) {
|
140
|
+
in_hook--;
|
141
|
+
return;
|
142
|
+
}
|
143
|
+
|
144
|
+
sourcefile = node->nd_file;
|
145
|
+
sourceline = nd_line(node) - 1;
|
146
|
+
|
147
|
+
coverage_increase_counter_cached(sourcefile, sourceline);
|
148
|
+
|
149
|
+
if(event & RUBY_EVENT_CALL)
|
150
|
+
coverage_mark_caller();
|
151
|
+
in_hook--;
|
152
|
+
}
|
153
|
+
|
154
|
+
static VALUE cov_install_coverage_hook(VALUE self) {
|
155
|
+
if(!coverage_hook_set_p) {
|
156
|
+
if(!coverinfo)
|
157
|
+
coverinfo = st_init_strtable();
|
158
|
+
coverage_hook_set_p = 1;
|
159
|
+
/* TODO: allow C_CALL too, since it's supported already
|
160
|
+
* the overhead is around ~30%, tested on typo */
|
161
|
+
rb_add_event_hook(coverage_event_coverage_hook, RUBY_EVENT_ALL & ~RUBY_EVENT_C_CALL & ~RUBY_EVENT_C_RETURN & ~RUBY_EVENT_CLASS);
|
162
|
+
|
163
|
+
return Qtrue;
|
164
|
+
}
|
165
|
+
else
|
166
|
+
return Qfalse;
|
167
|
+
}
|
168
|
+
|
169
|
+
static int populate_cover(st_data_t key, st_data_t value, st_data_t cover) {
|
170
|
+
VALUE rcover;
|
171
|
+
VALUE rkey;
|
172
|
+
VALUE rval;
|
173
|
+
struct cov_array *carray;
|
174
|
+
unsigned int i;
|
175
|
+
|
176
|
+
rcover = (VALUE)cover;
|
177
|
+
carray = (struct cov_array *) value;
|
178
|
+
rkey = rb_str_new2((char*) key);
|
179
|
+
rval = rb_ary_new2(carray->len);
|
180
|
+
|
181
|
+
for(i = 0; i < carray->len; i++)
|
182
|
+
RARRAY(rval)->ptr[i] = UINT2NUM(carray->ptr[i]);
|
183
|
+
|
184
|
+
RARRAY(rval)->len = carray->len;
|
185
|
+
rb_hash_aset(rcover, rkey, rval);
|
186
|
+
|
187
|
+
return ST_CONTINUE;
|
188
|
+
}
|
189
|
+
|
190
|
+
static int free_table(st_data_t key, st_data_t value, st_data_t ignored) {
|
191
|
+
struct cov_array *carray;
|
192
|
+
|
193
|
+
carray = (struct cov_array *) value;
|
194
|
+
free((char *)key);
|
195
|
+
free(carray->ptr);
|
196
|
+
free(carray);
|
197
|
+
|
198
|
+
return ST_CONTINUE;
|
199
|
+
}
|
200
|
+
|
201
|
+
static VALUE cov_remove_coverage_hook(VALUE self) {
|
202
|
+
if(!coverage_hook_set_p)
|
203
|
+
return Qfalse;
|
204
|
+
else {
|
205
|
+
rb_remove_event_hook(coverage_event_coverage_hook);
|
206
|
+
coverage_hook_set_p = 0;
|
207
|
+
return Qtrue;
|
208
|
+
}
|
209
|
+
}
|
210
|
+
|
211
|
+
static VALUE cov_generate_coverage_info(VALUE self) {
|
212
|
+
VALUE cover;
|
213
|
+
|
214
|
+
if(rb_const_defined_at(mRCOV__, id_cover)) {
|
215
|
+
rb_mod_remove_const(mRCOV__, ID2SYM(id_cover));
|
216
|
+
}
|
217
|
+
|
218
|
+
cover = rb_hash_new();
|
219
|
+
|
220
|
+
if(coverinfo)
|
221
|
+
st_foreach(coverinfo, populate_cover, cover);
|
222
|
+
|
223
|
+
rb_define_const(mRCOV__, "COVER", cover);
|
224
|
+
|
225
|
+
return cover;
|
226
|
+
}
|
227
|
+
|
228
|
+
static VALUE cov_reset_coverage(VALUE self) {
|
229
|
+
if(coverage_hook_set_p) {
|
230
|
+
rb_raise(rb_eRuntimeError, "Cannot reset the coverage info in the middle of a traced run.");
|
231
|
+
return Qnil;
|
232
|
+
}
|
233
|
+
|
234
|
+
cached_array = 0;
|
235
|
+
cached_file = 0;
|
236
|
+
st_foreach(coverinfo, free_table, Qnil);
|
237
|
+
st_free_table(coverinfo);
|
238
|
+
coverinfo = 0;
|
239
|
+
|
240
|
+
return Qnil;
|
241
|
+
}
|
242
|
+
|
243
|
+
static VALUE cov_ABI(VALUE self) {
|
244
|
+
VALUE ret;
|
245
|
+
|
246
|
+
ret = rb_ary_new();
|
247
|
+
rb_ary_push(ret, INT2FIX(RCOVRT_VERSION_MAJOR));
|
248
|
+
rb_ary_push(ret, INT2FIX(RCOVRT_VERSION_MINOR));
|
249
|
+
rb_ary_push(ret, INT2FIX(RCOVRT_VERSION_REV));
|
250
|
+
|
251
|
+
return ret;
|
252
|
+
}
|
253
|
+
|
254
|
+
void Init_rcovrt() {
|
255
|
+
ID id_rcov = rb_intern("Rcov");
|
256
|
+
ID id_coverage__ = rb_intern("RCOV__");
|
257
|
+
ID id_script_lines__ = rb_intern("SCRIPT_LINES__");
|
258
|
+
|
259
|
+
id_cover = rb_intern("COVER");
|
260
|
+
|
261
|
+
if(rb_const_defined(rb_cObject, id_rcov))
|
262
|
+
mRcov = rb_const_get(rb_cObject, id_rcov);
|
263
|
+
else
|
264
|
+
mRcov = rb_define_module("Rcov");
|
265
|
+
|
266
|
+
if(rb_const_defined(mRcov, id_coverage__))
|
267
|
+
mRCOV__ = rb_const_get_at(mRcov, id_coverage__);
|
268
|
+
else
|
269
|
+
mRCOV__ = rb_define_module_under(mRcov, "RCOV__");
|
270
|
+
|
271
|
+
if(rb_const_defined(rb_cObject, id_script_lines__))
|
272
|
+
oSCRIPT_LINES__ = rb_const_get(rb_cObject, rb_intern("SCRIPT_LINES__"));
|
273
|
+
else {
|
274
|
+
oSCRIPT_LINES__ = rb_hash_new();
|
275
|
+
rb_const_set(rb_cObject, id_script_lines__, oSCRIPT_LINES__);
|
276
|
+
}
|
277
|
+
|
278
|
+
coverage_hook_set_p = 0;
|
279
|
+
|
280
|
+
rb_define_singleton_method(mRCOV__, "install_coverage_hook", cov_install_coverage_hook, 0);
|
281
|
+
rb_define_singleton_method(mRCOV__, "remove_coverage_hook", cov_remove_coverage_hook, 0);
|
282
|
+
rb_define_singleton_method(mRCOV__, "generate_coverage_info", cov_generate_coverage_info, 0);
|
283
|
+
rb_define_singleton_method(mRCOV__, "reset_coverage", cov_reset_coverage, 0);
|
284
|
+
rb_define_singleton_method(mRCOV__, "ABI", cov_ABI, 0);
|
285
|
+
|
286
|
+
Init_rcov_callsite();
|
287
|
+
}
|