ruby-changes:47758
From: mame <ko1@a...>
Date: Thu, 14 Sep 2017 12:25:41 +0900 (JST)
Subject: [ruby-changes:47758] mame:r59876 (trunk): Add branch coverage for if statement
mame 2017-09-14 12:25:36 +0900 (Thu, 14 Sep 2017) New Revision: 59876 https://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=59876 Log: Add branch coverage for if statement Modified files: trunk/compile.c trunk/ext/coverage/coverage.c trunk/test/coverage/test_coverage.rb trunk/thread.c Index: ext/coverage/coverage.c =================================================================== --- ext/coverage/coverage.c (revision 59875) +++ ext/coverage/coverage.c (revision 59876) @@ -67,6 +67,31 @@ rb_coverage_start(int argc, VALUE *argv, https://github.com/ruby/ruby/blob/trunk/ext/coverage/coverage.c#L67 return Qnil; } +static VALUE +branch_coverage(VALUE branches) +{ + VALUE ret = rb_hash_new(); + VALUE structure = rb_ary_dup(RARRAY_AREF(branches, 0)); + VALUE counters = rb_ary_dup(RARRAY_AREF(branches, 1)); + int i, j, id = 0; + + for (i = 0; i < RARRAY_LEN(structure); i++) { + VALUE branches = RARRAY_AREF(structure, i); + VALUE base_type = RARRAY_AREF(branches, 0); + VALUE base_lineno = RARRAY_AREF(branches, 1); + VALUE children = rb_hash_new(); + rb_hash_aset(ret, rb_ary_new_from_args(3, base_type, INT2FIX(id++), base_lineno), children); + for (j = 2; j < RARRAY_LEN(branches); j += 3) { + VALUE target_label = RARRAY_AREF(branches, j); + VALUE target_lineno = RARRAY_AREF(branches, j + 1); + int idx = FIX2INT(RARRAY_AREF(branches, j + 2)); + rb_hash_aset(children, rb_ary_new_from_args(3, target_label, INT2FIX(id++), target_lineno), RARRAY_AREF(counters, idx)); + } + } + + return ret; +} + static int coverage_peek_result_i(st_data_t key, st_data_t val, st_data_t h) { @@ -92,7 +117,7 @@ coverage_peek_result_i(st_data_t key, st https://github.com/ruby/ruby/blob/trunk/ext/coverage/coverage.c#L117 } if (branches) { - rb_hash_aset(h, ID2SYM(rb_intern("branches")), branches); ++ rb_hash_aset(h, ID2SYM(rb_intern("branches")), branch_coverage(branches)); } if (methods) { Index: compile.c =================================================================== --- compile.c (revision 59875) +++ compile.c (revision 59876) @@ -251,6 +251,32 @@ struct iseq_compile_data_ensure_node_sta https://github.com/ruby/ruby/blob/trunk/compile.c#L251 ADD_INSN2((seq), (line), trace2, INT2FIX(RUBY_EVENT_COVERAGE), INT2FIX(COVERAGE_INDEX_LINES)); \ } \ } while (0) +#define DECL_BRANCH_BASE(branches, line, type) \ + do { \ + if (ISEQ_COVERAGE(iseq) && \ + ISEQ_BRANCH_COVERAGE(iseq) && \ + (line) > 0) { \ + VALUE structure = RARRAY_AREF(ISEQ_BRANCH_COVERAGE(iseq), 0); \ + branches = rb_ary_tmp_new(0); \ + rb_ary_push(structure, branches); \ + rb_ary_push(branches, ID2SYM(rb_intern(type))); \ + rb_ary_push(branches, INT2FIX(line)); \ + } \ + } while (0) +#define ADD_TRACE_BRANCH_COVERAGE(seq, line, type, branches) \ + do { \ + if (ISEQ_COVERAGE(iseq) && \ + ISEQ_BRANCH_COVERAGE(iseq) && \ + (line) > 0) { \ + VALUE counters = RARRAY_AREF(ISEQ_BRANCH_COVERAGE(iseq), 1); \ + long counter_idx = RARRAY_LEN(counters); \ + rb_ary_push(counters, INT2FIX(0)); \ + rb_ary_push(branches, ID2SYM(rb_intern(type))); \ + rb_ary_push(branches, INT2FIX(line)); \ + rb_ary_push(branches, INT2FIX(counter_idx)); \ + ADD_INSN2((seq), (line), trace2, INT2FIX(RUBY_EVENT_COVERAGE), INT2FIX(counter_idx * 16 + COVERAGE_INDEX_BRANCHES)); \ + } \ + } while (0) #define ADD_TRACE(seq, line, event) \ do { \ @@ -4145,6 +4171,7 @@ compile_if(rb_iseq_t *iseq, LINK_ANCHOR https://github.com/ruby/ruby/blob/trunk/compile.c#L4171 DECL_ANCHOR(then_seq); DECL_ANCHOR(else_seq); LABEL *then_label, *else_label, *end_label; + VALUE branches; INIT_ANCHOR(cond_seq); INIT_ANCHOR(then_seq); @@ -4160,8 +4187,15 @@ compile_if(rb_iseq_t *iseq, LINK_ANCHOR https://github.com/ruby/ruby/blob/trunk/compile.c#L4187 ADD_SEQ(ret, cond_seq); + if (then_label->refcnt && else_label->refcnt) { + DECL_BRANCH_BASE(branches, line, "if"); + } + if (then_label->refcnt) { ADD_LABEL(ret, then_label); + if (else_label->refcnt) { + ADD_TRACE_BRANCH_COVERAGE(ret, node->nd_body ? nd_line(node->nd_body) : line, "then", branches); + } ADD_SEQ(ret, then_seq); end_label = NEW_LABEL(line); ADD_INSNL(ret, line, jump, end_label); @@ -4169,6 +4203,9 @@ compile_if(rb_iseq_t *iseq, LINK_ANCHOR https://github.com/ruby/ruby/blob/trunk/compile.c#L4203 if (else_label->refcnt) { ADD_LABEL(ret, else_label); + if (then_label->refcnt) { + ADD_TRACE_BRANCH_COVERAGE(ret, node->nd_else ? nd_line(node->nd_else) : line, "else", branches); + } ADD_SEQ(ret, else_seq); } Index: test/coverage/test_coverage.rb =================================================================== --- test/coverage/test_coverage.rb (revision 59875) +++ test/coverage/test_coverage.rb (revision 59876) @@ -175,4 +175,31 @@ class TestCoverage < Test::Unit::TestCas https://github.com/ruby/ruby/blob/trunk/test/coverage/test_coverage.rb#L175 end end; end + + def test_branch_coverage_for_if_statement + 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 ' 0' + f.puts ' else' + f.puts ' 1' + f.puts ' end' + f.puts 'end' + f.puts 'foo(0)' + f.puts 'foo(0)' + f.puts 'foo(1)' + end + + assert_in_out_err(%w[-W0 -rcoverage], <<-"end;", ["{:branches=>{[:if, 0, 2]=>{[:then, 1, 3]=>2, [:else, 2, 5]=>1}}}"], []) + ENV["COVERAGE_EXPERIMENTAL_MODE"] = "true" + Coverage.start(branches: true) + tmp = Dir.pwd + require tmp + '/test.rb' + p Coverage.result[tmp + "/test.rb"] + end; + } + } + end end Index: thread.c =================================================================== --- thread.c (revision 59875) +++ thread.c (revision 59876) @@ -4093,6 +4093,7 @@ clear_coverage_i(st_data_t key, st_data_ https://github.com/ruby/ruby/blob/trunk/thread.c#L4093 int i; VALUE coverage = (VALUE)val; VALUE lines = RARRAY_AREF(coverage, COVERAGE_INDEX_LINES); + VALUE branches = RARRAY_AREF(coverage, COVERAGE_INDEX_BRANCHES); if (lines) { for (i = 0; i < RARRAY_LEN(lines); i++) { @@ -4101,6 +4102,12 @@ clear_coverage_i(st_data_t key, st_data_ https://github.com/ruby/ruby/blob/trunk/thread.c#L4102 } } } + if (branches) { + VALUE counters = RARRAY_AREF(branches, 1); + for (i = 0; i < RARRAY_LEN(counters); i++) { + RARRAY_ASET(counters, i, INT2FIX(0)); + } + } return ST_CONTINUE; } @@ -4975,7 +4982,8 @@ update_coverage(VALUE data, const rb_tra https://github.com/ruby/ruby/blob/trunk/thread.c#L4982 { VALUE coverage = rb_iseq_coverage(GET_THREAD()->ec.cfp->iseq); if (RB_TYPE_P(coverage, T_ARRAY) && !RBASIC_CLASS(coverage)) { - switch (FIX2INT(trace_arg->data)) { + long arg = FIX2INT(trace_arg->data); + switch (arg % 16) { case COVERAGE_INDEX_LINES: { VALUE lines = RARRAY_AREF(coverage, COVERAGE_INDEX_LINES); if (lines) { @@ -4994,6 +5002,20 @@ update_coverage(VALUE data, const rb_tra https://github.com/ruby/ruby/blob/trunk/thread.c#L5002 } break; } + case COVERAGE_INDEX_BRANCHES: { + VALUE branches = RARRAY_AREF(coverage, COVERAGE_INDEX_BRANCHES); + if (branches) { + long count; + long idx = arg / 16; + VALUE counters = RARRAY_AREF(branches, 1); + VALUE num = RARRAY_AREF(counters, idx); + count = FIX2LONG(num) + 1; + if (POSFIXABLE(count)) { + RARRAY_ASET(counters, idx, LONG2FIX(count)); + } + } + break; + } } } } @@ -5018,7 +5040,9 @@ reset_coverage_i(st_data_t key, st_data_ https://github.com/ruby/ruby/blob/trunk/thread.c#L5040 { VALUE coverage = (VALUE)val; VALUE lines = RARRAY_AREF(coverage, COVERAGE_INDEX_LINES); - rb_ary_clear(lines); + VALUE branches = RARRAY_AREF(coverage, COVERAGE_INDEX_BRANCHES); + if (lines) rb_ary_clear(lines); + if (branches) rb_ary_clear(branches); return ST_CONTINUE; } @@ -5044,7 +5068,22 @@ rb_default_coverage(int n) https://github.com/ruby/ruby/blob/trunk/thread.c#L5068 RARRAY_ASET(coverage, COVERAGE_INDEX_LINES, lines); if (mode & COVERAGE_TARGET_BRANCHES) { - /* not implemented yet */ + branches = rb_ary_tmp_new_fill(2); + /* internal data structures for branch coverage: + * + * [[base_type, base_lineno, + * target_type_1, target_lineno_1, target_counter_index_1, + * target_type_2, target_lineno_2, target_counter_index_2, ...], + * ...] + * + * Example: [[:case, 1, + * :when, 2, 0, + * :when, 3, 1, ...], + * ...] + */ + RARRAY_ASET(branches, 0, rb_ary_tmp_new(0)); + /* branch execution counters */ + RARRAY_ASET(branches, 1, rb_ary_tmp_new(0)); } RARRAY_ASET(coverage, COVERAGE_INDEX_BRANCHES, branches); -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/