Project

General

Profile

Feature #9508 ยป pull-request-511.patch

srawlins (Sam Rawlins), 02/26/2014 03:19 AM

View differences:

compile.c
226 226
	  iseq->compile_data->last_coverable_line = (line); \
227 227
	  ADD_INSN1((seq), (line), trace, INT2FIX(RUBY_EVENT_COVERAGE)); \
228 228
      } \
229
      if ((event) == RUBY_EVENT_DEFN && iseq->method_coverage) { \
230
	  /*iseq->compile_data->last_coverable_line = (line); \
231
	  VALUE info = rb_hash_new(); \
232
	  rb_hash_aset(info, ID2SYM(rb_intern("count")), INT2FIX(0));*/ \
233
	  rb_hash_aset(iseq->method_coverage, LONG2FIX(line), Qnil); \
234
      } \
235
      if ((event) == RUBY_EVENT_CALL && iseq->method_coverage && \
236
	  (line) != iseq->compile_data->last_coverable_line) { \
237
	  iseq->compile_data->last_coverable_line = (line); \
238
	  ADD_INSN1((seq), (line), trace, INT2FIX(RUBY_EVENT_MCOVERAGE)); \
239
      } \
229 240
      if (iseq->compile_data->option->trace_instruction) { \
230 241
	  ADD_INSN1((seq), (line), trace, INT2FIX(event)); \
231 242
      } \
......
4938 4949

  
4939 4950
	debugp_param("defn/iseq", iseqval);
4940 4951

  
4952
	ADD_TRACE(ret, nd_line(node), RUBY_EVENT_DEFN);
4941 4953
	ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE));
4942 4954
	ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_CBASE));
4943 4955
	ADD_INSN1(ret, line, putobject, ID2SYM(node->nd_mid));
ext/coverage/coverage.c
38 38
    VALUE path = (VALUE)key;
39 39
    VALUE coverage = (VALUE)val;
40 40
    VALUE coverages = (VALUE)h;
41
    coverage = rb_ary_dup(coverage);
42
    rb_ary_clear((VALUE)val);
41
    VALUE line_coverage = rb_hash_lookup(coverage, ID2SYM(rb_intern("lines")));
42
    line_coverage = rb_ary_dup(line_coverage);
43
    coverage = rb_hash_dup(coverage);
44
    rb_hash_clear((VALUE)val);
45
    rb_hash_freeze(line_coverage);
46
    rb_hash_aset(coverage, ID2SYM(rb_intern("lines")), line_coverage);
43 47
    rb_ary_freeze(coverage);
44 48
    rb_hash_aset(coverages, path, coverage);
45 49
    return ST_CONTINUE;
include/ruby/ruby.h
1714 1714
#define RUBY_EVENT_C_CALL    0x0020
1715 1715
#define RUBY_EVENT_C_RETURN  0x0040
1716 1716
#define RUBY_EVENT_RAISE     0x0080
1717
#define RUBY_EVENT_DEFN      0x0090
1717 1718
#define RUBY_EVENT_ALL       0x00ff
1718 1719

  
1719 1720
/* for TracePoint extended events */
......
1726 1727
/* special events */
1727 1728
#define RUBY_EVENT_SPECIFIED_LINE         0x010000
1728 1729
#define RUBY_EVENT_COVERAGE               0x020000
1730
#define RUBY_EVENT_MCOVERAGE              0x040000
1729 1731

  
1730 1732
/* internal events */
1731 1733
#define RUBY_INTERNAL_EVENT_SWITCH          0x040000
iseq.c
113 113
	RUBY_MARK_UNLESS_NULL((VALUE)iseq->cref_stack);
114 114
	RUBY_MARK_UNLESS_NULL(iseq->klass);
115 115
	RUBY_MARK_UNLESS_NULL(iseq->coverage);
116
	RUBY_MARK_UNLESS_NULL(iseq->method_coverage);
116 117
	RUBY_MARK_UNLESS_NULL(iseq->orig);
117 118

  
118 119
	if (iseq->compile_data != 0) {
......
305 306
    iseq->compile_data->last_coverable_line = -1;
306 307

  
307 308
    RB_OBJ_WRITE(iseq->self, &iseq->coverage, Qfalse);
309
    RB_OBJ_WRITE(iseq->self, &iseq->method_coverage, Qfalse);
308 310
    if (!GET_THREAD()->parse_in_eval) {
309 311
	VALUE coverages = rb_get_coverages();
310 312
	if (RTEST(coverages)) {
311
	    RB_OBJ_WRITE(iseq->self, &iseq->coverage, rb_hash_lookup(coverages, path));
313
	    RB_OBJ_WRITE(iseq->self, &iseq->coverage,
314
			 rb_hash_lookup(rb_hash_lookup(coverages, path), ID2SYM(rb_intern("lines"))));
315
	    RB_OBJ_WRITE(iseq->self, &iseq->method_coverage,
316
			 rb_hash_lookup(rb_hash_lookup(coverages, path), ID2SYM(rb_intern("methods"))));
312 317
	    if (NIL_P(iseq->coverage)) RB_OBJ_WRITE(iseq->self, &iseq->coverage, Qfalse);
318
	    if (NIL_P(iseq->method_coverage)) RB_OBJ_WRITE(iseq->self, &iseq->method_coverage, Qfalse);
313 319
	}
314 320
    }
315 321

  
parse.y
5299 5299
    VALUE coverages = rb_get_coverages();
5300 5300
    if (RTEST(coverages) && RBASIC(coverages)->klass == 0) {
5301 5301
	VALUE lines = rb_ary_new2(n);
5302
	VALUE rb_file_coverage = rb_hash_new();
5303
	VALUE methods = rb_hash_new();
5302 5304
	int i;
5303 5305
	RBASIC_CLEAR_CLASS(lines);
5304 5306
	for (i = 0; i < n; i++) RARRAY_ASET(lines, i, Qnil);
5305 5307
	RARRAY(lines)->as.heap.len = n;
5306
	rb_hash_aset(coverages, fname, lines);
5308
	rb_hash_aset(rb_file_coverage, ID2SYM(rb_intern("lines")), lines);
5309
	rb_hash_aset(rb_file_coverage, ID2SYM(rb_intern("methods")), methods);
5310
	rb_hash_aset(coverages, fname, rb_file_coverage);
5307 5311
	return lines;
5308 5312
    }
5309 5313
    return 0;
thread.c
3862 3862
clear_coverage_i(st_data_t key, st_data_t val, st_data_t dummy)
3863 3863
{
3864 3864
    int i;
3865
    VALUE lines = (VALUE)val;
3865
    VALUE lines = rb_hash_lookup(val, ID2SYM(rb_intern("lines")));
3866 3866

  
3867 3867
    for (i = 0; i < RARRAY_LEN(lines); i++) {
3868 3868
	if (RARRAY_AREF(lines, i) != Qnil) {
......
5254 5254
    }
5255 5255
}
5256 5256

  
5257
static void
5258
update_method_coverage(rb_event_flag_t event, VALUE proc, VALUE self, ID id, VALUE klass)
5259
{
5260
    VALUE method_coverage = GET_THREAD()->cfp->iseq->method_coverage;
5261
    if (method_coverage) {
5262
	long line = rb_sourceline();
5263
	VALUE info = rb_hash_lookup(method_coverage, LONG2FIX(line));
5264

  
5265
	if (info == Qnil) {
5266
	    VALUE info = rb_hash_new();
5267
	    rb_hash_aset(info, ID2SYM(rb_intern("count")), INT2FIX(1));
5268
	    rb_hash_aset(method_coverage, LONG2FIX(line), info);
5269
	} else {
5270
	    long count = FIX2LONG(rb_hash_lookup(info, ID2SYM(rb_intern("count")))) + 1;
5271
	    rb_hash_aset(info, ID2SYM(rb_intern("count")), LONG2FIX(count));
5272
	}
5273
    }
5274
}
5275

  
5257 5276
VALUE
5258 5277
rb_get_coverages(void)
5259 5278
{
......
5265 5284
{
5266 5285
    GET_VM()->coverages = coverages;
5267 5286
    rb_add_event_hook(update_coverage, RUBY_EVENT_COVERAGE, Qnil);
5287
    rb_add_event_hook(update_method_coverage, RUBY_EVENT_MCOVERAGE, Qnil);
5268 5288
}
5269 5289

  
5270 5290
void
vm_core.h
226 226
    VALUE *iseq_encoded; /* encoded iseq */
227 227
    unsigned long iseq_size;
228 228
    const VALUE mark_ary;     /* Array: includes operands which should be GC marked */
229
    const VALUE coverage;     /* coverage array */
229
    const VALUE coverage;        /* coverage array */
230
    const VALUE method_coverage; /* method coverage array */
230 231

  
231 232
    /* insn info, must be freed */
232 233
    struct iseq_line_info_entry *line_info_table;
233
- 
compile.c
226 226
	  iseq->compile_data->last_coverable_line = (line); \
227 227
	  ADD_INSN1((seq), (line), trace, INT2FIX(RUBY_EVENT_COVERAGE)); \
228 228
      } \
229
      if ((event) == RUBY_EVENT_DEFN && iseq->method_coverage) { \
230
	  /*iseq->compile_data->last_coverable_line = (line); \
231
	  VALUE info = rb_hash_new(); \
232
	  rb_hash_aset(info, ID2SYM(rb_intern("count")), INT2FIX(0));*/ \
233
	  rb_hash_aset(iseq->method_coverage, LONG2FIX(line), Qnil); \
234
      } \
235 229
      if ((event) == RUBY_EVENT_CALL && iseq->method_coverage && \
236 230
	  (line) != iseq->compile_data->last_coverable_line) { \
237 231
	  iseq->compile_data->last_coverable_line = (line); \
......
242 236
      } \
243 237
  } while (0)
244 238

  
239
#define ADD_COVERAGE_TRACE(seq, line, event) \
240
  do { \
241
      if ((event) == RUBY_EVENT_DEFN && iseq->method_coverage) { \
242
	  rb_hash_aset(iseq->method_coverage, LONG2FIX(line), INT2FIX(0)); \
243
      } \
244
  } while (0)
245

  
245 246
/* add label */
246 247
#define ADD_LABEL(seq, label) \
247 248
  ADD_ELEM((seq), (LINK_ELEMENT *) (label))
......
4949 4950

  
4950 4951
	debugp_param("defn/iseq", iseqval);
4951 4952

  
4952
	ADD_TRACE(ret, nd_line(node), RUBY_EVENT_DEFN);
4953
	ADD_COVERAGE_TRACE(ret, nd_line(node), RUBY_EVENT_DEFN);
4953 4954
	ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE));
4954 4955
	ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_CBASE));
4955 4956
	ADD_INSN1(ret, line, putobject, ID2SYM(node->nd_mid));
thread.c
5262 5262
	long line = rb_sourceline();
5263 5263
	VALUE info = rb_hash_lookup(method_coverage, LONG2FIX(line));
5264 5264

  
5265
	if (info == Qnil) {
5266
	    VALUE info = rb_hash_new();
5267
	    rb_hash_aset(info, ID2SYM(rb_intern("count")), INT2FIX(1));
5268
	    rb_hash_aset(method_coverage, LONG2FIX(line), info);
5269
	} else {
5270
	    long count = FIX2LONG(rb_hash_lookup(info, ID2SYM(rb_intern("count")))) + 1;
5271
	    rb_hash_aset(info, ID2SYM(rb_intern("count")), LONG2FIX(count));
5272
	}
5265
	long count = FIX2LONG(info) + 1;
5266
	rb_hash_aset(method_coverage, LONG2FIX(line), LONG2FIX(count));
5273 5267
    }
5274 5268
}
5275 5269

  
5276
- 
compile.c
241 241
      if ((event) == RUBY_EVENT_DEFN && iseq->method_coverage) { \
242 242
	  rb_hash_aset(iseq->method_coverage, LONG2FIX(line), INT2FIX(0)); \
243 243
      } \
244
      if ((event) == RUBY_EVENT_BRANCH && iseq->branch_coverage) { \
245
	  rb_hash_aset(iseq->branch_coverage, LONG2FIX(line), INT2FIX(0)); \
246
	  ADD_INSN1((seq), (line), trace, INT2FIX(RUBY_EVENT_BCOVERAGE)); \
247
      } \
244 248
  } while (0)
245 249

  
246 250
/* add label */
......
3257 3261
	ADD_SEQ(ret, cond_seq);
3258 3262

  
3259 3263
	ADD_LABEL(ret, then_label);
3264
	if (node->nd_body)
3265
	    ADD_COVERAGE_TRACE(ret, nd_line(node->nd_body), RUBY_EVENT_BRANCH);
3260 3266
	ADD_SEQ(ret, then_seq);
3261 3267
	ADD_INSNL(ret, line, jump, end_label);
3262 3268

  
3263 3269
	ADD_LABEL(ret, else_label);
3270
	if (node->nd_else)
3271
	    ADD_COVERAGE_TRACE(ret, nd_line(node->nd_else), RUBY_EVENT_BRANCH);
3264 3272
	ADD_SEQ(ret, else_seq);
3265 3273

  
3266 3274
	ADD_LABEL(ret, end_label);
include/ruby/ruby.h
1715 1715
#define RUBY_EVENT_C_RETURN  0x0040
1716 1716
#define RUBY_EVENT_RAISE     0x0080
1717 1717
#define RUBY_EVENT_DEFN      0x0090
1718
#define RUBY_EVENT_BRANCH    0x00a0
1718 1719
#define RUBY_EVENT_ALL       0x00ff
1719 1720

  
1720 1721
/* for TracePoint extended events */
......
1728 1729
#define RUBY_EVENT_SPECIFIED_LINE         0x010000
1729 1730
#define RUBY_EVENT_COVERAGE               0x020000
1730 1731
#define RUBY_EVENT_MCOVERAGE              0x040000
1732
#define RUBY_EVENT_BCOVERAGE              0x080000
1731 1733

  
1732 1734
/* internal events */
1733 1735
#define RUBY_INTERNAL_EVENT_SWITCH          0x040000
iseq.c
114 114
	RUBY_MARK_UNLESS_NULL(iseq->klass);
115 115
	RUBY_MARK_UNLESS_NULL(iseq->coverage);
116 116
	RUBY_MARK_UNLESS_NULL(iseq->method_coverage);
117
	RUBY_MARK_UNLESS_NULL(iseq->branch_coverage);
117 118
	RUBY_MARK_UNLESS_NULL(iseq->orig);
118 119

  
119 120
	if (iseq->compile_data != 0) {
......
307 308

  
308 309
    RB_OBJ_WRITE(iseq->self, &iseq->coverage, Qfalse);
309 310
    RB_OBJ_WRITE(iseq->self, &iseq->method_coverage, Qfalse);
311
    RB_OBJ_WRITE(iseq->self, &iseq->branch_coverage, Qfalse);
310 312
    if (!GET_THREAD()->parse_in_eval) {
311 313
	VALUE coverages = rb_get_coverages();
312 314
	if (RTEST(coverages)) {
......
314 316
			 rb_hash_lookup(rb_hash_lookup(coverages, path), ID2SYM(rb_intern("lines"))));
315 317
	    RB_OBJ_WRITE(iseq->self, &iseq->method_coverage,
316 318
			 rb_hash_lookup(rb_hash_lookup(coverages, path), ID2SYM(rb_intern("methods"))));
319
	    RB_OBJ_WRITE(iseq->self, &iseq->branch_coverage,
320
			 rb_hash_lookup(rb_hash_lookup(coverages, path), ID2SYM(rb_intern("branches"))));
317 321
	    if (NIL_P(iseq->coverage)) RB_OBJ_WRITE(iseq->self, &iseq->coverage, Qfalse);
318 322
	    if (NIL_P(iseq->method_coverage)) RB_OBJ_WRITE(iseq->self, &iseq->method_coverage, Qfalse);
319 323
	}
parse.y
5301 5301
	VALUE lines = rb_ary_new2(n);
5302 5302
	VALUE rb_file_coverage = rb_hash_new();
5303 5303
	VALUE methods = rb_hash_new();
5304
	VALUE branches = rb_hash_new();
5304 5305
	int i;
5305 5306
	RBASIC_CLEAR_CLASS(lines);
5306 5307
	for (i = 0; i < n; i++) RARRAY_ASET(lines, i, Qnil);
5307 5308
	RARRAY(lines)->as.heap.len = n;
5308 5309
	rb_hash_aset(rb_file_coverage, ID2SYM(rb_intern("lines")), lines);
5309 5310
	rb_hash_aset(rb_file_coverage, ID2SYM(rb_intern("methods")), methods);
5311
	rb_hash_aset(rb_file_coverage, ID2SYM(rb_intern("branches")), branches);
5310 5312
	rb_hash_aset(coverages, fname, rb_file_coverage);
5311 5313
	return lines;
5312 5314
    }
thread.c
5255 5255
}
5256 5256

  
5257 5257
static void
5258
update_method_coverage(rb_event_flag_t event, VALUE proc, VALUE self, ID id, VALUE klass)
5258
update_detailed_coverage(rb_event_flag_t event, VALUE proc, VALUE self, ID id, VALUE klass)
5259 5259
{
5260
    VALUE method_coverage = GET_THREAD()->cfp->iseq->method_coverage;
5261
    if (method_coverage) {
5262
	long line = rb_sourceline();
5263
	VALUE info = rb_hash_lookup(method_coverage, LONG2FIX(line));
5260
    VALUE coverage;
5261
    if (event == RUBY_EVENT_MCOVERAGE) {
5262
	coverage = GET_THREAD()->cfp->iseq->method_coverage;
5263
    } else if (event == RUBY_EVENT_BCOVERAGE) {
5264
	coverage = GET_THREAD()->cfp->iseq->branch_coverage;
5265
    } else {
5266
	rb_raise(rb_eArgError, "unknown detailed coverage event");
5267
    }
5268

  
5269
    if (coverage) {
5270
	VALUE line = LONG2FIX(rb_sourceline());
5271
	VALUE count = rb_hash_lookup(coverage, line);
5272

  
5273
	if (count == Qnil) {
5274
	    return;
5275
	}
5264 5276

  
5265
	long count = FIX2LONG(info) + 1;
5266
	rb_hash_aset(method_coverage, LONG2FIX(line), LONG2FIX(count));
5277
	rb_hash_aset(coverage, line, LONG2FIX(FIX2LONG(count) + 1));
5267 5278
    }
5268 5279
}
5269 5280

  
......
5278 5289
{
5279 5290
    GET_VM()->coverages = coverages;
5280 5291
    rb_add_event_hook(update_coverage, RUBY_EVENT_COVERAGE, Qnil);
5281
    rb_add_event_hook(update_method_coverage, RUBY_EVENT_MCOVERAGE, Qnil);
5292
    rb_add_event_hook(update_detailed_coverage, RUBY_EVENT_MCOVERAGE, Qnil);
5293
    rb_add_event_hook(update_detailed_coverage, RUBY_EVENT_BCOVERAGE, Qnil);
5282 5294
}
5283 5295

  
5284 5296
void
vm_core.h
228 228
    const VALUE mark_ary;     /* Array: includes operands which should be GC marked */
229 229
    const VALUE coverage;        /* coverage array */
230 230
    const VALUE method_coverage; /* method coverage array */
231
    const VALUE branch_coverage; /* branch coverage array */
231 232

  
232 233
    /* insn info, must be freed */
233 234
    struct iseq_line_info_entry *line_info_table;
234
- 
compile.c
3267 3267
	ADD_INSNL(ret, line, jump, end_label);
3268 3268

  
3269 3269
	ADD_LABEL(ret, else_label);
3270
	if (node->nd_else)
3270
	/* do not trace elsif node */
3271
	if (node->nd_else && nd_type(node->nd_else) != NODE_IF)
3271 3272
	    ADD_COVERAGE_TRACE(ret, nd_line(node->nd_else), RUBY_EVENT_BRANCH);
3272 3273
	ADD_SEQ(ret, else_seq);
3273 3274

  
ext/coverage/coverage.c
38 38
    VALUE path = (VALUE)key;
39 39
    VALUE coverage = (VALUE)val;
40 40
    VALUE coverages = (VALUE)h;
41

  
41 42
    VALUE line_coverage = rb_hash_lookup(coverage, ID2SYM(rb_intern("lines")));
43
    VALUE method_coverage = rb_hash_lookup(coverage, ID2SYM(rb_intern("methods")));
44
    VALUE branch_coverage = rb_hash_lookup(coverage, ID2SYM(rb_intern("branches")));
45

  
42 46
    line_coverage = rb_ary_dup(line_coverage);
47
    method_coverage = rb_hash_dup(method_coverage);
48
    branch_coverage = rb_hash_dup(branch_coverage);
49

  
50
    rb_ary_clear(rb_hash_lookup(coverage, ID2SYM(rb_intern("lines"))));
51
    rb_hash_clear(rb_hash_lookup(coverage, ID2SYM(rb_intern("methods"))));
52
    rb_hash_clear(rb_hash_lookup(coverage, ID2SYM(rb_intern("branches"))));
53

  
43 54
    coverage = rb_hash_dup(coverage);
44
    rb_hash_clear((VALUE)val);
45
    rb_hash_freeze(line_coverage);
55

  
56
    rb_ary_freeze(line_coverage);
57
    rb_hash_freeze(method_coverage);
58
    rb_hash_freeze(branch_coverage);
59

  
46 60
    rb_hash_aset(coverage, ID2SYM(rb_intern("lines")), line_coverage);
61
    rb_hash_aset(coverage, ID2SYM(rb_intern("methods")), method_coverage);
62
    rb_hash_aset(coverage, ID2SYM(rb_intern("branches")), branch_coverage);
63

  
47 64
    rb_ary_freeze(coverage);
48 65
    rb_hash_aset(coverages, path, coverage);
49 66
    return ST_CONTINUE;
iseq.c
320 320
			 rb_hash_lookup(rb_hash_lookup(coverages, path), ID2SYM(rb_intern("branches"))));
321 321
	    if (NIL_P(iseq->coverage)) RB_OBJ_WRITE(iseq->self, &iseq->coverage, Qfalse);
322 322
	    if (NIL_P(iseq->method_coverage)) RB_OBJ_WRITE(iseq->self, &iseq->method_coverage, Qfalse);
323
	    if (NIL_P(iseq->branch_coverage)) RB_OBJ_WRITE(iseq->self, &iseq->branch_coverage, Qfalse);
323 324
	}
324 325
    }
325 326

  
test/coverage/test_branch_coverage.rb
1
require "test/unit"
2
require "coverage"
3
require "tmpdir"
4

  
5
class TestBranchCoverage < Test::Unit::TestCase
6
  def test_branch_coverage
7
    loaded_features = $".dup
8

  
9
    Dir.mktmpdir {|tmp|
10
      Dir.chdir(tmp) {
11
        File.open("test.rb", "w") do |f|
12
          f.puts <<-EOS
13
            if 2+2 == 4
14
              :ok
15
            end
16

  
17
            :ok unless [].size > 0
18
            :bad unless [].size == 0
19

  
20
            ary = [1,2,3]
21
            if ary.include? 4
22
              :bad
23
            elsif ary.include? 5
24
              :also_bad
25
            else
26
              :good
27
            end
28
          EOS
29
        end
30

  
31
        Coverage.start
32
        require tmp + '/test.rb'
33
        branch_coverage = Coverage.result[tmp + '/test.rb'][:branches]
34
        assert_equal 6, branch_coverage.size
35
        assert_equal 1, branch_coverage[2]
36
        assert_equal 1, branch_coverage[5]
37
        assert_equal 0, branch_coverage[6]
38
        assert_equal 0, branch_coverage[10]
39
        assert_equal 0, branch_coverage[12]
40
        assert_equal 1, branch_coverage[14]
41
      }
42
    }
43
  ensure
44
    $".replace loaded_features
45
  end
46
end
test/coverage/test_coverage.rb
6 6
  def test_result_without_start
7 7
    assert_raise(RuntimeError) {Coverage.result}
8 8
  end
9

  
9 10
  def test_result_with_nothing
10 11
    Coverage.start
11 12
    result = Coverage.result
12 13
    assert_kind_of(Hash, result)
14
    assert(! result.empty?)
13 15
    result.each do |key, val|
14 16
      assert_kind_of(String, key)
15
      assert_kind_of(Array, val)
17
      assert_kind_of(Hash, val)
18
      assert_kind_of(Array, val[:lines])
19
      assert_kind_of(Hash, val[:methods])
20
      assert_kind_of(Hash, val[:branches])
16 21
    end
17 22
  end
18 23

  
......
31 36

  
32 37
        Coverage.start
33 38
        require tmp + '/test.rb'
34
        assert_equal 3, Coverage.result[tmp + '/test.rb'].size
39
        assert_equal 3, Coverage.result[tmp + '/test.rb'][:lines].size
35 40
        Coverage.start
36 41
        coverage_test_method
37
        assert_equal 0, Coverage.result[tmp + '/test.rb'].size
42
        assert_equal 0, Coverage.result[tmp + '/test.rb'][:lines].size
38 43
      }
39 44
    }
40 45
  ensure
......
55 60

  
56 61
        Coverage.start
57 62
        require tmp + '/test.rb'
58
        assert_equal 10003, Coverage.result[tmp + '/test.rb'].size
63
        assert_equal 10003, Coverage.result[tmp + '/test.rb'][:lines].size
59 64
      }
60 65
    }
61 66
  ensure
test/coverage/test_method_coverage.rb
1
require "test/unit"
2
require "coverage"
3
require "tmpdir"
4

  
5
class TestMethodCoverage < Test::Unit::TestCase
6
  def test_method_coverage
7
    loaded_features = $".dup
8

  
9
    Dir.mktmpdir {|tmp|
10
      Dir.chdir(tmp) {
11
        File.open("test.rb", "w") do |f|
12
          f.puts <<-EOS
13
            def method_one
14
              :one
15
            end
16

  
17
            def method_two
18
              :two
19
            end
20
            method_two; method_two
21
          EOS
22
        end
23

  
24
        Coverage.start
25
        require tmp + '/test.rb'
26
        method_coverage = Coverage.result[tmp + '/test.rb'][:methods]
27

  
28
        assert_equal 2, method_coverage.size
29
        assert_equal 0, method_coverage[1]
30
        assert_equal 2, method_coverage[5]
31
      }
32
    }
33
  ensure
34
    $".replace loaded_features
35
  end
36
end
thread.c
5298 5298
{
5299 5299
    GET_VM()->coverages = Qfalse;
5300 5300
    rb_remove_event_hook(update_coverage);
5301
    rb_remove_event_hook(update_detailed_coverage);
5301 5302
}
5302 5303

  
5303 5304
VALUE
5304
- 
compile.c
3316 3316

  
3317 3317
	    l1 = NEW_LABEL(line);
3318 3318
	    ADD_LABEL(body_seq, l1);
3319
	    if (node->nd_body)
3320
	        ADD_COVERAGE_TRACE(body_seq, nd_line(node->nd_body), RUBY_EVENT_BRANCH);
3319 3321
	    ADD_INSN(body_seq, line, pop);
3320 3322
	    COMPILE_(body_seq, "when body", node->nd_body, poped);
3321 3323
	    ADD_INSNL(body_seq, line, jump, endlabel);
......
3354 3356
	/* else */
3355 3357
	if (node) {
3356 3358
	    ADD_LABEL(cond_seq, elselabel);
3359
	    ADD_COVERAGE_TRACE(cond_seq, nd_line(node), RUBY_EVENT_BRANCH);
3357 3360
	    ADD_INSN(cond_seq, line, pop);
3358 3361
	    COMPILE_(cond_seq, "else", node, poped);
3359 3362
	    ADD_INSNL(cond_seq, line, jump, endlabel);
......
3393 3396
	while (node && nd_type(node) == NODE_WHEN) {
3394 3397
	    LABEL *l1 = NEW_LABEL(line = nd_line(node));
3395 3398
	    ADD_LABEL(body_seq, l1);
3399
	    if (node->nd_body)
3400
	        ADD_COVERAGE_TRACE(body_seq, nd_line(node->nd_body), RUBY_EVENT_BRANCH);
3396 3401
	    COMPILE_(body_seq, "when", node->nd_body, poped);
3397 3402
	    ADD_INSNL(body_seq, line, jump, endlabel);
3398 3403

  
......
3424 3429
	    node = node->nd_next;
3425 3430
	}
3426 3431
	/* else */
3432
	ADD_COVERAGE_TRACE(ret, nd_line(node), RUBY_EVENT_BRANCH);
3427 3433
	COMPILE_(ret, "else", node, poped);
3428 3434
	ADD_INSNL(ret, nd_line(orig_node), jump, endlabel);
3429 3435

  
......
4980 4986

  
4981 4987
	debugp_param("defs/iseq", iseqval);
4982 4988

  
4989
	ADD_COVERAGE_TRACE(ret, nd_line(node), RUBY_EVENT_DEFN);
4983 4990
	ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE));
4984 4991
	COMPILE(ret, "defs: recv", node->nd_recv);
4985 4992
	ADD_INSN1(ret, line, putobject, ID2SYM(node->nd_mid));
test/coverage/test_branch_coverage.rb
3 3
require "tmpdir"
4 4

  
5 5
class TestBranchCoverage < Test::Unit::TestCase
6
  def test_branch_coverage
6
  def test_if_else_coverage
7 7
    loaded_features = $".dup
8 8

  
9 9
    Dir.mktmpdir {|tmp|
......
43 43
  ensure
44 44
    $".replace loaded_features
45 45
  end
46

  
47
  def test_when_coverage
48
    loaded_features = $".dup
49

  
50
    Dir.mktmpdir {|tmp|
51
      Dir.chdir(tmp) {
52
        File.open("test.rb", "w") do |f|
53
          f.puts <<-EOS
54
            case
55
            when 2 + 2 == 5
56
              x = :bad
57
              x = :math
58
            when 2 + 2 == 4
59
              x = :good
60
            else
61
              x = :also_bad
62
            end
63

  
64
            case [1,2,3]
65
            when String
66
              :string?
67
            else
68
              :else
69
            end
70
          EOS
71
        end
72

  
73
        Coverage.start
74
        require tmp + '/test.rb'
75
        branch_coverage = Coverage.result[tmp + '/test.rb'][:branches]
76
        assert_equal 5, branch_coverage.size
77
        assert_equal 0, branch_coverage[3]
78
        assert_equal 1, branch_coverage[6]
79
        assert_equal 0, branch_coverage[8]
80
        assert_equal 0, branch_coverage[13]
81
        assert_equal 1, branch_coverage[15]
82
      }
83
    }
84
  ensure
85
    $".replace loaded_features
86
  end
46 87
end
47
- 
compile.c
226 226
	  iseq->compile_data->last_coverable_line = (line); \
227 227
	  ADD_INSN1((seq), (line), trace, INT2FIX(RUBY_EVENT_COVERAGE)); \
228 228
      } \
229
      if ((event) == RUBY_EVENT_CALL && iseq->method_coverage && \
230
	  (line) != iseq->compile_data->last_coverable_line) { \
231
	  iseq->compile_data->last_coverable_line = (line); \
232
	  ADD_INSN1((seq), (line), trace, INT2FIX(RUBY_EVENT_MCOVERAGE)); \
233
      } \
234 229
      if (iseq->compile_data->option->trace_instruction) { \
235 230
	  ADD_INSN1((seq), (line), trace, INT2FIX(event)); \
236 231
      } \
237 232
  } while (0)
238 233

  
239
#define ADD_COVERAGE_TRACE(seq, line, event) \
234
#define ADD_METHOD_COVERAGE_TRACE(seq, line, event, end_line) \
240 235
  do { \
241 236
      if ((event) == RUBY_EVENT_DEFN && iseq->method_coverage) { \
242 237
	  rb_hash_aset(iseq->method_coverage, LONG2FIX(line), INT2FIX(0)); \
243 238
      } \
239
      if ((event) == RUBY_EVENT_CALL && iseq->method_coverage) { \
240
	  ADD_INSN1((seq), (line), trace, INT2FIX(RUBY_EVENT_MCOVERAGE)); \
241
      } \
242
  } while (0)
243

  
244
#define ADD_BRANCH_COVERAGE_TRACE(seq, line, event) \
245
  do { \
244 246
      if ((event) == RUBY_EVENT_BRANCH && iseq->branch_coverage) { \
245 247
	  rb_hash_aset(iseq->branch_coverage, LONG2FIX(line), INT2FIX(0)); \
246 248
	  ADD_INSN1((seq), (line), trace, INT2FIX(RUBY_EVENT_BCOVERAGE)); \
......
515 517
	  case ISEQ_TYPE_METHOD:
516 518
	    {
517 519
		ADD_TRACE(ret, FIX2INT(iseq->location.first_lineno), RUBY_EVENT_CALL);
520
		ADD_METHOD_COVERAGE_TRACE(ret, FIX2INT(iseq->location.first_lineno), RUBY_EVENT_CALL, nd_line(node));
518 521
		COMPILE(ret, "scoped node", node->nd_body);
519 522
		ADD_TRACE(ret, nd_line(node), RUBY_EVENT_RETURN);
520 523
		break;
......
3262 3265

  
3263 3266
	ADD_LABEL(ret, then_label);
3264 3267
	if (node->nd_body)
3265
	    ADD_COVERAGE_TRACE(ret, nd_line(node->nd_body), RUBY_EVENT_BRANCH);
3268
	    ADD_BRANCH_COVERAGE_TRACE(ret, nd_line(node->nd_body), RUBY_EVENT_BRANCH);
3266 3269
	ADD_SEQ(ret, then_seq);
3267 3270
	ADD_INSNL(ret, line, jump, end_label);
3268 3271

  
3269 3272
	ADD_LABEL(ret, else_label);
3270 3273
	/* do not trace elsif node */
3271 3274
	if (node->nd_else && nd_type(node->nd_else) != NODE_IF)
3272
	    ADD_COVERAGE_TRACE(ret, nd_line(node->nd_else), RUBY_EVENT_BRANCH);
3275
	    ADD_BRANCH_COVERAGE_TRACE(ret, nd_line(node->nd_else), RUBY_EVENT_BRANCH);
3273 3276
	ADD_SEQ(ret, else_seq);
3274 3277

  
3275 3278
	ADD_LABEL(ret, end_label);
......
3317 3320
	    l1 = NEW_LABEL(line);
3318 3321
	    ADD_LABEL(body_seq, l1);
3319 3322
	    if (node->nd_body)
3320
	        ADD_COVERAGE_TRACE(body_seq, nd_line(node->nd_body), RUBY_EVENT_BRANCH);
3323
	        ADD_BRANCH_COVERAGE_TRACE(body_seq, nd_line(node->nd_body), RUBY_EVENT_BRANCH);
3321 3324
	    ADD_INSN(body_seq, line, pop);
3322 3325
	    COMPILE_(body_seq, "when body", node->nd_body, poped);
3323 3326
	    ADD_INSNL(body_seq, line, jump, endlabel);
......
3356 3359
	/* else */
3357 3360
	if (node) {
3358 3361
	    ADD_LABEL(cond_seq, elselabel);
3359
	    ADD_COVERAGE_TRACE(cond_seq, nd_line(node), RUBY_EVENT_BRANCH);
3362
	    ADD_BRANCH_COVERAGE_TRACE(cond_seq, nd_line(node), RUBY_EVENT_BRANCH);
3360 3363
	    ADD_INSN(cond_seq, line, pop);
3361 3364
	    COMPILE_(cond_seq, "else", node, poped);
3362 3365
	    ADD_INSNL(cond_seq, line, jump, endlabel);
......
3397 3400
	    LABEL *l1 = NEW_LABEL(line = nd_line(node));
3398 3401
	    ADD_LABEL(body_seq, l1);
3399 3402
	    if (node->nd_body)
3400
	        ADD_COVERAGE_TRACE(body_seq, nd_line(node->nd_body), RUBY_EVENT_BRANCH);
3403
	        ADD_BRANCH_COVERAGE_TRACE(body_seq, nd_line(node->nd_body), RUBY_EVENT_BRANCH);
3401 3404
	    COMPILE_(body_seq, "when", node->nd_body, poped);
3402 3405
	    ADD_INSNL(body_seq, line, jump, endlabel);
3403 3406

  
......
3429 3432
	    node = node->nd_next;
3430 3433
	}
3431 3434
	/* else */
3432
	ADD_COVERAGE_TRACE(ret, nd_line(node), RUBY_EVENT_BRANCH);
3435
	ADD_BRANCH_COVERAGE_TRACE(ret, nd_line(node), RUBY_EVENT_BRANCH);
3433 3436
	COMPILE_(ret, "else", node, poped);
3434 3437
	ADD_INSNL(ret, nd_line(orig_node), jump, endlabel);
3435 3438

  
......
4965 4968

  
4966 4969
	debugp_param("defn/iseq", iseqval);
4967 4970

  
4968
	ADD_COVERAGE_TRACE(ret, nd_line(node), RUBY_EVENT_DEFN);
4971
	ADD_METHOD_COVERAGE_TRACE(ret, nd_line(node), RUBY_EVENT_DEFN, nd_line(node->nd_defn));
4969 4972
	ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE));
4970 4973
	ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_CBASE));
4971 4974
	ADD_INSN1(ret, line, putobject, ID2SYM(node->nd_mid));
......
4986 4989

  
4987 4990
	debugp_param("defs/iseq", iseqval);
4988 4991

  
4989
	ADD_COVERAGE_TRACE(ret, nd_line(node), RUBY_EVENT_DEFN);
4992
	ADD_METHOD_COVERAGE_TRACE(ret, nd_line(node), RUBY_EVENT_DEFN, nd_line(node->nd_defn));
4990 4993
	ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE));
4991 4994
	COMPILE(ret, "defs: recv", node->nd_recv);
4992 4995
	ADD_INSN1(ret, line, putobject, ID2SYM(node->nd_mid));
4993
- 
compile.c
220 220

  
221 221
#define ADD_TRACE(seq, line, event) \
222 222
  do { \
223
      if ((event) == RUBY_EVENT_LINE && iseq->coverage && \
223
      if ((event) == RUBY_EVENT_LINE && iseq->coverage->lines && \
224 224
	  (line) != iseq->compile_data->last_coverable_line) { \
225
	  RARRAY_ASET(iseq->coverage, (line) - 1, INT2FIX(0)); \
225
	  RARRAY_ASET(iseq->coverage->lines, (line) - 1, INT2FIX(0)); \
226 226
	  iseq->compile_data->last_coverable_line = (line); \
227 227
	  ADD_INSN1((seq), (line), trace, INT2FIX(RUBY_EVENT_COVERAGE)); \
228 228
      } \
......
233 233

  
234 234
#define ADD_METHOD_COVERAGE_TRACE(seq, line, event, end_line) \
235 235
  do { \
236
      if ((event) == RUBY_EVENT_DEFN && iseq->method_coverage) { \
237
	  rb_hash_aset(iseq->method_coverage, LONG2FIX(line), INT2FIX(0)); \
236
      if ((event) == RUBY_EVENT_DEFN && iseq->coverage->methods) { \
237
	  rb_hash_aset(iseq->coverage->methods, LONG2FIX(line), INT2FIX(0)); \
238 238
      } \
239
      if ((event) == RUBY_EVENT_CALL && iseq->method_coverage) { \
239
      if ((event) == RUBY_EVENT_CALL && iseq->coverage->methods) { \
240 240
	  ADD_INSN1((seq), (line), trace, INT2FIX(RUBY_EVENT_MCOVERAGE)); \
241 241
      } \
242 242
  } while (0)
243 243

  
244 244
#define ADD_BRANCH_COVERAGE_TRACE(seq, line, event) \
245 245
  do { \
246
      if ((event) == RUBY_EVENT_BRANCH && iseq->branch_coverage) { \
247
	  rb_hash_aset(iseq->branch_coverage, LONG2FIX(line), INT2FIX(0)); \
246
      if ((event) == RUBY_EVENT_BRANCH && iseq->coverage->branches) { \
247
	  rb_hash_aset(iseq->coverage->branches, LONG2FIX(line), INT2FIX(0)); \
248 248
	  ADD_INSN1((seq), (line), trace, INT2FIX(RUBY_EVENT_BCOVERAGE)); \
249 249
      } \
250 250
  } while (0)
iseq.c
112 112

  
113 113
	RUBY_MARK_UNLESS_NULL((VALUE)iseq->cref_stack);
114 114
	RUBY_MARK_UNLESS_NULL(iseq->klass);
115
	RUBY_MARK_UNLESS_NULL(iseq->coverage);
116
	RUBY_MARK_UNLESS_NULL(iseq->method_coverage);
117
	RUBY_MARK_UNLESS_NULL(iseq->branch_coverage);
118 115
	RUBY_MARK_UNLESS_NULL(iseq->orig);
119 116

  
117
	if (iseq->coverage != 0) {
118
	    struct rb_coverage_struct *const coverage = iseq->coverage;
119
	    RUBY_MARK_UNLESS_NULL(coverage->lines);
120
	    RUBY_MARK_UNLESS_NULL(coverage->methods);
121
	    RUBY_MARK_UNLESS_NULL(coverage->branches);
122
	}
123

  
120 124
	if (iseq->compile_data != 0) {
121 125
	    struct iseq_compile_data *const compile_data = iseq->compile_data;
122 126
	    RUBY_MARK_UNLESS_NULL(compile_data->mark_ary);
......
306 310
    iseq->compile_data->option = option;
307 311
    iseq->compile_data->last_coverable_line = -1;
308 312

  
309
    RB_OBJ_WRITE(iseq->self, &iseq->coverage, Qfalse);
310
    RB_OBJ_WRITE(iseq->self, &iseq->method_coverage, Qfalse);
311
    RB_OBJ_WRITE(iseq->self, &iseq->branch_coverage, Qfalse);
313
    iseq->coverage = ALLOC(struct rb_coverage_struct);
314
    RB_OBJ_WRITE(iseq->self, &iseq->coverage->lines, Qfalse);
315
    RB_OBJ_WRITE(iseq->self, &iseq->coverage->methods, Qfalse);
316
    RB_OBJ_WRITE(iseq->self, &iseq->coverage->branches, Qfalse);
312 317
    if (!GET_THREAD()->parse_in_eval) {
313 318
	VALUE coverages = rb_get_coverages();
314 319
	if (RTEST(coverages)) {
315
	    RB_OBJ_WRITE(iseq->self, &iseq->coverage,
320
	    RB_OBJ_WRITE(iseq->self, &iseq->coverage->lines,
316 321
			 rb_hash_lookup(rb_hash_lookup(coverages, path), ID2SYM(rb_intern("lines"))));
317
	    RB_OBJ_WRITE(iseq->self, &iseq->method_coverage,
322
	    RB_OBJ_WRITE(iseq->self, &iseq->coverage->methods,
318 323
			 rb_hash_lookup(rb_hash_lookup(coverages, path), ID2SYM(rb_intern("methods"))));
319
	    RB_OBJ_WRITE(iseq->self, &iseq->branch_coverage,
324
	    RB_OBJ_WRITE(iseq->self, &iseq->coverage->branches,
320 325
			 rb_hash_lookup(rb_hash_lookup(coverages, path), ID2SYM(rb_intern("branches"))));
321
	    if (NIL_P(iseq->coverage)) RB_OBJ_WRITE(iseq->self, &iseq->coverage, Qfalse);
322
	    if (NIL_P(iseq->method_coverage)) RB_OBJ_WRITE(iseq->self, &iseq->method_coverage, Qfalse);
323
	    if (NIL_P(iseq->branch_coverage)) RB_OBJ_WRITE(iseq->self, &iseq->branch_coverage, Qfalse);
326
	    if (NIL_P(iseq->coverage->lines)) RB_OBJ_WRITE(iseq->self, &iseq->coverage->lines, Qfalse);
327
	    if (NIL_P(iseq->coverage->methods)) RB_OBJ_WRITE(iseq->self, &iseq->coverage->methods, Qfalse);
328
	    if (NIL_P(iseq->coverage->branches)) RB_OBJ_WRITE(iseq->self, &iseq->coverage->branches, Qfalse);
324 329
	}
325 330
    }
326 331

  
thread.c
5240 5240
static void
5241 5241
update_coverage(rb_event_flag_t event, VALUE proc, VALUE self, ID id, VALUE klass)
5242 5242
{
5243
    VALUE coverage = GET_THREAD()->cfp->iseq->coverage;
5243
    VALUE coverage = GET_THREAD()->cfp->iseq->coverage->lines;
5244 5244
    if (coverage && RBASIC(coverage)->klass == 0) {
5245 5245
	long line = rb_sourceline() - 1;
5246 5246
	long count;
......
5259 5259
{
5260 5260
    VALUE coverage;
5261 5261
    if (event == RUBY_EVENT_MCOVERAGE) {
5262
	coverage = GET_THREAD()->cfp->iseq->method_coverage;
5262
	coverage = GET_THREAD()->cfp->iseq->coverage->methods;
5263 5263
    } else if (event == RUBY_EVENT_BCOVERAGE) {
5264
	coverage = GET_THREAD()->cfp->iseq->branch_coverage;
5264
	coverage = GET_THREAD()->cfp->iseq->coverage->branches;
5265 5265
    } else {
5266 5266
	rb_raise(rb_eArgError, "unknown detailed coverage event");
5267 5267
    }
vm_core.h
201 201
    size_t first_lineno;
202 202
} rb_iseq_location_t;
203 203

  
204
struct rb_coverage_struct {
205
    const VALUE lines;
206
    const VALUE methods;
207
    const VALUE branches;
208
};
209

  
204 210
struct rb_iseq_struct;
205 211

  
206 212
struct rb_iseq_struct {
......
226 232
    VALUE *iseq_encoded; /* encoded iseq */
227 233
    unsigned long iseq_size;
228 234
    const VALUE mark_ary;     /* Array: includes operands which should be GC marked */
229
    const VALUE coverage;        /* coverage array */
230
    const VALUE method_coverage; /* method coverage array */
231
    const VALUE branch_coverage; /* branch coverage array */
235
    struct rb_coverage_struct *coverage;
232 236

  
233 237
    /* insn info, must be freed */
234 238
    struct iseq_line_info_entry *line_info_table;
235
- 
compile.c
241 241
      } \
242 242
  } while (0)
243 243

  
244
#define ADD_BRANCH_COVERAGE_TRACE(seq, line, event) \
244
#define ADD_DECISION_COVERAGE_TRACE(seq, line, event) \
245 245
  do { \
246
      if ((event) == RUBY_EVENT_BRANCH && iseq->coverage->branches) { \
247
	  rb_hash_aset(iseq->coverage->branches, LONG2FIX(line), INT2FIX(0)); \
248
	  ADD_INSN1((seq), (line), trace, INT2FIX(RUBY_EVENT_BCOVERAGE)); \
246
      if (((event) == RUBY_EVENT_DECISION_TRUE || (event) == RUBY_EVENT_DECISION_FALSE) \
247
		      && iseq->coverage->decisions) { \
248
	    rb_hash_aset(iseq->coverage->decisions, LONG2FIX(line), rb_assoc_new(INT2FIX(0), INT2FIX(0))); \
249
	    if ((event) == RUBY_EVENT_DECISION_TRUE) \
250
		ADD_INSN1((seq), (line), trace, INT2FIX(RUBY_EVENT_DCOVERAGE_TRUE)); \
251
	    if ((event) == RUBY_EVENT_DECISION_FALSE) \
252
		ADD_INSN1((seq), (line), trace, INT2FIX(RUBY_EVENT_DCOVERAGE_FALSE)); \
249 253
      } \
250 254
  } while (0)
251 255

  
......
2576 2580

  
2577 2581
	ADD_INSN1(cond_seq, nd_line(vals), checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_CASE));
2578 2582
	ADD_INSNL(cond_seq, nd_line(val), branchif, l1);
2583
	ADD_DECISION_COVERAGE_TRACE(cond_seq, nd_line(vals), RUBY_EVENT_DECISION_FALSE);
2579 2584
	vals = vals->nd_next;
2580 2585
    }
2581 2586
    return only_special_literals;
......
3264 3269
	ADD_SEQ(ret, cond_seq);
3265 3270

  
3266 3271
	ADD_LABEL(ret, then_label);
3267
	if (node->nd_body)
3268
	    ADD_BRANCH_COVERAGE_TRACE(ret, nd_line(node->nd_body), RUBY_EVENT_BRANCH);
3272
	ADD_DECISION_COVERAGE_TRACE(ret, nd_line(node), RUBY_EVENT_DECISION_TRUE);
3269 3273
	ADD_SEQ(ret, then_seq);
3270 3274
	ADD_INSNL(ret, line, jump, end_label);
3271 3275

  
3272 3276
	ADD_LABEL(ret, else_label);
3273
	/* do not trace elsif node */
3274
	if (node->nd_else && nd_type(node->nd_else) != NODE_IF)
3275
	    ADD_BRANCH_COVERAGE_TRACE(ret, nd_line(node->nd_else), RUBY_EVENT_BRANCH);
3277
	ADD_DECISION_COVERAGE_TRACE(ret, nd_line(node), RUBY_EVENT_DECISION_FALSE);
3276 3278
	ADD_SEQ(ret, else_seq);
3277 3279

  
3278 3280
	ADD_LABEL(ret, end_label);
......
3319 3321

  
3320 3322
	    l1 = NEW_LABEL(line);
3321 3323
	    ADD_LABEL(body_seq, l1);
3322
	    if (node->nd_body)
3323
	        ADD_BRANCH_COVERAGE_TRACE(body_seq, nd_line(node->nd_body), RUBY_EVENT_BRANCH);
3324
	    if (node->nd_head)
3325
		ADD_DECISION_COVERAGE_TRACE(body_seq, nd_line(node->nd_head), RUBY_EVENT_DECISION_TRUE);
3324 3326
	    ADD_INSN(body_seq, line, pop);
3325 3327
	    COMPILE_(body_seq, "when body", node->nd_body, poped);
3326 3328
	    ADD_INSNL(body_seq, line, jump, endlabel);
......
3339 3341
		    COMPILE(cond_seq, "when/cond splat", vals);
3340 3342
		    ADD_INSN1(cond_seq, nd_line(vals), checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_CASE | VM_CHECKMATCH_ARRAY));
3341 3343
		    ADD_INSNL(cond_seq, nd_line(vals), branchif, l1);
3344
	            ADD_DECISION_COVERAGE_TRACE(cond_seq, nd_line(vals), RUBY_EVENT_DECISION_FALSE);
3342 3345
		    break;
3343 3346
		  default:
3344 3347
		    rb_bug("NODE_CASE: unknown node (%s)",
......
3359 3362
	/* else */
3360 3363
	if (node) {
3361 3364
	    ADD_LABEL(cond_seq, elselabel);
3362
	    ADD_BRANCH_COVERAGE_TRACE(cond_seq, nd_line(node), RUBY_EVENT_BRANCH);
3363 3365
	    ADD_INSN(cond_seq, line, pop);
3364 3366
	    COMPILE_(cond_seq, "else", node, poped);
3365 3367
	    ADD_INSNL(cond_seq, line, jump, endlabel);
......
3399 3401
	while (node && nd_type(node) == NODE_WHEN) {
3400 3402
	    LABEL *l1 = NEW_LABEL(line = nd_line(node));
3401 3403
	    ADD_LABEL(body_seq, l1);
3402
	    if (node->nd_body)
3403
	        ADD_BRANCH_COVERAGE_TRACE(body_seq, nd_line(node->nd_body), RUBY_EVENT_BRANCH);
3404
	    if (node->nd_head)
3405
		ADD_DECISION_COVERAGE_TRACE(body_seq, nd_line(node->nd_head), RUBY_EVENT_DECISION_TRUE);
3404 3406
	    COMPILE_(body_seq, "when", node->nd_body, poped);
3405 3407
	    ADD_INSNL(body_seq, line, jump, endlabel);
3406 3408

  
......
3414 3416
		    val = vals->nd_head;
3415 3417
		    COMPILE(ret, "when2", val);
3416 3418
		    ADD_INSNL(ret, nd_line(val), branchif, l1);
3419
		    ADD_DECISION_COVERAGE_TRACE(ret, nd_line(vals), RUBY_EVENT_DECISION_FALSE);
3417 3420
		    vals = vals->nd_next;
3418 3421
		}
3419 3422
		break;
......
3424 3427
		COMPILE(ret, "when2/cond splat", vals);
3425 3428
		ADD_INSN1(ret, nd_line(vals), checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_WHEN | VM_CHECKMATCH_ARRAY));
3426 3429
		ADD_INSNL(ret, nd_line(vals), branchif, l1);
3430
		ADD_DECISION_COVERAGE_TRACE(ret, nd_line(vals), RUBY_EVENT_DECISION_FALSE);
3427 3431
		break;
3428 3432
	      default:
3429 3433
		rb_bug("NODE_WHEN: unknown node (%s)",
......
3432 3436
	    node = node->nd_next;
3433 3437
	}
3434 3438
	/* else */
3435
	ADD_BRANCH_COVERAGE_TRACE(ret, nd_line(node), RUBY_EVENT_BRANCH);
3436 3439
	COMPILE_(ret, "else", node, poped);
3437 3440
	ADD_INSNL(ret, nd_line(orig_node), jump, endlabel);
3438 3441

  
ext/coverage/coverage.c
41 41

  
42 42
    VALUE line_coverage = rb_hash_lookup(coverage, ID2SYM(rb_intern("lines")));
43 43
    VALUE method_coverage = rb_hash_lookup(coverage, ID2SYM(rb_intern("methods")));
44
    VALUE branch_coverage = rb_hash_lookup(coverage, ID2SYM(rb_intern("branches")));
44
    VALUE decision_coverage = rb_hash_lookup(coverage, ID2SYM(rb_intern("decisions")));
45 45

  
46 46
    line_coverage = rb_ary_dup(line_coverage);
47 47
    method_coverage = rb_hash_dup(method_coverage);
48
    branch_coverage = rb_hash_dup(branch_coverage);
48
    decision_coverage = rb_hash_dup(decision_coverage);
49 49

  
50 50
    rb_ary_clear(rb_hash_lookup(coverage, ID2SYM(rb_intern("lines"))));
51 51
    rb_hash_clear(rb_hash_lookup(coverage, ID2SYM(rb_intern("methods"))));
52
    rb_hash_clear(rb_hash_lookup(coverage, ID2SYM(rb_intern("branches"))));
52
    rb_hash_clear(rb_hash_lookup(coverage, ID2SYM(rb_intern("decisions"))));
53 53

  
54 54
    coverage = rb_hash_dup(coverage);
55 55

  
56 56
    rb_ary_freeze(line_coverage);
57 57
    rb_hash_freeze(method_coverage);
58
    rb_hash_freeze(branch_coverage);
58
    rb_hash_freeze(decision_coverage);
59 59

  
60 60
    rb_hash_aset(coverage, ID2SYM(rb_intern("lines")), line_coverage);
61 61
    rb_hash_aset(coverage, ID2SYM(rb_intern("methods")), method_coverage);
62
    rb_hash_aset(coverage, ID2SYM(rb_intern("branches")), branch_coverage);
62
    rb_hash_aset(coverage, ID2SYM(rb_intern("decisions")), decision_coverage);
63 63

  
64 64
    rb_ary_freeze(coverage);
65 65
    rb_hash_aset(coverages, path, coverage);
include/ruby/ruby.h
1677 1677
#define RUBY_EVENT_C_RETURN  0x0040
1678 1678
#define RUBY_EVENT_RAISE     0x0080
1679 1679
#define RUBY_EVENT_DEFN      0x0090
1680
#define RUBY_EVENT_BRANCH    0x00a0
1680
#define RUBY_EVENT_DECISION_TRUE  0x00a0
1681
#define RUBY_EVENT_DECISION_FALSE 0x00b0
1681 1682
#define RUBY_EVENT_ALL       0x00ff
1682 1683

  
1683 1684
/* for TracePoint extended events */
......
1691 1692
#define RUBY_EVENT_SPECIFIED_LINE         0x010000
1692 1693
#define RUBY_EVENT_COVERAGE               0x020000
1693 1694
#define RUBY_EVENT_MCOVERAGE              0x040000
1694
#define RUBY_EVENT_BCOVERAGE              0x080000
1695
#define RUBY_EVENT_DCOVERAGE_TRUE         0x080000
1696
#define RUBY_EVENT_DCOVERAGE_FALSE       0x2000000
1695 1697

  
1696 1698
/* internal events */
1697 1699
#define RUBY_INTERNAL_EVENT_SWITCH          0x040000
iseq.c
118 118
	    struct rb_coverage_struct *const coverage = iseq->coverage;
119 119
	    RUBY_MARK_UNLESS_NULL(coverage->lines);
120 120
	    RUBY_MARK_UNLESS_NULL(coverage->methods);
121
	    RUBY_MARK_UNLESS_NULL(coverage->branches);
121
	    RUBY_MARK_UNLESS_NULL(coverage->decisions);
122 122
	}
123 123

  
124 124
	if (iseq->compile_data != 0) {
......
313 313
    iseq->coverage = ALLOC(struct rb_coverage_struct);
314 314
    RB_OBJ_WRITE(iseq->self, &iseq->coverage->lines, Qfalse);
315 315
    RB_OBJ_WRITE(iseq->self, &iseq->coverage->methods, Qfalse);
316
    RB_OBJ_WRITE(iseq->self, &iseq->coverage->branches, Qfalse);
316
    RB_OBJ_WRITE(iseq->self, &iseq->coverage->decisions, Qfalse);
317 317
    if (!GET_THREAD()->parse_in_eval) {
318 318
	VALUE coverages = rb_get_coverages();
319 319
	if (RTEST(coverages)) {
......
321 321
			 rb_hash_lookup(rb_hash_lookup(coverages, path), ID2SYM(rb_intern("lines"))));
322 322
	    RB_OBJ_WRITE(iseq->self, &iseq->coverage->methods,
323 323
			 rb_hash_lookup(rb_hash_lookup(coverages, path), ID2SYM(rb_intern("methods"))));
324
	    RB_OBJ_WRITE(iseq->self, &iseq->coverage->branches,
325
			 rb_hash_lookup(rb_hash_lookup(coverages, path), ID2SYM(rb_intern("branches"))));
324
	    RB_OBJ_WRITE(iseq->self, &iseq->coverage->decisions,
325
			 rb_hash_lookup(rb_hash_lookup(coverages, path), ID2SYM(rb_intern("decisions"))));
326 326
	    if (NIL_P(iseq->coverage->lines)) RB_OBJ_WRITE(iseq->self, &iseq->coverage->lines, Qfalse);
327 327
	    if (NIL_P(iseq->coverage->methods)) RB_OBJ_WRITE(iseq->self, &iseq->coverage->methods, Qfalse);
328
	    if (NIL_P(iseq->coverage->branches)) RB_OBJ_WRITE(iseq->self, &iseq->coverage->branches, Qfalse);
328
	    if (NIL_P(iseq->coverage->decisions)) RB_OBJ_WRITE(iseq->self, &iseq->coverage->decisions, Qfalse);
329 329
	}
330 330
    }
331 331

  
parse.y
5301 5301
	VALUE lines = rb_ary_new2(n);
5302 5302
	VALUE rb_file_coverage = rb_hash_new();
5303 5303
	VALUE methods = rb_hash_new();
5304
	VALUE branches = rb_hash_new();
5304
	VALUE decisions = rb_hash_new();
5305 5305
	int i;
5306 5306
	RBASIC_CLEAR_CLASS(lines);
5307 5307
	for (i = 0; i < n; i++) RARRAY_ASET(lines, i, Qnil);
5308 5308
	RARRAY(lines)->as.heap.len = n;
5309 5309
	rb_hash_aset(rb_file_coverage, ID2SYM(rb_intern("lines")), lines);
5310 5310
	rb_hash_aset(rb_file_coverage, ID2SYM(rb_intern("methods")), methods);
5311
	rb_hash_aset(rb_file_coverage, ID2SYM(rb_intern("branches")), branches);
5311
	rb_hash_aset(rb_file_coverage, ID2SYM(rb_intern("decisions")), decisions);
5312 5312
	rb_hash_aset(coverages, fname, rb_file_coverage);
5313 5313
	return lines;
5314 5314
    }
test/coverage/test_branch_coverage.rb
30 30

  
31 31
        Coverage.start
32 32
        require tmp + '/test.rb'
33
        branch_coverage = Coverage.result[tmp + '/test.rb'][:branches]
34
        assert_equal 6, branch_coverage.size
35
        assert_equal 1, branch_coverage[2]
36
        assert_equal 1, branch_coverage[5]
37
        assert_equal 0, branch_coverage[6]
38
        assert_equal 0, branch_coverage[10]
39
        assert_equal 0, branch_coverage[12]
40
        assert_equal 1, branch_coverage[14]
33
        decision_coverage = Coverage.result[tmp + '/test.rb'][:decisions]
34
        assert_equal 5, decision_coverage.size
35
        assert_equal [1,0], decision_coverage[1]
36
        assert_equal [0,1], decision_coverage[5]
37
        assert_equal [1,0], decision_coverage[6]
38
        assert_equal [0,1], decision_coverage[9]
39
        assert_equal [0,1], decision_coverage[11]
41 40
      }
42 41
    }
43 42
  ensure
......
51 50
      Dir.chdir(tmp) {
52 51
        File.open("test.rb", "w") do |f|
53 52
          f.puts <<-EOS
54
            case
55
            when 2 + 2 == 5
56
              x = :bad
57
              x = :math
58
            when 2 + 2 == 4
59
              x = :good
60
            else
61
              x = :also_bad
53
            def foo(arg)
54
              case
55
              when arg == 1
56
                x = :one
57
              when arg == 2
58
                x = :two
59
              else
60
                x = :none
61
              end
62 62
            end
63
            foo(2); foo(2); foo(3)
63 64

  
64
            case [1,2,3]
65
            when String
66
              :string?
67
            else
68
              :else
65
            def bar(arg)
66
              case arg
67
              when String
68
                :string?
69
              when 1
70
                :one
71
              else
72
                :else
73
              end
74
            end
75
            bar("BAR"); bar("BAZ"); bar(1); bar(7)
76
          EOS
77
        end
78

  
79
        Coverage.start
80
        require tmp + '/test.rb'
81
        decision_coverage = Coverage.result[tmp + '/test.rb'][:decisions]
82
        assert_equal 4, decision_coverage.size
83
        assert_equal [0,3], decision_coverage[3]
84
        assert_equal [2,1], decision_coverage[5]
85
        assert_equal [2,2], decision_coverage[15]
86
        assert_equal [1,1], decision_coverage[17]
87
      }
88
    }
89
  ensure
90
    $".replace loaded_features
91
  end
92

  
93
  def test_when_without_else_coverage
94
    loaded_features = $".dup
95

  
96
    Dir.mktmpdir {|tmp|
97
      Dir.chdir(tmp) {
98
        File.open("test.rb", "w") do |f|
99
          f.puts <<-EOS
100
            def foo(arg)
101
              case
102
              when arg == 1
103
                x = :one
104
              when arg == 2
105
                x = :two
106
              end
107
            end
108
            foo(2); foo(2); foo(3)
109

  
110
            def bar(arg)
111
              case arg
112
              when String
113
                :string?
114
              when 1
115
                :one
116
              end
69 117
            end
118
            bar("BAR"); bar("BAZ"); bar(1); bar(7)
119
          EOS
120
        end
121

  
122
        Coverage.start
123
        require tmp + '/test.rb'
124
        decision_coverage = Coverage.result[tmp + '/test.rb'][:decisions]
125
        assert_equal 4, decision_coverage.size
126
        assert_equal [0,3], decision_coverage[3]
127
        assert_equal [2,1], decision_coverage[5]
128
        assert_equal [2,2], decision_coverage[13]
129
        assert_equal [1,1], decision_coverage[15]
130
      }
131
    }
132
  ensure
133
    $".replace loaded_features
134
  end
135

  
136
  def test_when_with_splats_coverage
137
    loaded_features = $".dup
138

  
139
    Dir.mktmpdir {|tmp|
140
      Dir.chdir(tmp) {
141
        File.open("test.rb", "w") do |f|
142
          f.puts <<-EOS
143
            def prime?(arg)
144
              primes = [2,3,5,7]
145
              composites = [4,6,8,9]
146

  
147
              case arg
148
              when *primes
149
                :prime
150
              when *composites
151
                :composite
152
              end
153
            end
154

  
155
            9.times {|i| prime?(i+1) }
70 156
          EOS
71 157
        end
72 158

  
73 159
        Coverage.start
74 160
        require tmp + '/test.rb'
75
        branch_coverage = Coverage.result[tmp + '/test.rb'][:branches]
76
        assert_equal 5, branch_coverage.size
77
        assert_equal 0, branch_coverage[3]
78
        assert_equal 1, branch_coverage[6]
... This diff was truncated because it exceeds the maximum size that can be displayed.