Feature #9508 » pull-request-511.patch
| compile.c | ||
|---|---|---|
|
iseq->compile_data->last_coverable_line = (line); \
|
||
|
ADD_INSN1((seq), (line), trace, INT2FIX(RUBY_EVENT_COVERAGE)); \
|
||
|
} \
|
||
|
if ((event) == RUBY_EVENT_DEFN && iseq->method_coverage) { \
|
||
|
/*iseq->compile_data->last_coverable_line = (line); \
|
||
|
VALUE info = rb_hash_new(); \
|
||
|
rb_hash_aset(info, ID2SYM(rb_intern("count")), INT2FIX(0));*/ \
|
||
|
rb_hash_aset(iseq->method_coverage, LONG2FIX(line), Qnil); \
|
||
|
} \
|
||
|
if ((event) == RUBY_EVENT_CALL && iseq->method_coverage && \
|
||
|
(line) != iseq->compile_data->last_coverable_line) { \
|
||
|
iseq->compile_data->last_coverable_line = (line); \
|
||
|
ADD_INSN1((seq), (line), trace, INT2FIX(RUBY_EVENT_MCOVERAGE)); \
|
||
|
} \
|
||
|
if (iseq->compile_data->option->trace_instruction) { \
|
||
|
ADD_INSN1((seq), (line), trace, INT2FIX(event)); \
|
||
|
} \
|
||
| ... | ... | |
|
debugp_param("defn/iseq", iseqval);
|
||
|
ADD_TRACE(ret, nd_line(node), RUBY_EVENT_DEFN);
|
||
|
ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE));
|
||
|
ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_CBASE));
|
||
|
ADD_INSN1(ret, line, putobject, ID2SYM(node->nd_mid));
|
||
| ext/coverage/coverage.c | ||
|---|---|---|
|
VALUE path = (VALUE)key;
|
||
|
VALUE coverage = (VALUE)val;
|
||
|
VALUE coverages = (VALUE)h;
|
||
|
coverage = rb_ary_dup(coverage);
|
||
|
rb_ary_clear((VALUE)val);
|
||
|
VALUE line_coverage = rb_hash_lookup(coverage, ID2SYM(rb_intern("lines")));
|
||
|
line_coverage = rb_ary_dup(line_coverage);
|
||
|
coverage = rb_hash_dup(coverage);
|
||
|
rb_hash_clear((VALUE)val);
|
||
|
rb_hash_freeze(line_coverage);
|
||
|
rb_hash_aset(coverage, ID2SYM(rb_intern("lines")), line_coverage);
|
||
|
rb_ary_freeze(coverage);
|
||
|
rb_hash_aset(coverages, path, coverage);
|
||
|
return ST_CONTINUE;
|
||
| include/ruby/ruby.h | ||
|---|---|---|
|
#define RUBY_EVENT_C_CALL 0x0020
|
||
|
#define RUBY_EVENT_C_RETURN 0x0040
|
||
|
#define RUBY_EVENT_RAISE 0x0080
|
||
|
#define RUBY_EVENT_DEFN 0x0090
|
||
|
#define RUBY_EVENT_ALL 0x00ff
|
||
|
/* for TracePoint extended events */
|
||
| ... | ... | |
|
/* special events */
|
||
|
#define RUBY_EVENT_SPECIFIED_LINE 0x010000
|
||
|
#define RUBY_EVENT_COVERAGE 0x020000
|
||
|
#define RUBY_EVENT_MCOVERAGE 0x040000
|
||
|
/* internal events */
|
||
|
#define RUBY_INTERNAL_EVENT_SWITCH 0x040000
|
||
| iseq.c | ||
|---|---|---|
|
RUBY_MARK_UNLESS_NULL((VALUE)iseq->cref_stack);
|
||
|
RUBY_MARK_UNLESS_NULL(iseq->klass);
|
||
|
RUBY_MARK_UNLESS_NULL(iseq->coverage);
|
||
|
RUBY_MARK_UNLESS_NULL(iseq->method_coverage);
|
||
|
RUBY_MARK_UNLESS_NULL(iseq->orig);
|
||
|
if (iseq->compile_data != 0) {
|
||
| ... | ... | |
|
iseq->compile_data->last_coverable_line = -1;
|
||
|
RB_OBJ_WRITE(iseq->self, &iseq->coverage, Qfalse);
|
||
|
RB_OBJ_WRITE(iseq->self, &iseq->method_coverage, Qfalse);
|
||
|
if (!GET_THREAD()->parse_in_eval) {
|
||
|
VALUE coverages = rb_get_coverages();
|
||
|
if (RTEST(coverages)) {
|
||
|
RB_OBJ_WRITE(iseq->self, &iseq->coverage, rb_hash_lookup(coverages, path));
|
||
|
RB_OBJ_WRITE(iseq->self, &iseq->coverage,
|
||
|
rb_hash_lookup(rb_hash_lookup(coverages, path), ID2SYM(rb_intern("lines"))));
|
||
|
RB_OBJ_WRITE(iseq->self, &iseq->method_coverage,
|
||
|
rb_hash_lookup(rb_hash_lookup(coverages, path), ID2SYM(rb_intern("methods"))));
|
||
|
if (NIL_P(iseq->coverage)) RB_OBJ_WRITE(iseq->self, &iseq->coverage, Qfalse);
|
||
|
if (NIL_P(iseq->method_coverage)) RB_OBJ_WRITE(iseq->self, &iseq->method_coverage, Qfalse);
|
||
|
}
|
||
|
}
|
||
| parse.y | ||
|---|---|---|
|
VALUE coverages = rb_get_coverages();
|
||
|
if (RTEST(coverages) && RBASIC(coverages)->klass == 0) {
|
||
|
VALUE lines = rb_ary_new2(n);
|
||
|
VALUE rb_file_coverage = rb_hash_new();
|
||
|
VALUE methods = rb_hash_new();
|
||
|
int i;
|
||
|
RBASIC_CLEAR_CLASS(lines);
|
||
|
for (i = 0; i < n; i++) RARRAY_ASET(lines, i, Qnil);
|
||
|
RARRAY(lines)->as.heap.len = n;
|
||
|
rb_hash_aset(coverages, fname, lines);
|
||
|
rb_hash_aset(rb_file_coverage, ID2SYM(rb_intern("lines")), lines);
|
||
|
rb_hash_aset(rb_file_coverage, ID2SYM(rb_intern("methods")), methods);
|
||
|
rb_hash_aset(coverages, fname, rb_file_coverage);
|
||
|
return lines;
|
||
|
}
|
||
|
return 0;
|
||
| thread.c | ||
|---|---|---|
|
clear_coverage_i(st_data_t key, st_data_t val, st_data_t dummy)
|
||
|
{
|
||
|
int i;
|
||
|
VALUE lines = (VALUE)val;
|
||
|
VALUE lines = rb_hash_lookup(val, ID2SYM(rb_intern("lines")));
|
||
|
for (i = 0; i < RARRAY_LEN(lines); i++) {
|
||
|
if (RARRAY_AREF(lines, i) != Qnil) {
|
||
| ... | ... | |
|
}
|
||
|
}
|
||
|
static void
|
||
|
update_method_coverage(rb_event_flag_t event, VALUE proc, VALUE self, ID id, VALUE klass)
|
||
|
{
|
||
|
VALUE method_coverage = GET_THREAD()->cfp->iseq->method_coverage;
|
||
|
if (method_coverage) {
|
||
|
long line = rb_sourceline();
|
||
|
VALUE info = rb_hash_lookup(method_coverage, LONG2FIX(line));
|
||
|
if (info == Qnil) {
|
||
|
VALUE info = rb_hash_new();
|
||
|
rb_hash_aset(info, ID2SYM(rb_intern("count")), INT2FIX(1));
|
||
|
rb_hash_aset(method_coverage, LONG2FIX(line), info);
|
||
|
} else {
|
||
|
long count = FIX2LONG(rb_hash_lookup(info, ID2SYM(rb_intern("count")))) + 1;
|
||
|
rb_hash_aset(info, ID2SYM(rb_intern("count")), LONG2FIX(count));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
VALUE
|
||
|
rb_get_coverages(void)
|
||
|
{
|
||
| ... | ... | |
|
{
|
||
|
GET_VM()->coverages = coverages;
|
||
|
rb_add_event_hook(update_coverage, RUBY_EVENT_COVERAGE, Qnil);
|
||
|
rb_add_event_hook(update_method_coverage, RUBY_EVENT_MCOVERAGE, Qnil);
|
||
|
}
|
||
|
void
|
||
| vm_core.h | ||
|---|---|---|
|
VALUE *iseq_encoded; /* encoded iseq */
|
||
|
unsigned long iseq_size;
|
||
|
const VALUE mark_ary; /* Array: includes operands which should be GC marked */
|
||
|
const VALUE coverage; /* coverage array */
|
||
|
const VALUE coverage; /* coverage array */
|
||
|
const VALUE method_coverage; /* method coverage array */
|
||
|
/* insn info, must be freed */
|
||
|
struct iseq_line_info_entry *line_info_table;
|
||
|
-
|
||
| compile.c | ||
|---|---|---|
|
iseq->compile_data->last_coverable_line = (line); \
|
||
|
ADD_INSN1((seq), (line), trace, INT2FIX(RUBY_EVENT_COVERAGE)); \
|
||
|
} \
|
||
|
if ((event) == RUBY_EVENT_DEFN && iseq->method_coverage) { \
|
||
|
/*iseq->compile_data->last_coverable_line = (line); \
|
||
|
VALUE info = rb_hash_new(); \
|
||
|
rb_hash_aset(info, ID2SYM(rb_intern("count")), INT2FIX(0));*/ \
|
||
|
rb_hash_aset(iseq->method_coverage, LONG2FIX(line), Qnil); \
|
||
|
} \
|
||
|
if ((event) == RUBY_EVENT_CALL && iseq->method_coverage && \
|
||
|
(line) != iseq->compile_data->last_coverable_line) { \
|
||
|
iseq->compile_data->last_coverable_line = (line); \
|
||
| ... | ... | |
|
} \
|
||
|
} while (0)
|
||
|
#define ADD_COVERAGE_TRACE(seq, line, event) \
|
||
|
do { \
|
||
|
if ((event) == RUBY_EVENT_DEFN && iseq->method_coverage) { \
|
||
|
rb_hash_aset(iseq->method_coverage, LONG2FIX(line), INT2FIX(0)); \
|
||
|
} \
|
||
|
} while (0)
|
||
|
/* add label */
|
||
|
#define ADD_LABEL(seq, label) \
|
||
|
ADD_ELEM((seq), (LINK_ELEMENT *) (label))
|
||
| ... | ... | |
|
debugp_param("defn/iseq", iseqval);
|
||
|
ADD_TRACE(ret, nd_line(node), RUBY_EVENT_DEFN);
|
||
|
ADD_COVERAGE_TRACE(ret, nd_line(node), RUBY_EVENT_DEFN);
|
||
|
ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE));
|
||
|
ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_CBASE));
|
||
|
ADD_INSN1(ret, line, putobject, ID2SYM(node->nd_mid));
|
||
| thread.c | ||
|---|---|---|
|
long line = rb_sourceline();
|
||
|
VALUE info = rb_hash_lookup(method_coverage, LONG2FIX(line));
|
||
|
if (info == Qnil) {
|
||
|
VALUE info = rb_hash_new();
|
||
|
rb_hash_aset(info, ID2SYM(rb_intern("count")), INT2FIX(1));
|
||
|
rb_hash_aset(method_coverage, LONG2FIX(line), info);
|
||
|
} else {
|
||
|
long count = FIX2LONG(rb_hash_lookup(info, ID2SYM(rb_intern("count")))) + 1;
|
||
|
rb_hash_aset(info, ID2SYM(rb_intern("count")), LONG2FIX(count));
|
||
|
}
|
||
|
long count = FIX2LONG(info) + 1;
|
||
|
rb_hash_aset(method_coverage, LONG2FIX(line), LONG2FIX(count));
|
||
|
}
|
||
|
}
|
||
|
-
|
||
| compile.c | ||
|---|---|---|
|
if ((event) == RUBY_EVENT_DEFN && iseq->method_coverage) { \
|
||
|
rb_hash_aset(iseq->method_coverage, LONG2FIX(line), INT2FIX(0)); \
|
||
|
} \
|
||
|
if ((event) == RUBY_EVENT_BRANCH && iseq->branch_coverage) { \
|
||
|
rb_hash_aset(iseq->branch_coverage, LONG2FIX(line), INT2FIX(0)); \
|
||
|
ADD_INSN1((seq), (line), trace, INT2FIX(RUBY_EVENT_BCOVERAGE)); \
|
||
|
} \
|
||
|
} while (0)
|
||
|
/* add label */
|
||
| ... | ... | |
|
ADD_SEQ(ret, cond_seq);
|
||
|
ADD_LABEL(ret, then_label);
|
||
|
if (node->nd_body)
|
||
|
ADD_COVERAGE_TRACE(ret, nd_line(node->nd_body), RUBY_EVENT_BRANCH);
|
||
|
ADD_SEQ(ret, then_seq);
|
||
|
ADD_INSNL(ret, line, jump, end_label);
|
||
|
ADD_LABEL(ret, else_label);
|
||
|
if (node->nd_else)
|
||
|
ADD_COVERAGE_TRACE(ret, nd_line(node->nd_else), RUBY_EVENT_BRANCH);
|
||
|
ADD_SEQ(ret, else_seq);
|
||
|
ADD_LABEL(ret, end_label);
|
||
| include/ruby/ruby.h | ||
|---|---|---|
|
#define RUBY_EVENT_C_RETURN 0x0040
|
||
|
#define RUBY_EVENT_RAISE 0x0080
|
||
|
#define RUBY_EVENT_DEFN 0x0090
|
||
|
#define RUBY_EVENT_BRANCH 0x00a0
|
||
|
#define RUBY_EVENT_ALL 0x00ff
|
||
|
/* for TracePoint extended events */
|
||
| ... | ... | |
|
#define RUBY_EVENT_SPECIFIED_LINE 0x010000
|
||
|
#define RUBY_EVENT_COVERAGE 0x020000
|
||
|
#define RUBY_EVENT_MCOVERAGE 0x040000
|
||
|
#define RUBY_EVENT_BCOVERAGE 0x080000
|
||
|
/* internal events */
|
||
|
#define RUBY_INTERNAL_EVENT_SWITCH 0x040000
|
||
| iseq.c | ||
|---|---|---|
|
RUBY_MARK_UNLESS_NULL(iseq->klass);
|
||
|
RUBY_MARK_UNLESS_NULL(iseq->coverage);
|
||
|
RUBY_MARK_UNLESS_NULL(iseq->method_coverage);
|
||
|
RUBY_MARK_UNLESS_NULL(iseq->branch_coverage);
|
||
|
RUBY_MARK_UNLESS_NULL(iseq->orig);
|
||
|
if (iseq->compile_data != 0) {
|
||
| ... | ... | |
|
RB_OBJ_WRITE(iseq->self, &iseq->coverage, Qfalse);
|
||
|
RB_OBJ_WRITE(iseq->self, &iseq->method_coverage, Qfalse);
|
||
|
RB_OBJ_WRITE(iseq->self, &iseq->branch_coverage, Qfalse);
|
||
|
if (!GET_THREAD()->parse_in_eval) {
|
||
|
VALUE coverages = rb_get_coverages();
|
||
|
if (RTEST(coverages)) {
|
||
| ... | ... | |
|
rb_hash_lookup(rb_hash_lookup(coverages, path), ID2SYM(rb_intern("lines"))));
|
||
|
RB_OBJ_WRITE(iseq->self, &iseq->method_coverage,
|
||
|
rb_hash_lookup(rb_hash_lookup(coverages, path), ID2SYM(rb_intern("methods"))));
|
||
|
RB_OBJ_WRITE(iseq->self, &iseq->branch_coverage,
|
||
|
rb_hash_lookup(rb_hash_lookup(coverages, path), ID2SYM(rb_intern("branches"))));
|
||
|
if (NIL_P(iseq->coverage)) RB_OBJ_WRITE(iseq->self, &iseq->coverage, Qfalse);
|
||
|
if (NIL_P(iseq->method_coverage)) RB_OBJ_WRITE(iseq->self, &iseq->method_coverage, Qfalse);
|
||
|
}
|
||
| parse.y | ||
|---|---|---|
|
VALUE lines = rb_ary_new2(n);
|
||
|
VALUE rb_file_coverage = rb_hash_new();
|
||
|
VALUE methods = rb_hash_new();
|
||
|
VALUE branches = rb_hash_new();
|
||
|
int i;
|
||
|
RBASIC_CLEAR_CLASS(lines);
|
||
|
for (i = 0; i < n; i++) RARRAY_ASET(lines, i, Qnil);
|
||
|
RARRAY(lines)->as.heap.len = n;
|
||
|
rb_hash_aset(rb_file_coverage, ID2SYM(rb_intern("lines")), lines);
|
||
|
rb_hash_aset(rb_file_coverage, ID2SYM(rb_intern("methods")), methods);
|
||
|
rb_hash_aset(rb_file_coverage, ID2SYM(rb_intern("branches")), branches);
|
||
|
rb_hash_aset(coverages, fname, rb_file_coverage);
|
||
|
return lines;
|
||
|
}
|
||
| thread.c | ||
|---|---|---|
|
}
|
||
|
static void
|
||
|
update_method_coverage(rb_event_flag_t event, VALUE proc, VALUE self, ID id, VALUE klass)
|
||
|
update_detailed_coverage(rb_event_flag_t event, VALUE proc, VALUE self, ID id, VALUE klass)
|
||
|
{
|
||
|
VALUE method_coverage = GET_THREAD()->cfp->iseq->method_coverage;
|
||
|
if (method_coverage) {
|
||
|
long line = rb_sourceline();
|
||
|
VALUE info = rb_hash_lookup(method_coverage, LONG2FIX(line));
|
||
|
VALUE coverage;
|
||
|
if (event == RUBY_EVENT_MCOVERAGE) {
|
||
|
coverage = GET_THREAD()->cfp->iseq->method_coverage;
|
||
|
} else if (event == RUBY_EVENT_BCOVERAGE) {
|
||
|
coverage = GET_THREAD()->cfp->iseq->branch_coverage;
|
||
|
} else {
|
||
|
rb_raise(rb_eArgError, "unknown detailed coverage event");
|
||
|
}
|
||
|
if (coverage) {
|
||
|
VALUE line = LONG2FIX(rb_sourceline());
|
||
|
VALUE count = rb_hash_lookup(coverage, line);
|
||
|
if (count == Qnil) {
|
||
|
return;
|
||
|
}
|
||
|
long count = FIX2LONG(info) + 1;
|
||
|
rb_hash_aset(method_coverage, LONG2FIX(line), LONG2FIX(count));
|
||
|
rb_hash_aset(coverage, line, LONG2FIX(FIX2LONG(count) + 1));
|
||
|
}
|
||
|
}
|
||
| ... | ... | |
|
{
|
||
|
GET_VM()->coverages = coverages;
|
||
|
rb_add_event_hook(update_coverage, RUBY_EVENT_COVERAGE, Qnil);
|
||
|
rb_add_event_hook(update_method_coverage, RUBY_EVENT_MCOVERAGE, Qnil);
|
||
|
rb_add_event_hook(update_detailed_coverage, RUBY_EVENT_MCOVERAGE, Qnil);
|
||
|
rb_add_event_hook(update_detailed_coverage, RUBY_EVENT_BCOVERAGE, Qnil);
|
||
|
}
|
||
|
void
|
||
| vm_core.h | ||
|---|---|---|
|
const VALUE mark_ary; /* Array: includes operands which should be GC marked */
|
||
|
const VALUE coverage; /* coverage array */
|
||
|
const VALUE method_coverage; /* method coverage array */
|
||
|
const VALUE branch_coverage; /* branch coverage array */
|
||
|
/* insn info, must be freed */
|
||
|
struct iseq_line_info_entry *line_info_table;
|
||
|
-
|
||
| compile.c | ||
|---|---|---|
|
ADD_INSNL(ret, line, jump, end_label);
|
||
|
ADD_LABEL(ret, else_label);
|
||
|
if (node->nd_else)
|
||
|
/* do not trace elsif node */
|
||
|
if (node->nd_else && nd_type(node->nd_else) != NODE_IF)
|
||
|
ADD_COVERAGE_TRACE(ret, nd_line(node->nd_else), RUBY_EVENT_BRANCH);
|
||
|
ADD_SEQ(ret, else_seq);
|
||
| ext/coverage/coverage.c | ||
|---|---|---|
|
VALUE path = (VALUE)key;
|
||
|
VALUE coverage = (VALUE)val;
|
||
|
VALUE coverages = (VALUE)h;
|
||
|
VALUE line_coverage = rb_hash_lookup(coverage, ID2SYM(rb_intern("lines")));
|
||
|
VALUE method_coverage = rb_hash_lookup(coverage, ID2SYM(rb_intern("methods")));
|
||
|
VALUE branch_coverage = rb_hash_lookup(coverage, ID2SYM(rb_intern("branches")));
|
||
|
line_coverage = rb_ary_dup(line_coverage);
|
||
|
method_coverage = rb_hash_dup(method_coverage);
|
||
|
branch_coverage = rb_hash_dup(branch_coverage);
|
||
|
rb_ary_clear(rb_hash_lookup(coverage, ID2SYM(rb_intern("lines"))));
|
||
|
rb_hash_clear(rb_hash_lookup(coverage, ID2SYM(rb_intern("methods"))));
|
||
|
rb_hash_clear(rb_hash_lookup(coverage, ID2SYM(rb_intern("branches"))));
|
||
|
coverage = rb_hash_dup(coverage);
|
||
|
rb_hash_clear((VALUE)val);
|
||
|
rb_hash_freeze(line_coverage);
|
||
|
rb_ary_freeze(line_coverage);
|
||
|
rb_hash_freeze(method_coverage);
|
||
|
rb_hash_freeze(branch_coverage);
|
||
|
rb_hash_aset(coverage, ID2SYM(rb_intern("lines")), line_coverage);
|
||
|
rb_hash_aset(coverage, ID2SYM(rb_intern("methods")), method_coverage);
|
||
|
rb_hash_aset(coverage, ID2SYM(rb_intern("branches")), branch_coverage);
|
||
|
rb_ary_freeze(coverage);
|
||
|
rb_hash_aset(coverages, path, coverage);
|
||
|
return ST_CONTINUE;
|
||
| iseq.c | ||
|---|---|---|
|
rb_hash_lookup(rb_hash_lookup(coverages, path), ID2SYM(rb_intern("branches"))));
|
||
|
if (NIL_P(iseq->coverage)) RB_OBJ_WRITE(iseq->self, &iseq->coverage, Qfalse);
|
||
|
if (NIL_P(iseq->method_coverage)) RB_OBJ_WRITE(iseq->self, &iseq->method_coverage, Qfalse);
|
||
|
if (NIL_P(iseq->branch_coverage)) RB_OBJ_WRITE(iseq->self, &iseq->branch_coverage, Qfalse);
|
||
|
}
|
||
|
}
|
||
| test/coverage/test_branch_coverage.rb | ||
|---|---|---|
|
require "test/unit"
|
||
|
require "coverage"
|
||
|
require "tmpdir"
|
||
|
class TestBranchCoverage < Test::Unit::TestCase
|
||
|
def test_branch_coverage
|
||
|
loaded_features = $".dup
|
||
|
Dir.mktmpdir {|tmp|
|
||
|
Dir.chdir(tmp) {
|
||
|
File.open("test.rb", "w") do |f|
|
||
|
f.puts <<-EOS
|
||
|
if 2+2 == 4
|
||
|
:ok
|
||
|
end
|
||
|
:ok unless [].size > 0
|
||
|
:bad unless [].size == 0
|
||
|
ary = [1,2,3]
|
||
|
if ary.include? 4
|
||
|
:bad
|
||
|
elsif ary.include? 5
|
||
|
:also_bad
|
||
|
else
|
||
|
:good
|
||
|
end
|
||
|
EOS
|
||
|
end
|
||
|
Coverage.start
|
||
|
require tmp + '/test.rb'
|
||
|
branch_coverage = Coverage.result[tmp + '/test.rb'][:branches]
|
||
|
assert_equal 6, branch_coverage.size
|
||
|
assert_equal 1, branch_coverage[2]
|
||
|
assert_equal 1, branch_coverage[5]
|
||
|
assert_equal 0, branch_coverage[6]
|
||
|
assert_equal 0, branch_coverage[10]
|
||
|
assert_equal 0, branch_coverage[12]
|
||
|
assert_equal 1, branch_coverage[14]
|
||
|
}
|
||
|
}
|
||
|
ensure
|
||
|
$".replace loaded_features
|
||
|
end
|
||
|
end
|
||
| test/coverage/test_coverage.rb | ||
|---|---|---|
|
def test_result_without_start
|
||
|
assert_raise(RuntimeError) {Coverage.result}
|
||
|
end
|
||
|
def test_result_with_nothing
|
||
|
Coverage.start
|
||
|
result = Coverage.result
|
||
|
assert_kind_of(Hash, result)
|
||
|
assert(! result.empty?)
|
||
|
result.each do |key, val|
|
||
|
assert_kind_of(String, key)
|
||
|
assert_kind_of(Array, val)
|
||
|
assert_kind_of(Hash, val)
|
||
|
assert_kind_of(Array, val[:lines])
|
||
|
assert_kind_of(Hash, val[:methods])
|
||
|
assert_kind_of(Hash, val[:branches])
|
||
|
end
|
||
|
end
|
||
| ... | ... | |
|
Coverage.start
|
||
|
require tmp + '/test.rb'
|
||
|
assert_equal 3, Coverage.result[tmp + '/test.rb'].size
|
||
|
assert_equal 3, Coverage.result[tmp + '/test.rb'][:lines].size
|
||
|
Coverage.start
|
||
|
coverage_test_method
|
||
|
assert_equal 0, Coverage.result[tmp + '/test.rb'].size
|
||
|
assert_equal 0, Coverage.result[tmp + '/test.rb'][:lines].size
|
||
|
}
|
||
|
}
|
||
|
ensure
|
||
| ... | ... | |
|
Coverage.start
|
||
|
require tmp + '/test.rb'
|
||
|
assert_equal 10003, Coverage.result[tmp + '/test.rb'].size
|
||
|
assert_equal 10003, Coverage.result[tmp + '/test.rb'][:lines].size
|
||
|
}
|
||
|
}
|
||
|
ensure
|
||
| test/coverage/test_method_coverage.rb | ||
|---|---|---|
|
require "test/unit"
|
||
|
require "coverage"
|
||
|
require "tmpdir"
|
||
|
class TestMethodCoverage < Test::Unit::TestCase
|
||
|
def test_method_coverage
|
||
|
loaded_features = $".dup
|
||
|
Dir.mktmpdir {|tmp|
|
||
|
Dir.chdir(tmp) {
|
||
|
File.open("test.rb", "w") do |f|
|
||
|
f.puts <<-EOS
|
||
|
def method_one
|
||
|
:one
|
||
|
end
|
||
|
def method_two
|
||
|
:two
|
||
|
end
|
||
|
method_two; method_two
|
||
|
EOS
|
||
|
end
|
||
|
Coverage.start
|
||
|
require tmp + '/test.rb'
|
||
|
method_coverage = Coverage.result[tmp + '/test.rb'][:methods]
|
||
|
assert_equal 2, method_coverage.size
|
||
|
assert_equal 0, method_coverage[1]
|
||
|
assert_equal 2, method_coverage[5]
|
||
|
}
|
||
|
}
|
||
|
ensure
|
||
|
$".replace loaded_features
|
||
|
end
|
||
|
end
|
||
| thread.c | ||
|---|---|---|
|
{
|
||
|
GET_VM()->coverages = Qfalse;
|
||
|
rb_remove_event_hook(update_coverage);
|
||
|
rb_remove_event_hook(update_detailed_coverage);
|
||
|
}
|
||
|
VALUE
|
||
|
-
|
||
| compile.c | ||
|---|---|---|
|
l1 = NEW_LABEL(line);
|
||
|
ADD_LABEL(body_seq, l1);
|
||
|
if (node->nd_body)
|
||
|
ADD_COVERAGE_TRACE(body_seq, nd_line(node->nd_body), RUBY_EVENT_BRANCH);
|
||
|
ADD_INSN(body_seq, line, pop);
|
||
|
COMPILE_(body_seq, "when body", node->nd_body, poped);
|
||
|
ADD_INSNL(body_seq, line, jump, endlabel);
|
||
| ... | ... | |
|
/* else */
|
||
|
if (node) {
|
||
|
ADD_LABEL(cond_seq, elselabel);
|
||
|
ADD_COVERAGE_TRACE(cond_seq, nd_line(node), RUBY_EVENT_BRANCH);
|
||
|
ADD_INSN(cond_seq, line, pop);
|
||
|
COMPILE_(cond_seq, "else", node, poped);
|
||
|
ADD_INSNL(cond_seq, line, jump, endlabel);
|
||
| ... | ... | |
|
while (node && nd_type(node) == NODE_WHEN) {
|
||
|
LABEL *l1 = NEW_LABEL(line = nd_line(node));
|
||
|
ADD_LABEL(body_seq, l1);
|
||
|
if (node->nd_body)
|
||
|
ADD_COVERAGE_TRACE(body_seq, nd_line(node->nd_body), RUBY_EVENT_BRANCH);
|
||
|
COMPILE_(body_seq, "when", node->nd_body, poped);
|
||
|
ADD_INSNL(body_seq, line, jump, endlabel);
|
||
| ... | ... | |
|
node = node->nd_next;
|
||
|
}
|
||
|
/* else */
|
||
|
ADD_COVERAGE_TRACE(ret, nd_line(node), RUBY_EVENT_BRANCH);
|
||
|
COMPILE_(ret, "else", node, poped);
|
||
|
ADD_INSNL(ret, nd_line(orig_node), jump, endlabel);
|
||
| ... | ... | |
|
debugp_param("defs/iseq", iseqval);
|
||
|
ADD_COVERAGE_TRACE(ret, nd_line(node), RUBY_EVENT_DEFN);
|
||
|
ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE));
|
||
|
COMPILE(ret, "defs: recv", node->nd_recv);
|
||
|
ADD_INSN1(ret, line, putobject, ID2SYM(node->nd_mid));
|
||
| test/coverage/test_branch_coverage.rb | ||
|---|---|---|
|
require "tmpdir"
|
||
|
class TestBranchCoverage < Test::Unit::TestCase
|
||
|
def test_branch_coverage
|
||
|
def test_if_else_coverage
|
||
|
loaded_features = $".dup
|
||
|
Dir.mktmpdir {|tmp|
|
||
| ... | ... | |
|
ensure
|
||
|
$".replace loaded_features
|
||
|
end
|
||
|
def test_when_coverage
|
||
|
loaded_features = $".dup
|
||
|
Dir.mktmpdir {|tmp|
|
||
|
Dir.chdir(tmp) {
|
||
|
File.open("test.rb", "w") do |f|
|
||
|
f.puts <<-EOS
|
||
|
case
|
||
|
when 2 + 2 == 5
|
||
|
x = :bad
|
||
|
x = :math
|
||
|
when 2 + 2 == 4
|
||
|
x = :good
|
||
|
else
|
||
|
x = :also_bad
|
||
|
end
|
||
|
case [1,2,3]
|
||
|
when String
|
||
|
:string?
|
||
|
else
|
||
|
:else
|
||
|
end
|
||
|
EOS
|
||
|
end
|
||
|
Coverage.start
|
||
|
require tmp + '/test.rb'
|
||
|
branch_coverage = Coverage.result[tmp + '/test.rb'][:branches]
|
||
|
assert_equal 5, branch_coverage.size
|
||
|
assert_equal 0, branch_coverage[3]
|
||
|
assert_equal 1, branch_coverage[6]
|
||
|
assert_equal 0, branch_coverage[8]
|
||
|
assert_equal 0, branch_coverage[13]
|
||
|
assert_equal 1, branch_coverage[15]
|
||
|
}
|
||
|
}
|
||
|
ensure
|
||
|
$".replace loaded_features
|
||
|
end
|
||
|
end
|
||
|
-
|
||
| compile.c | ||
|---|---|---|
|
iseq->compile_data->last_coverable_line = (line); \
|
||
|
ADD_INSN1((seq), (line), trace, INT2FIX(RUBY_EVENT_COVERAGE)); \
|
||
|
} \
|
||
|
if ((event) == RUBY_EVENT_CALL && iseq->method_coverage && \
|
||
|
(line) != iseq->compile_data->last_coverable_line) { \
|
||
|
iseq->compile_data->last_coverable_line = (line); \
|
||
|
ADD_INSN1((seq), (line), trace, INT2FIX(RUBY_EVENT_MCOVERAGE)); \
|
||
|
} \
|
||
|
if (iseq->compile_data->option->trace_instruction) { \
|
||
|
ADD_INSN1((seq), (line), trace, INT2FIX(event)); \
|
||
|
} \
|
||
|
} while (0)
|
||
|
#define ADD_COVERAGE_TRACE(seq, line, event) \
|
||
|
#define ADD_METHOD_COVERAGE_TRACE(seq, line, event, end_line) \
|
||
|
do { \
|
||
|
if ((event) == RUBY_EVENT_DEFN && iseq->method_coverage) { \
|
||
|
rb_hash_aset(iseq->method_coverage, LONG2FIX(line), INT2FIX(0)); \
|
||
|
} \
|
||
|
if ((event) == RUBY_EVENT_CALL && iseq->method_coverage) { \
|
||
|
ADD_INSN1((seq), (line), trace, INT2FIX(RUBY_EVENT_MCOVERAGE)); \
|
||
|
} \
|
||
|
} while (0)
|
||
|
#define ADD_BRANCH_COVERAGE_TRACE(seq, line, event) \
|
||
|
do { \
|
||
|
if ((event) == RUBY_EVENT_BRANCH && iseq->branch_coverage) { \
|
||
|
rb_hash_aset(iseq->branch_coverage, LONG2FIX(line), INT2FIX(0)); \
|
||
|
ADD_INSN1((seq), (line), trace, INT2FIX(RUBY_EVENT_BCOVERAGE)); \
|
||
| ... | ... | |
|
case ISEQ_TYPE_METHOD:
|
||
|
{
|
||
|
ADD_TRACE(ret, FIX2INT(iseq->location.first_lineno), RUBY_EVENT_CALL);
|
||
|
ADD_METHOD_COVERAGE_TRACE(ret, FIX2INT(iseq->location.first_lineno), RUBY_EVENT_CALL, nd_line(node));
|
||
|
COMPILE(ret, "scoped node", node->nd_body);
|
||
|
ADD_TRACE(ret, nd_line(node), RUBY_EVENT_RETURN);
|
||
|
break;
|
||
| ... | ... | |
|
ADD_LABEL(ret, then_label);
|
||
|
if (node->nd_body)
|
||
|
ADD_COVERAGE_TRACE(ret, nd_line(node->nd_body), RUBY_EVENT_BRANCH);
|
||
|
ADD_BRANCH_COVERAGE_TRACE(ret, nd_line(node->nd_body), RUBY_EVENT_BRANCH);
|
||
|
ADD_SEQ(ret, then_seq);
|
||
|
ADD_INSNL(ret, line, jump, end_label);
|
||
|
ADD_LABEL(ret, else_label);
|
||
|
/* do not trace elsif node */
|
||
|
if (node->nd_else && nd_type(node->nd_else) != NODE_IF)
|
||
|
ADD_COVERAGE_TRACE(ret, nd_line(node->nd_else), RUBY_EVENT_BRANCH);
|
||
|
ADD_BRANCH_COVERAGE_TRACE(ret, nd_line(node->nd_else), RUBY_EVENT_BRANCH);
|
||
|
ADD_SEQ(ret, else_seq);
|
||
|
ADD_LABEL(ret, end_label);
|
||
| ... | ... | |
|
l1 = NEW_LABEL(line);
|
||
|
ADD_LABEL(body_seq, l1);
|
||
|
if (node->nd_body)
|
||
|
ADD_COVERAGE_TRACE(body_seq, nd_line(node->nd_body), RUBY_EVENT_BRANCH);
|
||
|
ADD_BRANCH_COVERAGE_TRACE(body_seq, nd_line(node->nd_body), RUBY_EVENT_BRANCH);
|
||
|
ADD_INSN(body_seq, line, pop);
|
||
|
COMPILE_(body_seq, "when body", node->nd_body, poped);
|
||
|
ADD_INSNL(body_seq, line, jump, endlabel);
|
||
| ... | ... | |
|
/* else */
|
||
|
if (node) {
|
||
|
ADD_LABEL(cond_seq, elselabel);
|
||
|
ADD_COVERAGE_TRACE(cond_seq, nd_line(node), RUBY_EVENT_BRANCH);
|
||
|
ADD_BRANCH_COVERAGE_TRACE(cond_seq, nd_line(node), RUBY_EVENT_BRANCH);
|
||
|
ADD_INSN(cond_seq, line, pop);
|
||
|
COMPILE_(cond_seq, "else", node, poped);
|
||
|
ADD_INSNL(cond_seq, line, jump, endlabel);
|
||
| ... | ... | |
|
LABEL *l1 = NEW_LABEL(line = nd_line(node));
|
||
|
ADD_LABEL(body_seq, l1);
|
||
|
if (node->nd_body)
|
||
|
ADD_COVERAGE_TRACE(body_seq, nd_line(node->nd_body), RUBY_EVENT_BRANCH);
|
||
|
ADD_BRANCH_COVERAGE_TRACE(body_seq, nd_line(node->nd_body), RUBY_EVENT_BRANCH);
|
||
|
COMPILE_(body_seq, "when", node->nd_body, poped);
|
||
|
ADD_INSNL(body_seq, line, jump, endlabel);
|
||
| ... | ... | |
|
node = node->nd_next;
|
||
|
}
|
||
|
/* else */
|
||
|
ADD_COVERAGE_TRACE(ret, nd_line(node), RUBY_EVENT_BRANCH);
|
||
|
ADD_BRANCH_COVERAGE_TRACE(ret, nd_line(node), RUBY_EVENT_BRANCH);
|
||
|
COMPILE_(ret, "else", node, poped);
|
||
|
ADD_INSNL(ret, nd_line(orig_node), jump, endlabel);
|
||
| ... | ... | |
|
debugp_param("defn/iseq", iseqval);
|
||
|
ADD_COVERAGE_TRACE(ret, nd_line(node), RUBY_EVENT_DEFN);
|
||
|
ADD_METHOD_COVERAGE_TRACE(ret, nd_line(node), RUBY_EVENT_DEFN, nd_line(node->nd_defn));
|
||
|
ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE));
|
||
|
ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_CBASE));
|
||
|
ADD_INSN1(ret, line, putobject, ID2SYM(node->nd_mid));
|
||
| ... | ... | |
|
debugp_param("defs/iseq", iseqval);
|
||
|
ADD_COVERAGE_TRACE(ret, nd_line(node), RUBY_EVENT_DEFN);
|
||
|
ADD_METHOD_COVERAGE_TRACE(ret, nd_line(node), RUBY_EVENT_DEFN, nd_line(node->nd_defn));
|
||
|
ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE));
|
||
|
COMPILE(ret, "defs: recv", node->nd_recv);
|
||
|
ADD_INSN1(ret, line, putobject, ID2SYM(node->nd_mid));
|
||
|
-
|
||
| compile.c | ||
|---|---|---|
|
#define ADD_TRACE(seq, line, event) \
|
||
|
do { \
|
||
|
if ((event) == RUBY_EVENT_LINE && iseq->coverage && \
|
||
|
if ((event) == RUBY_EVENT_LINE && iseq->coverage->lines && \
|
||
|
(line) != iseq->compile_data->last_coverable_line) { \
|
||
|
RARRAY_ASET(iseq->coverage, (line) - 1, INT2FIX(0)); \
|
||
|
RARRAY_ASET(iseq->coverage->lines, (line) - 1, INT2FIX(0)); \
|
||
|
iseq->compile_data->last_coverable_line = (line); \
|
||
|
ADD_INSN1((seq), (line), trace, INT2FIX(RUBY_EVENT_COVERAGE)); \
|
||
|
} \
|
||
| ... | ... | |
|
#define ADD_METHOD_COVERAGE_TRACE(seq, line, event, end_line) \
|
||
|
do { \
|
||
|
if ((event) == RUBY_EVENT_DEFN && iseq->method_coverage) { \
|
||
|
rb_hash_aset(iseq->method_coverage, LONG2FIX(line), INT2FIX(0)); \
|
||
|
if ((event) == RUBY_EVENT_DEFN && iseq->coverage->methods) { \
|
||
|
rb_hash_aset(iseq->coverage->methods, LONG2FIX(line), INT2FIX(0)); \
|
||
|
} \
|
||
|
if ((event) == RUBY_EVENT_CALL && iseq->method_coverage) { \
|
||
|
if ((event) == RUBY_EVENT_CALL && iseq->coverage->methods) { \
|
||
|
ADD_INSN1((seq), (line), trace, INT2FIX(RUBY_EVENT_MCOVERAGE)); \
|
||
|
} \
|
||
|
} while (0)
|
||
|
#define ADD_BRANCH_COVERAGE_TRACE(seq, line, event) \
|
||
|
do { \
|
||
|
if ((event) == RUBY_EVENT_BRANCH && iseq->branch_coverage) { \
|
||
|
rb_hash_aset(iseq->branch_coverage, LONG2FIX(line), INT2FIX(0)); \
|
||
|
if ((event) == RUBY_EVENT_BRANCH && iseq->coverage->branches) { \
|
||
|
rb_hash_aset(iseq->coverage->branches, LONG2FIX(line), INT2FIX(0)); \
|
||
|
ADD_INSN1((seq), (line), trace, INT2FIX(RUBY_EVENT_BCOVERAGE)); \
|
||
|
} \
|
||
|
} while (0)
|
||
| iseq.c | ||
|---|---|---|
|
RUBY_MARK_UNLESS_NULL((VALUE)iseq->cref_stack);
|
||
|
RUBY_MARK_UNLESS_NULL(iseq->klass);
|
||
|
RUBY_MARK_UNLESS_NULL(iseq->coverage);
|
||
|
RUBY_MARK_UNLESS_NULL(iseq->method_coverage);
|
||
|
RUBY_MARK_UNLESS_NULL(iseq->branch_coverage);
|
||
|
RUBY_MARK_UNLESS_NULL(iseq->orig);
|
||
|
if (iseq->coverage != 0) {
|
||
|
struct rb_coverage_struct *const coverage = iseq->coverage;
|
||
|
RUBY_MARK_UNLESS_NULL(coverage->lines);
|
||
|
RUBY_MARK_UNLESS_NULL(coverage->methods);
|
||
|
RUBY_MARK_UNLESS_NULL(coverage->branches);
|
||
|
}
|
||
|
if (iseq->compile_data != 0) {
|
||
|
struct iseq_compile_data *const compile_data = iseq->compile_data;
|
||
|
RUBY_MARK_UNLESS_NULL(compile_data->mark_ary);
|
||
| ... | ... | |
|
iseq->compile_data->option = option;
|
||
|
iseq->compile_data->last_coverable_line = -1;
|
||
|
RB_OBJ_WRITE(iseq->self, &iseq->coverage, Qfalse);
|
||
|
RB_OBJ_WRITE(iseq->self, &iseq->method_coverage, Qfalse);
|
||
|
RB_OBJ_WRITE(iseq->self, &iseq->branch_coverage, Qfalse);
|
||
|
iseq->coverage = ALLOC(struct rb_coverage_struct);
|
||
|
RB_OBJ_WRITE(iseq->self, &iseq->coverage->lines, Qfalse);
|
||
|
RB_OBJ_WRITE(iseq->self, &iseq->coverage->methods, Qfalse);
|
||
|
RB_OBJ_WRITE(iseq->self, &iseq->coverage->branches, Qfalse);
|
||
|
if (!GET_THREAD()->parse_in_eval) {
|
||
|
VALUE coverages = rb_get_coverages();
|
||
|
if (RTEST(coverages)) {
|
||
|
RB_OBJ_WRITE(iseq->self, &iseq->coverage,
|
||
|
RB_OBJ_WRITE(iseq->self, &iseq->coverage->lines,
|
||
|
rb_hash_lookup(rb_hash_lookup(coverages, path), ID2SYM(rb_intern("lines"))));
|
||
|
RB_OBJ_WRITE(iseq->self, &iseq->method_coverage,
|
||
|
RB_OBJ_WRITE(iseq->self, &iseq->coverage->methods,
|
||
|
rb_hash_lookup(rb_hash_lookup(coverages, path), ID2SYM(rb_intern("methods"))));
|
||
|
RB_OBJ_WRITE(iseq->self, &iseq->branch_coverage,
|
||
|
RB_OBJ_WRITE(iseq->self, &iseq->coverage->branches,
|
||
|
rb_hash_lookup(rb_hash_lookup(coverages, path), ID2SYM(rb_intern("branches"))));
|
||
|
if (NIL_P(iseq->coverage)) RB_OBJ_WRITE(iseq->self, &iseq->coverage, Qfalse);
|
||
|
if (NIL_P(iseq->method_coverage)) RB_OBJ_WRITE(iseq->self, &iseq->method_coverage, Qfalse);
|
||
|
if (NIL_P(iseq->branch_coverage)) RB_OBJ_WRITE(iseq->self, &iseq->branch_coverage, Qfalse);
|
||
|
if (NIL_P(iseq->coverage->lines)) RB_OBJ_WRITE(iseq->self, &iseq->coverage->lines, Qfalse);
|
||
|
if (NIL_P(iseq->coverage->methods)) RB_OBJ_WRITE(iseq->self, &iseq->coverage->methods, Qfalse);
|
||
|
if (NIL_P(iseq->coverage->branches)) RB_OBJ_WRITE(iseq->self, &iseq->coverage->branches, Qfalse);
|
||
|
}
|
||
|
}
|
||
| thread.c | ||
|---|---|---|
|
static void
|
||
|
update_coverage(rb_event_flag_t event, VALUE proc, VALUE self, ID id, VALUE klass)
|
||
|
{
|
||
|
VALUE coverage = GET_THREAD()->cfp->iseq->coverage;
|
||
|
VALUE coverage = GET_THREAD()->cfp->iseq->coverage->lines;
|
||
|
if (coverage && RBASIC(coverage)->klass == 0) {
|
||
|
long line = rb_sourceline() - 1;
|
||
|
long count;
|
||
| ... | ... | |
|
{
|
||
|
VALUE coverage;
|
||
|
if (event == RUBY_EVENT_MCOVERAGE) {
|
||
|
coverage = GET_THREAD()->cfp->iseq->method_coverage;
|
||
|
coverage = GET_THREAD()->cfp->iseq->coverage->methods;
|
||
|
} else if (event == RUBY_EVENT_BCOVERAGE) {
|
||
|
coverage = GET_THREAD()->cfp->iseq->branch_coverage;
|
||
|
coverage = GET_THREAD()->cfp->iseq->coverage->branches;
|
||
|
} else {
|
||
|
rb_raise(rb_eArgError, "unknown detailed coverage event");
|
||
|
}
|
||
| vm_core.h | ||
|---|---|---|
|
size_t first_lineno;
|
||
|
} rb_iseq_location_t;
|
||
|
struct rb_coverage_struct {
|
||
|
const VALUE lines;
|
||
|
const VALUE methods;
|
||
|
const VALUE branches;
|
||
|
};
|
||
|
struct rb_iseq_struct;
|
||
|
struct rb_iseq_struct {
|
||
| ... | ... | |
|
VALUE *iseq_encoded; /* encoded iseq */
|
||
|
unsigned long iseq_size;
|
||
|
const VALUE mark_ary; /* Array: includes operands which should be GC marked */
|
||
|
const VALUE coverage; /* coverage array */
|
||
|
const VALUE method_coverage; /* method coverage array */
|
||
|
const VALUE branch_coverage; /* branch coverage array */
|
||
|
struct rb_coverage_struct *coverage;
|
||
|
/* insn info, must be freed */
|
||
|
struct iseq_line_info_entry *line_info_table;
|
||
|
-
|
||
| compile.c | ||
|---|---|---|
|
} \
|
||
|
} while (0)
|
||
|
#define ADD_BRANCH_COVERAGE_TRACE(seq, line, event) \
|
||
|
#define ADD_DECISION_COVERAGE_TRACE(seq, line, event) \
|
||
|
do { \
|
||
|
if ((event) == RUBY_EVENT_BRANCH && iseq->coverage->branches) { \
|
||
|
rb_hash_aset(iseq->coverage->branches, LONG2FIX(line), INT2FIX(0)); \
|
||
|
ADD_INSN1((seq), (line), trace, INT2FIX(RUBY_EVENT_BCOVERAGE)); \
|
||
|
if (((event) == RUBY_EVENT_DECISION_TRUE || (event) == RUBY_EVENT_DECISION_FALSE) \
|
||
|
&& iseq->coverage->decisions) { \
|
||
|
rb_hash_aset(iseq->coverage->decisions, LONG2FIX(line), rb_assoc_new(INT2FIX(0), INT2FIX(0))); \
|
||
|
if ((event) == RUBY_EVENT_DECISION_TRUE) \
|
||
|
ADD_INSN1((seq), (line), trace, INT2FIX(RUBY_EVENT_DCOVERAGE_TRUE)); \
|
||
|
if ((event) == RUBY_EVENT_DECISION_FALSE) \
|
||
|
ADD_INSN1((seq), (line), trace, INT2FIX(RUBY_EVENT_DCOVERAGE_FALSE)); \
|
||
|
} \
|
||
|
} while (0)
|
||
| ... | ... | |
|
ADD_INSN1(cond_seq, nd_line(vals), checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_CASE));
|
||
|
ADD_INSNL(cond_seq, nd_line(val), branchif, l1);
|
||
|
ADD_DECISION_COVERAGE_TRACE(cond_seq, nd_line(vals), RUBY_EVENT_DECISION_FALSE);
|
||
|
vals = vals->nd_next;
|
||
|
}
|
||
|
return only_special_literals;
|
||
| ... | ... | |
|
ADD_SEQ(ret, cond_seq);
|
||
|
ADD_LABEL(ret, then_label);
|
||
|
if (node->nd_body)
|
||
|
ADD_BRANCH_COVERAGE_TRACE(ret, nd_line(node->nd_body), RUBY_EVENT_BRANCH);
|
||
|
ADD_DECISION_COVERAGE_TRACE(ret, nd_line(node), RUBY_EVENT_DECISION_TRUE);
|
||
|
ADD_SEQ(ret, then_seq);
|
||
|
ADD_INSNL(ret, line, jump, end_label);
|
||
|
ADD_LABEL(ret, else_label);
|
||
|
/* do not trace elsif node */
|
||
|
if (node->nd_else && nd_type(node->nd_else) != NODE_IF)
|
||
|
ADD_BRANCH_COVERAGE_TRACE(ret, nd_line(node->nd_else), RUBY_EVENT_BRANCH);
|
||
|
ADD_DECISION_COVERAGE_TRACE(ret, nd_line(node), RUBY_EVENT_DECISION_FALSE);
|
||
|
ADD_SEQ(ret, else_seq);
|
||
|
ADD_LABEL(ret, end_label);
|
||
| ... | ... | |
|
l1 = NEW_LABEL(line);
|
||
|
ADD_LABEL(body_seq, l1);
|
||
|
if (node->nd_body)
|
||
|
ADD_BRANCH_COVERAGE_TRACE(body_seq, nd_line(node->nd_body), RUBY_EVENT_BRANCH);
|
||
|
if (node->nd_head)
|
||
|
ADD_DECISION_COVERAGE_TRACE(body_seq, nd_line(node->nd_head), RUBY_EVENT_DECISION_TRUE);
|
||
|
ADD_INSN(body_seq, line, pop);
|
||
|
COMPILE_(body_seq, "when body", node->nd_body, poped);
|
||
|
ADD_INSNL(body_seq, line, jump, endlabel);
|
||
| ... | ... | |
|
COMPILE(cond_seq, "when/cond splat", vals);
|
||
|
ADD_INSN1(cond_seq, nd_line(vals), checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_CASE | VM_CHECKMATCH_ARRAY));
|
||
|
ADD_INSNL(cond_seq, nd_line(vals), branchif, l1);
|
||
|
ADD_DECISION_COVERAGE_TRACE(cond_seq, nd_line(vals), RUBY_EVENT_DECISION_FALSE);
|
||
|
break;
|
||
|
default:
|
||
|
rb_bug("NODE_CASE: unknown node (%s)",
|
||
| ... | ... | |
|
/* else */
|
||
|
if (node) {
|
||
|
ADD_LABEL(cond_seq, elselabel);
|
||
|
ADD_BRANCH_COVERAGE_TRACE(cond_seq, nd_line(node), RUBY_EVENT_BRANCH);
|
||
|
ADD_INSN(cond_seq, line, pop);
|
||
|
COMPILE_(cond_seq, "else", node, poped);
|
||
|
ADD_INSNL(cond_seq, line, jump, endlabel);
|
||
| ... | ... | |
|
while (node && nd_type(node) == NODE_WHEN) {
|
||
|
LABEL *l1 = NEW_LABEL(line = nd_line(node));
|
||
|
ADD_LABEL(body_seq, l1);
|
||
|
if (node->nd_body)
|
||
|
ADD_BRANCH_COVERAGE_TRACE(body_seq, nd_line(node->nd_body), RUBY_EVENT_BRANCH);
|
||
|
if (node->nd_head)
|
||
|
ADD_DECISION_COVERAGE_TRACE(body_seq, nd_line(node->nd_head), RUBY_EVENT_DECISION_TRUE);
|
||
|
COMPILE_(body_seq, "when", node->nd_body, poped);
|
||
|
ADD_INSNL(body_seq, line, jump, endlabel);
|
||
| ... | ... | |
|
val = vals->nd_head;
|
||
|
COMPILE(ret, "when2", val);
|
||
|
ADD_INSNL(ret, nd_line(val), branchif, l1);
|
||
|
ADD_DECISION_COVERAGE_TRACE(ret, nd_line(vals), RUBY_EVENT_DECISION_FALSE);
|
||
|
vals = vals->nd_next;
|
||
|
}
|
||
|
break;
|
||
| ... | ... | |
|
COMPILE(ret, "when2/cond splat", vals);
|
||
|
ADD_INSN1(ret, nd_line(vals), checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_WHEN | VM_CHECKMATCH_ARRAY));
|
||
|
ADD_INSNL(ret, nd_line(vals), branchif, l1);
|
||
|
ADD_DECISION_COVERAGE_TRACE(ret, nd_line(vals), RUBY_EVENT_DECISION_FALSE);
|
||
|
break;
|
||
|
default:
|
||
|
rb_bug("NODE_WHEN: unknown node (%s)",
|
||
| ... | ... | |
|
node = node->nd_next;
|
||
|
}
|
||
|
/* else */
|
||
|
ADD_BRANCH_COVERAGE_TRACE(ret, nd_line(node), RUBY_EVENT_BRANCH);
|
||
|
COMPILE_(ret, "else", node, poped);
|
||
|
ADD_INSNL(ret, nd_line(orig_node), jump, endlabel);
|
||
| ext/coverage/coverage.c | ||
|---|---|---|
|
VALUE line_coverage = rb_hash_lookup(coverage, ID2SYM(rb_intern("lines")));
|
||
|
VALUE method_coverage = rb_hash_lookup(coverage, ID2SYM(rb_intern("methods")));
|
||
|
VALUE branch_coverage = rb_hash_lookup(coverage, ID2SYM(rb_intern("branches")));
|
||
|
VALUE decision_coverage = rb_hash_lookup(coverage, ID2SYM(rb_intern("decisions")));
|
||
|
line_coverage = rb_ary_dup(line_coverage);
|
||
|
method_coverage = rb_hash_dup(method_coverage);
|
||
|
branch_coverage = rb_hash_dup(branch_coverage);
|
||
|
decision_coverage = rb_hash_dup(decision_coverage);
|
||
|
rb_ary_clear(rb_hash_lookup(coverage, ID2SYM(rb_intern("lines"))));
|
||
|
rb_hash_clear(rb_hash_lookup(coverage, ID2SYM(rb_intern("methods"))));
|
||
|
rb_hash_clear(rb_hash_lookup(coverage, ID2SYM(rb_intern("branches"))));
|
||
|
rb_hash_clear(rb_hash_lookup(coverage, ID2SYM(rb_intern("decisions"))));
|
||
|
coverage = rb_hash_dup(coverage);
|
||
|
rb_ary_freeze(line_coverage);
|
||
|
rb_hash_freeze(method_coverage);
|
||
|
rb_hash_freeze(branch_coverage);
|
||
|
rb_hash_freeze(decision_coverage);
|
||
|
rb_hash_aset(coverage, ID2SYM(rb_intern("lines")), line_coverage);
|
||
|
rb_hash_aset(coverage, ID2SYM(rb_intern("methods")), method_coverage);
|
||
|
rb_hash_aset(coverage, ID2SYM(rb_intern("branches")), branch_coverage);
|
||
|
rb_hash_aset(coverage, ID2SYM(rb_intern("decisions")), decision_coverage);
|
||
|
rb_ary_freeze(coverage);
|
||
|
rb_hash_aset(coverages, path, coverage);
|
||
| include/ruby/ruby.h | ||
|---|---|---|
|
#define RUBY_EVENT_C_RETURN 0x0040
|
||
|
#define RUBY_EVENT_RAISE 0x0080
|
||
|
#define RUBY_EVENT_DEFN 0x0090
|
||
|
#define RUBY_EVENT_BRANCH 0x00a0
|
||
|
#define RUBY_EVENT_DECISION_TRUE 0x00a0
|
||
|
#define RUBY_EVENT_DECISION_FALSE 0x00b0
|
||
|
#define RUBY_EVENT_ALL 0x00ff
|
||
|
/* for TracePoint extended events */
|
||
| ... | ... | |
|
#define RUBY_EVENT_SPECIFIED_LINE 0x010000
|
||
|
#define RUBY_EVENT_COVERAGE 0x020000
|
||
|
#define RUBY_EVENT_MCOVERAGE 0x040000
|
||
|
#define RUBY_EVENT_BCOVERAGE 0x080000
|
||
|
#define RUBY_EVENT_DCOVERAGE_TRUE 0x080000
|
||
|
#define RUBY_EVENT_DCOVERAGE_FALSE 0x2000000
|
||
|
/* internal events */
|
||
|
#define RUBY_INTERNAL_EVENT_SWITCH 0x040000
|
||
| iseq.c | ||
|---|---|---|
|
struct rb_coverage_struct *const coverage = iseq->coverage;
|
||
|
RUBY_MARK_UNLESS_NULL(coverage->lines);
|
||
|
RUBY_MARK_UNLESS_NULL(coverage->methods);
|
||
|
RUBY_MARK_UNLESS_NULL(coverage->branches);
|
||
|
RUBY_MARK_UNLESS_NULL(coverage->decisions);
|
||
|
}
|
||
|
if (iseq->compile_data != 0) {
|
||
| ... | ... | |
|
iseq->coverage = ALLOC(struct rb_coverage_struct);
|
||
|
RB_OBJ_WRITE(iseq->self, &iseq->coverage->lines, Qfalse);
|
||
|
RB_OBJ_WRITE(iseq->self, &iseq->coverage->methods, Qfalse);
|
||
|
RB_OBJ_WRITE(iseq->self, &iseq->coverage->branches, Qfalse);
|
||
|
RB_OBJ_WRITE(iseq->self, &iseq->coverage->decisions, Qfalse);
|
||
|
if (!GET_THREAD()->parse_in_eval) {
|
||
|
VALUE coverages = rb_get_coverages();
|
||
|
if (RTEST(coverages)) {
|
||
| ... | ... | |
|
rb_hash_lookup(rb_hash_lookup(coverages, path), ID2SYM(rb_intern("lines"))));
|
||
|
RB_OBJ_WRITE(iseq->self, &iseq->coverage->methods,
|
||
|
rb_hash_lookup(rb_hash_lookup(coverages, path), ID2SYM(rb_intern("methods"))));
|
||
|
RB_OBJ_WRITE(iseq->self, &iseq->coverage->branches,
|
||
|
rb_hash_lookup(rb_hash_lookup(coverages, path), ID2SYM(rb_intern("branches"))));
|
||
|
RB_OBJ_WRITE(iseq->self, &iseq->coverage->decisions,
|
||
|
rb_hash_lookup(rb_hash_lookup(coverages, path), ID2SYM(rb_intern("decisions"))));
|
||
|
if (NIL_P(iseq->coverage->lines)) RB_OBJ_WRITE(iseq->self, &iseq->coverage->lines, Qfalse);
|
||
|
if (NIL_P(iseq->coverage->methods)) RB_OBJ_WRITE(iseq->self, &iseq->coverage->methods, Qfalse);
|
||
|
if (NIL_P(iseq->coverage->branches)) RB_OBJ_WRITE(iseq->self, &iseq->coverage->branches, Qfalse);
|
||
|
if (NIL_P(iseq->coverage->decisions)) RB_OBJ_WRITE(iseq->self, &iseq->coverage->decisions, Qfalse);
|
||
|
}
|
||
|
}
|
||
| parse.y | ||
|---|---|---|
|
VALUE lines = rb_ary_new2(n);
|
||
|
VALUE rb_file_coverage = rb_hash_new();
|
||
|
VALUE methods = rb_hash_new();
|
||
|
VALUE branches = rb_hash_new();
|
||
|
VALUE decisions = rb_hash_new();
|
||
|
int i;
|
||
|
RBASIC_CLEAR_CLASS(lines);
|
||
|
for (i = 0; i < n; i++) RARRAY_ASET(lines, i, Qnil);
|
||
|
RARRAY(lines)->as.heap.len = n;
|
||
|
rb_hash_aset(rb_file_coverage, ID2SYM(rb_intern("lines")), lines);
|
||
|
rb_hash_aset(rb_file_coverage, ID2SYM(rb_intern("methods")), methods);
|
||
|
rb_hash_aset(rb_file_coverage, ID2SYM(rb_intern("branches")), branches);
|
||
|
rb_hash_aset(rb_file_coverage, ID2SYM(rb_intern("decisions")), decisions);
|
||
|
rb_hash_aset(coverages, fname, rb_file_coverage);
|
||
|
return lines;
|
||
|
}
|
||
| test/coverage/test_branch_coverage.rb | ||
|---|---|---|
|
Coverage.start
|
||
|
require tmp + '/test.rb'
|
||
|
branch_coverage = Coverage.result[tmp + '/test.rb'][:branches]
|
||
|
assert_equal 6, branch_coverage.size
|
||
|
assert_equal 1, branch_coverage[2]
|
||
|
assert_equal 1, branch_coverage[5]
|
||
|
assert_equal 0, branch_coverage[6]
|
||
|
assert_equal 0, branch_coverage[10]
|
||
|
assert_equal 0, branch_coverage[12]
|
||
|
assert_equal 1, branch_coverage[14]
|
||
|
decision_coverage = Coverage.result[tmp + '/test.rb'][:decisions]
|
||
|
assert_equal 5, decision_coverage.size
|
||
|
assert_equal [1,0], decision_coverage[1]
|
||
|
assert_equal [0,1], decision_coverage[5]
|
||
|
assert_equal [1,0], decision_coverage[6]
|
||
|
assert_equal [0,1], decision_coverage[9]
|
||
|
assert_equal [0,1], decision_coverage[11]
|
||
|
}
|
||
|
}
|
||
|
ensure
|
||
| ... | ... | |
|
Dir.chdir(tmp) {
|
||
|
File.open("test.rb", "w") do |f|
|
||
|
f.puts <<-EOS
|
||
|
case
|
||
|
when 2 + 2 == 5
|
||
|
x = :bad
|
||
|
x = :math
|
||
|
when 2 + 2 == 4
|
||
|
x = :good
|
||
|
else
|
||
|
x = :also_bad
|
||
|
def foo(arg)
|
||
|
case
|
||
|
when arg == 1
|
||
|
x = :one
|
||
|
when arg == 2
|
||
|
x = :two
|
||
|
else
|
||
|
x = :none
|
||
|
end
|
||
|
end
|
||
|
foo(2); foo(2); foo(3)
|
||
|
case [1,2,3]
|
||
|
when String
|
||
|
:string?
|
||
|
else
|
||
|
:else
|
||
|
def bar(arg)
|
||
|
case arg
|
||
|
when String
|
||
|
:string?
|
||
|
when 1
|
||
|
:one
|
||
|
else
|
||
|
:else
|
||
|
end
|
||
|
end
|
||
|
bar("BAR"); bar("BAZ"); bar(1); bar(7)
|
||
|
EOS
|
||
|
end
|
||
|
Coverage.start
|
||
|
require tmp + '/test.rb'
|
||
|
decision_coverage = Coverage.result[tmp + '/test.rb'][:decisions]
|
||
|
assert_equal 4, decision_coverage.size
|
||
|
assert_equal [0,3], decision_coverage[3]
|
||
|
assert_equal [2,1], decision_coverage[5]
|
||
|
assert_equal [2,2], decision_coverage[15]
|
||
|
assert_equal [1,1], decision_coverage[17]
|
||
|
}
|
||
|
}
|
||
|
ensure
|
||
|
$".replace loaded_features
|
||
|
end
|
||
|
def test_when_without_else_coverage
|
||
|
loaded_features = $".dup
|
||
|
Dir.mktmpdir {|tmp|
|
||
|
Dir.chdir(tmp) {
|
||
|
File.open("test.rb", "w") do |f|
|
||
|
f.puts <<-EOS
|
||
|
def foo(arg)
|
||
|
case
|
||
|
when arg == 1
|
||
|
x = :one
|
||
|
when arg == 2
|
||
|
x = :two
|
||
|
end
|
||
|
end
|
||
|
foo(2); foo(2); foo(3)
|
||
|
def bar(arg)
|
||
|
case arg
|
||
|
when String
|
||
|
:string?
|
||
|
when 1
|
||
|
:one
|
||
|
end
|
||
|
end
|
||
|
bar("BAR"); bar("BAZ"); bar(1); bar(7)
|
||
|
EOS
|
||
|
end
|
||
|
Coverage.start
|
||
|
require tmp + '/test.rb'
|
||
|
decision_coverage = Coverage.result[tmp + '/test.rb'][:decisions]
|
||
|
assert_equal 4, decision_coverage.size
|
||
|
assert_equal [0,3], decision_coverage[3]
|
||
|
assert_equal [2,1], decision_coverage[5]
|
||
|
assert_equal [2,2], decision_coverage[13]
|
||
|
assert_equal [1,1], decision_coverage[15]
|
||
|
}
|
||
|
}
|
||
|
ensure
|
||
|
$".replace loaded_features
|
||
|
end
|
||
|
def test_when_with_splats_coverage
|
||
|
loaded_features = $".dup
|
||
|
Dir.mktmpdir {|tmp|
|
||
|
Dir.chdir(tmp) {
|
||
|
File.open("test.rb", "w") do |f|
|
||
|
f.puts <<-EOS
|
||
|
def prime?(arg)
|
||
|
primes = [2,3,5,7]
|
||
|
composites = [4,6,8,9]
|
||
|
case arg
|
||
|
when *primes
|
||
|
:prime
|
||
|
when *composites
|
||
|
:composite
|
||
|
end
|
||
|
end
|
||
|
9.times {|i| prime?(i+1) }
|
||
|
EOS
|
||
|
end
|
||
|
Coverage.start
|
||
|
require tmp + '/test.rb'
|
||
|
branch_coverage = Coverage.result[tmp + '/test.rb'][:branches]
|
||
|
assert_equal 5, branch_coverage.size
|
||
|
assert_equal 0, branch_coverage[3]
|
||
|
assert_equal 1, branch_coverage[6]
|
||
- « Previous
- 1
- 2
- 3
- Next »