Project

General

Profile

Feature #9508 » pull-request-511.patch

srawlins (Sam Rawlins), 02/10/2014 06:27 PM

View differences:

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
(1-1/3)