Feature #15022 ยป oneshot-coverage.patch
compile.c | ||
---|---|---|
sp = calc_sp_depth(sp, iobj);
|
||
code_index += insn_data_length(iobj);
|
||
insn_num++;
|
||
if (ISEQ_COVERAGE(iseq) && ISEQ_LINE_COVERAGE(iseq) && (events & RUBY_EVENT_COVERAGE_LINE)) {
|
||
if (ISEQ_COVERAGE(iseq) && ISEQ_LINE_COVERAGE(iseq) &&
|
||
(events & RUBY_EVENT_COVERAGE_LINE) &&
|
||
!(rb_get_coverage_mode() & COVERAGE_TARGET_ONESHOT_LINES)) {
|
||
int line = iobj->insn_info.line_no;
|
||
RARRAY_ASET(ISEQ_LINE_COVERAGE(iseq), line - 1, INT2FIX(0));
|
||
}
|
ext/coverage/coverage.c | ||
---|---|---|
mode |= COVERAGE_TARGET_BRANCHES;
|
||
if (RTEST(rb_hash_lookup(opt, ID2SYM(rb_intern("methods")))))
|
||
mode |= COVERAGE_TARGET_METHODS;
|
||
if (mode == 0) {
|
||
rb_raise(rb_eRuntimeError, "no measuring target is specified");
|
||
}
|
||
if (RTEST(rb_hash_lookup(opt, ID2SYM(rb_intern("oneshot_lines"))))) {
|
||
if (mode & COVERAGE_TARGET_LINES)
|
||
rb_raise(rb_eRuntimeError, "cannot enable lines and oneshot_lines simultaneously");
|
||
mode |= COVERAGE_TARGET_LINES;
|
||
mode |= COVERAGE_TARGET_ONESHOT_LINES;
|
||
}
|
||
}
|
||
if (mode & COVERAGE_TARGET_METHODS) {
|
||
... | ... | |
if (current_mode & COVERAGE_TARGET_LINES) {
|
||
VALUE lines = RARRAY_AREF(coverage, COVERAGE_INDEX_LINES);
|
||
const char *kw = (current_mode & COVERAGE_TARGET_ONESHOT_LINES) ? "oneshot_lines" : "lines";
|
||
lines = rb_ary_dup(lines);
|
||
rb_ary_freeze(lines);
|
||
rb_hash_aset(h, ID2SYM(rb_intern("lines")), lines);
|
||
rb_hash_aset(h, ID2SYM(rb_intern(kw)), lines);
|
||
}
|
||
if (current_mode & COVERAGE_TARGET_BRANCHES) {
|
||
... | ... | |
static VALUE
|
||
rb_coverage_result(VALUE klass)
|
||
{
|
||
VALUE ncoverages = rb_coverage_peek_result(klass);
|
||
VALUE ncoverages;
|
||
ncoverages = rb_coverage_peek_result(klass);
|
||
rb_reset_coverages();
|
||
me2counter = Qnil;
|
||
return ncoverages;
|
||
}
|
||
static int
|
||
clear_me2counter_i(VALUE key, VALUE value, VALUE unused)
|
||
{
|
||
rb_hash_aset(me2counter, key, INT2FIX(0));
|
||
return ST_CONTINUE;
|
||
}
|
||
/*
|
||
* call-seq:
|
||
* Coverage.clear => nil
|
||
*
|
||
* Clears the internal counters of coverage.
|
||
*/
|
||
static VALUE
|
||
rb_coverage_clear(VALUE klass)
|
||
{
|
||
rb_clear_coverages();
|
||
if (!NIL_P(me2counter)) rb_hash_foreach(me2counter, clear_me2counter_i, Qnil);
|
||
return Qnil;
|
||
}
|
||
/*
|
||
* call-seq:
|
||
* Coverage.running? => bool
|
||
... | ... | |
VALUE rb_mCoverage = rb_define_module("Coverage");
|
||
rb_define_module_function(rb_mCoverage, "start", rb_coverage_start, -1);
|
||
rb_define_module_function(rb_mCoverage, "result", rb_coverage_result, 0);
|
||
rb_define_module_function(rb_mCoverage, "clear", rb_coverage_clear, 0);
|
||
rb_define_module_function(rb_mCoverage, "peek_result", rb_coverage_peek_result, 0);
|
||
rb_define_module_function(rb_mCoverage, "running?", rb_coverage_running, 0);
|
||
rb_global_variable(&me2counter);
|
internal.h | ||
---|---|---|
#define COVERAGE_TARGET_LINES 1
|
||
#define COVERAGE_TARGET_BRANCHES 2
|
||
#define COVERAGE_TARGET_METHODS 4
|
||
#define COVERAGE_TARGET_ONESHOT_LINES 8
|
||
VALUE rb_obj_is_mutex(VALUE obj);
|
||
VALUE rb_suppress_tracing(VALUE (*func)(VALUE), VALUE arg);
|
||
void rb_thread_execute_interrupts(VALUE th);
|
||
void rb_clear_trace_func(void);
|
||
VALUE rb_get_coverages(void);
|
||
int rb_get_coverage_mode(void);
|
||
VALUE rb_default_coverage(int);
|
||
VALUE rb_thread_shield_new(void);
|
||
VALUE rb_thread_shield_wait(VALUE self);
|
iseq.c | ||
---|---|---|
VALUE coverages = rb_get_coverages();
|
||
if (RTEST(coverages)) {
|
||
if (ast->line_count >= 0) {
|
||
VALUE coverage = rb_default_coverage(ast->line_count);
|
||
int len = (rb_get_coverage_mode() & COVERAGE_TARGET_ONESHOT_LINES) ? 0 : ast->line_count;
|
||
VALUE coverage = rb_default_coverage(len);
|
||
rb_hash_aset(coverages, path, coverage);
|
||
}
|
||
}
|
||
... | ... | |
}
|
||
}
|
||
void
|
||
rb_iseq_clear_event_flags(const rb_iseq_t *iseq, size_t pos, rb_event_flag_t reset)
|
||
{
|
||
struct iseq_insn_info_entry *entry = (struct iseq_insn_info_entry *)get_insn_info(iseq, pos);
|
||
if (entry) {
|
||
entry->events &= ~reset;
|
||
if (!(entry->events & iseq->aux.trace_events)) {
|
||
void rb_iseq_trace_flag_cleared(const rb_iseq_t *iseq, int pos);
|
||
rb_iseq_trace_flag_cleared(iseq, pos);
|
||
}
|
||
}
|
||
}
|
||
static VALUE
|
||
local_var_name(const rb_iseq_t *diseq, VALUE level, VALUE op)
|
||
{
|
||
... | ... | |
rb_bug("trace_instrument: invalid insn address: %p", (void *)*iseq_encoded_insn);
|
||
}
|
||
void
|
||
rb_iseq_trace_flag_cleared(const rb_iseq_t *iseq, int pos)
|
||
{
|
||
const struct rb_iseq_constant_body *const body = iseq->body;
|
||
VALUE *iseq_encoded = (VALUE *)body->iseq_encoded;
|
||
encoded_iseq_trace_instrument(&iseq_encoded[pos], 0);
|
||
}
|
||
void
|
||
rb_iseq_trace_set(const rb_iseq_t *iseq, rb_event_flag_t turnon_events)
|
||
{
|
test/coverage/test_coverage.rb | ||
---|---|---|
}
|
||
assert_coverage(code, { methods: true }, result)
|
||
end
|
||
def test_oneshot_line_coverage
|
||
result = {
|
||
:oneshot_lines => [2, 6, 10, 12, 17, 18, 25, 20]
|
||
}
|
||
assert_coverage(<<~"end;", { oneshot_lines: true }, result)
|
||
FOO = [
|
||
{ foo: 'bar' }, # 2
|
||
{ bar: 'baz' }
|
||
]
|
||
'some string'.split # 6
|
||
.map(&:length)
|
||
some =
|
||
'value' # 10
|
||
Struct.new( # 12
|
||
:foo,
|
||
:bar
|
||
).new
|
||
class Test # 17
|
||
def foo(bar) # 18
|
||
{
|
||
foo: bar # 20
|
||
}
|
||
end
|
||
end
|
||
Test.new.foo(Object.new) # 25
|
||
end;
|
||
end
|
||
def test_clear_with_lines
|
||
Dir.mktmpdir {|tmp|
|
||
Dir.chdir(tmp) {
|
||
File.open("test.rb", "w") do |f|
|
||
f.puts "def foo(x)"
|
||
f.puts " if x > 0"
|
||
f.puts " :pos"
|
||
f.puts " else"
|
||
f.puts " :non_pos"
|
||
f.puts " end"
|
||
f.puts "end"
|
||
end
|
||
exp = [
|
||
"{:lines=>[1, 0, 0, nil, 0, nil, nil]}",
|
||
"{:lines=>[0, 1, 1, nil, 0, nil, nil]}",
|
||
"{:lines=>[0, 1, 0, nil, 1, nil, nil]}",
|
||
]
|
||
assert_in_out_err(%w[-rcoverage], <<-"end;", exp, [])
|
||
Coverage.start(lines: true)
|
||
tmp = Dir.pwd
|
||
f = tmp + "/test.rb"
|
||
require f
|
||
p Coverage.peek_result[f]
|
||
Coverage.clear
|
||
foo(1)
|
||
p Coverage.peek_result[f]
|
||
Coverage.clear
|
||
foo(-1)
|
||
p Coverage.peek_result[f]
|
||
end;
|
||
}
|
||
}
|
||
end
|
||
def test_clear_with_branches
|
||
Dir.mktmpdir {|tmp|
|
||
Dir.chdir(tmp) {
|
||
File.open("test.rb", "w") do |f|
|
||
f.puts "def foo(x)"
|
||
f.puts " if x > 0"
|
||
f.puts " :pos"
|
||
f.puts " else"
|
||
f.puts " :non_pos"
|
||
f.puts " end"
|
||
f.puts "end"
|
||
end
|
||
exp = [
|
||
"{:branches=>{[:if, 0, 2, 2, 6, 5]=>{[:then, 1, 3, 4, 3, 8]=>0, [:else, 2, 5, 4, 5, 12]=>0}}}",
|
||
"{:branches=>{[:if, 0, 2, 2, 6, 5]=>{[:then, 1, 3, 4, 3, 8]=>1, [:else, 2, 5, 4, 5, 12]=>0}}}",
|
||
"{:branches=>{[:if, 0, 2, 2, 6, 5]=>{[:then, 1, 3, 4, 3, 8]=>0, [:else, 2, 5, 4, 5, 12]=>1}}}",
|
||
"{:branches=>{[:if, 0, 2, 2, 6, 5]=>{[:then, 1, 3, 4, 3, 8]=>0, [:else, 2, 5, 4, 5, 12]=>1}}}",
|
||
]
|
||
assert_in_out_err(%w[-rcoverage], <<-"end;", exp, [])
|
||
Coverage.start(branches: true)
|
||
tmp = Dir.pwd
|
||
f = tmp + "/test.rb"
|
||
require f
|
||
p Coverage.peek_result[f]
|
||
Coverage.clear
|
||
foo(1)
|
||
p Coverage.peek_result[f]
|
||
Coverage.clear
|
||
foo(-1)
|
||
p Coverage.peek_result[f]
|
||
Coverage.clear
|
||
foo(-1)
|
||
p Coverage.peek_result[f]
|
||
end;
|
||
}
|
||
}
|
||
end
|
||
def test_clear_with_methods
|
||
Dir.mktmpdir {|tmp|
|
||
Dir.chdir(tmp) {
|
||
File.open("test.rb", "w") do |f|
|
||
f.puts "def foo(x)"
|
||
f.puts " if x > 0"
|
||
f.puts " :pos"
|
||
f.puts " else"
|
||
f.puts " :non_pos"
|
||
f.puts " end"
|
||
f.puts "end"
|
||
end
|
||
exp = [
|
||
"{:methods=>{[Object, :foo, 1, 0, 7, 3]=>0}}",
|
||
"{:methods=>{[Object, :foo, 1, 0, 7, 3]=>1}}",
|
||
"{:methods=>{[Object, :foo, 1, 0, 7, 3]=>1}}",
|
||
"{:methods=>{[Object, :foo, 1, 0, 7, 3]=>1}}"
|
||
]
|
||
assert_in_out_err(%w[-rcoverage], <<-"end;", exp, [])
|
||
Coverage.start(methods: true)
|
||
tmp = Dir.pwd
|
||
f = tmp + "/test.rb"
|
||
require f
|
||
p Coverage.peek_result[f]
|
||
Coverage.clear
|
||
foo(1)
|
||
p Coverage.peek_result[f]
|
||
Coverage.clear
|
||
foo(-1)
|
||
p Coverage.peek_result[f]
|
||
Coverage.clear
|
||
foo(-1)
|
||
p Coverage.peek_result[f]
|
||
end;
|
||
}
|
||
}
|
||
end
|
||
def test_clear_with_oneshot_lines
|
||
Dir.mktmpdir {|tmp|
|
||
Dir.chdir(tmp) {
|
||
File.open("test.rb", "w") do |f|
|
||
f.puts "def foo(x)"
|
||
f.puts " if x > 0"
|
||
f.puts " :pos"
|
||
f.puts " else"
|
||
f.puts " :non_pos"
|
||
f.puts " end"
|
||
f.puts "end"
|
||
end
|
||
exp = [
|
||
"{:oneshot_lines=>[1]}",
|
||
"{:oneshot_lines=>[2, 3]}",
|
||
"{:oneshot_lines=>[5]}",
|
||
"{:oneshot_lines=>[]}",
|
||
]
|
||
assert_in_out_err(%w[-rcoverage], <<-"end;", exp, [])
|
||
Coverage.start(oneshot_lines: true)
|
||
tmp = Dir.pwd
|
||
f = tmp + "/test.rb"
|
||
require f
|
||
p Coverage.peek_result[f]
|
||
Coverage.clear
|
||
foo(1)
|
||
p Coverage.peek_result[f]
|
||
Coverage.clear
|
||
foo(-1)
|
||
p Coverage.peek_result[f]
|
||
Coverage.clear
|
||
foo(-1)
|
||
p Coverage.peek_result[f]
|
||
end;
|
||
}
|
||
}
|
||
end
|
||
def test_line_stub
|
||
Dir.mktmpdir {|tmp|
|
||
Dir.chdir(tmp) {
|
||
File.open("test.rb", "w") do |f|
|
||
f.puts "def foo(x)"
|
||
f.puts " if x > 0"
|
||
f.puts " :pos"
|
||
f.puts " else"
|
||
f.puts " :non_pos"
|
||
f.puts " end"
|
||
f.puts "end"
|
||
end
|
||
assert_equal([0, 0, 0, nil, 0, nil, 0], Coverage.line_stub("test.rb"))
|
||
}
|
||
}
|
||
end
|
||
end
|
thread.c | ||
---|---|---|
VALUE branches = RARRAY_AREF(coverage, COVERAGE_INDEX_BRANCHES);
|
||
if (lines) {
|
||
for (i = 0; i < RARRAY_LEN(lines); i++) {
|
||
if (RARRAY_AREF(lines, i) != Qnil) {
|
||
RARRAY_ASET(lines, i, INT2FIX(0));
|
||
}
|
||
}
|
||
if (GET_VM()->coverage_mode & COVERAGE_TARGET_ONESHOT_LINES) {
|
||
rb_ary_clear(lines);
|
||
}
|
||
else {
|
||
int i;
|
||
for (i = 0; i < RARRAY_LEN(lines); i++) {
|
||
if (RARRAY_AREF(lines, i) != Qnil)
|
||
RARRAY_ASET(lines, i, INT2FIX(0));
|
||
}
|
||
}
|
||
}
|
||
if (branches) {
|
||
VALUE counters = RARRAY_AREF(branches, 1);
|
||
... | ... | |
return ST_CONTINUE;
|
||
}
|
||
static void
|
||
clear_coverage(void)
|
||
void
|
||
rb_clear_coverages(void)
|
||
{
|
||
VALUE coverages = rb_get_coverages();
|
||
if (RTEST(coverages)) {
|
||
... | ... | |
vm->fork_gen++;
|
||
vm->sleeper = 0;
|
||
clear_coverage();
|
||
rb_clear_coverages();
|
||
}
|
||
static void
|
||
... | ... | |
static void
|
||
update_line_coverage(VALUE data, const rb_trace_arg_t *trace_arg)
|
||
{
|
||
VALUE coverage = rb_iseq_coverage(GET_EC()->cfp->iseq);
|
||
const rb_control_frame_t *cfp = GET_EC()->cfp;
|
||
VALUE coverage = rb_iseq_coverage(cfp->iseq);
|
||
if (RB_TYPE_P(coverage, T_ARRAY) && !RBASIC_CLASS(coverage)) {
|
||
VALUE lines = RARRAY_AREF(coverage, COVERAGE_INDEX_LINES);
|
||
if (lines) {
|
||
long line = rb_sourceline() - 1;
|
||
long count;
|
||
VALUE num;
|
||
void rb_iseq_clear_event_flags(const rb_iseq_t *iseq, size_t pos, rb_event_flag_t reset);
|
||
if (GET_VM()->coverage_mode & COVERAGE_TARGET_ONESHOT_LINES) {
|
||
rb_iseq_clear_event_flags(cfp->iseq, cfp->pc - cfp->iseq->body->iseq_encoded - 1, RUBY_EVENT_COVERAGE_LINE);
|
||
rb_ary_push(lines, LONG2FIX(line + 1));
|
||
return;
|
||
}
|
||
if (line >= RARRAY_LEN(lines)) { /* no longer tracked */
|
||
return;
|
||
}
|
||
... | ... | |
return GET_VM()->coverages;
|
||
}
|
||
int
|
||
rb_get_coverage_mode(void)
|
||
{
|
||
return GET_VM()->coverage_mode;
|
||
}
|
||
void
|
||
rb_set_coverages(VALUE coverages, int mode, VALUE me2counter)
|
||
{
|
||
... | ... | |
}
|
||
/* Make coverage arrays empty so old covered files are no longer tracked. */
|
||
static int
|
||
reset_coverage_i(st_data_t key, st_data_t val, st_data_t dummy)
|
||
{
|
||
VALUE coverage = (VALUE)val;
|
||
VALUE lines = RARRAY_AREF(coverage, COVERAGE_INDEX_LINES);
|
||
VALUE branches = RARRAY_AREF(coverage, COVERAGE_INDEX_BRANCHES);
|
||
if (lines) rb_ary_clear(lines);
|
||
if (branches) rb_ary_clear(branches);
|
||
return ST_CONTINUE;
|
||
}
|
||
void
|
||
rb_reset_coverages(void)
|
||
{
|
||
VALUE coverages = rb_get_coverages();
|
||
st_foreach(rb_hash_tbl_raw(coverages), reset_coverage_i, 0);
|
||
rb_clear_coverages();
|
||
rb_iseq_remove_coverage_all();
|
||
GET_VM()->coverages = Qfalse;
|
||
rb_remove_event_hook((rb_event_hook_func_t) update_line_coverage);
|
vm_core.h | ||
---|---|---|
extern VALUE rb_get_coverages(void);
|
||
extern void rb_set_coverages(VALUE, int, VALUE);
|
||
extern void rb_clear_coverages(void);
|
||
extern void rb_reset_coverages(void);
|
||
void rb_postponed_job_flush(rb_vm_t *vm);
|