Project

General

Profile

Feature #15022 ยป oneshot-coverage.patch

mame (Yusuke Endoh), 08/24/2018 01:28 PM

View differences:

compile.c
1983 1983
		sp = calc_sp_depth(sp, iobj);
1984 1984
		code_index += insn_data_length(iobj);
1985 1985
		insn_num++;
1986
		if (ISEQ_COVERAGE(iseq) && ISEQ_LINE_COVERAGE(iseq) && (events & RUBY_EVENT_COVERAGE_LINE)) {
1986
                if (ISEQ_COVERAGE(iseq) && ISEQ_LINE_COVERAGE(iseq) &&
1987
                    (events & RUBY_EVENT_COVERAGE_LINE) &&
1988
                    !(rb_get_coverage_mode() & COVERAGE_TARGET_ONESHOT_LINES)) {
1987 1989
		    int line = iobj->insn_info.line_no;
1988 1990
		    RARRAY_ASET(ISEQ_LINE_COVERAGE(iseq), line - 1, INT2FIX(0));
1989 1991
		}
ext/coverage/coverage.c
45 45
	    mode |= COVERAGE_TARGET_BRANCHES;
46 46
	if (RTEST(rb_hash_lookup(opt, ID2SYM(rb_intern("methods")))))
47 47
	    mode |= COVERAGE_TARGET_METHODS;
48
	if (mode == 0) {
49
	    rb_raise(rb_eRuntimeError, "no measuring target is specified");
50
	}
48
	if (RTEST(rb_hash_lookup(opt, ID2SYM(rb_intern("oneshot_lines"))))) {
49
            if (mode & COVERAGE_TARGET_LINES)
50
                rb_raise(rb_eRuntimeError, "cannot enable lines and oneshot_lines simultaneously");
51
            mode |= COVERAGE_TARGET_LINES;
52
            mode |= COVERAGE_TARGET_ONESHOT_LINES;
53
        }
51 54
    }
52 55

  
53 56
    if (mode & COVERAGE_TARGET_METHODS) {
......
179 182

  
180 183
	if (current_mode & COVERAGE_TARGET_LINES) {
181 184
	    VALUE lines = RARRAY_AREF(coverage, COVERAGE_INDEX_LINES);
185
            const char *kw = (current_mode & COVERAGE_TARGET_ONESHOT_LINES) ? "oneshot_lines" : "lines";
182 186
	    lines = rb_ary_dup(lines);
183 187
	    rb_ary_freeze(lines);
184
	    rb_hash_aset(h, ID2SYM(rb_intern("lines")), lines);
188
	    rb_hash_aset(h, ID2SYM(rb_intern(kw)), lines);
185 189
	}
186 190

  
187 191
	if (current_mode & COVERAGE_TARGET_BRANCHES) {
......
239 243
static VALUE
240 244
rb_coverage_result(VALUE klass)
241 245
{
242
    VALUE ncoverages = rb_coverage_peek_result(klass);
246
    VALUE ncoverages;
247

  
248
    ncoverages = rb_coverage_peek_result(klass);
243 249
    rb_reset_coverages();
244 250
    me2counter = Qnil;
245 251
    return ncoverages;
246 252
}
247 253

  
254

  
255
static int
256
clear_me2counter_i(VALUE key, VALUE value, VALUE unused)
257
{
258
    rb_hash_aset(me2counter, key, INT2FIX(0));
259
    return ST_CONTINUE;
260
}
261

  
262
/*
263
 *  call-seq:
264
 *     Coverage.clear  => nil
265
 *
266
 * Clears the internal counters of coverage.
267
 */
268
static VALUE
269
rb_coverage_clear(VALUE klass)
270
{
271
    rb_clear_coverages();
272
    if (!NIL_P(me2counter)) rb_hash_foreach(me2counter, clear_me2counter_i, Qnil);
273
    return Qnil;
274
}
275

  
248 276
/*
249 277
 *  call-seq:
250 278
 *     Coverage.running?  => bool
......
298 326
    VALUE rb_mCoverage = rb_define_module("Coverage");
299 327
    rb_define_module_function(rb_mCoverage, "start", rb_coverage_start, -1);
300 328
    rb_define_module_function(rb_mCoverage, "result", rb_coverage_result, 0);
329
    rb_define_module_function(rb_mCoverage, "clear", rb_coverage_clear, 0);
301 330
    rb_define_module_function(rb_mCoverage, "peek_result", rb_coverage_peek_result, 0);
302 331
    rb_define_module_function(rb_mCoverage, "running?", rb_coverage_running, 0);
303 332
    rb_global_variable(&me2counter);
internal.h
1857 1857
#define COVERAGE_TARGET_LINES    1
1858 1858
#define COVERAGE_TARGET_BRANCHES 2
1859 1859
#define COVERAGE_TARGET_METHODS  4
1860
#define COVERAGE_TARGET_ONESHOT_LINES 8
1860 1861

  
1861 1862
VALUE rb_obj_is_mutex(VALUE obj);
1862 1863
VALUE rb_suppress_tracing(VALUE (*func)(VALUE), VALUE arg);
1863 1864
void rb_thread_execute_interrupts(VALUE th);
1864 1865
void rb_clear_trace_func(void);
1865 1866
VALUE rb_get_coverages(void);
1867
int rb_get_coverage_mode(void);
1866 1868
VALUE rb_default_coverage(int);
1867 1869
VALUE rb_thread_shield_new(void);
1868 1870
VALUE rb_thread_shield_wait(VALUE self);
iseq.c
656 656
    VALUE coverages = rb_get_coverages();
657 657
    if (RTEST(coverages)) {
658 658
        if (ast->line_count >= 0) {
659
            VALUE coverage = rb_default_coverage(ast->line_count);
659
            int len = (rb_get_coverage_mode() & COVERAGE_TARGET_ONESHOT_LINES) ? 0 : ast->line_count;
660
            VALUE coverage = rb_default_coverage(len);
660 661
            rb_hash_aset(coverages, path, coverage);
661 662
        }
662 663
    }
......
1650 1651
    }
1651 1652
}
1652 1653

  
1654
void
1655
rb_iseq_clear_event_flags(const rb_iseq_t *iseq, size_t pos, rb_event_flag_t reset)
1656
{
1657
    struct iseq_insn_info_entry *entry = (struct iseq_insn_info_entry *)get_insn_info(iseq, pos);
1658
    if (entry) {
1659
	entry->events &= ~reset;
1660
        if (!(entry->events & iseq->aux.trace_events)) {
1661
            void rb_iseq_trace_flag_cleared(const rb_iseq_t *iseq, int pos);
1662
            rb_iseq_trace_flag_cleared(iseq, pos);
1663
        }
1664
    }
1665
}
1666

  
1653 1667
static VALUE
1654 1668
local_var_name(const rb_iseq_t *diseq, VALUE level, VALUE op)
1655 1669
{
......
2929 2943
    rb_bug("trace_instrument: invalid insn address: %p", (void *)*iseq_encoded_insn);
2930 2944
}
2931 2945

  
2946
void
2947
rb_iseq_trace_flag_cleared(const rb_iseq_t *iseq, int pos)
2948
{
2949
    const struct rb_iseq_constant_body *const body = iseq->body;
2950
    VALUE *iseq_encoded = (VALUE *)body->iseq_encoded;
2951
    encoded_iseq_trace_instrument(&iseq_encoded[pos], 0);
2952
}
2953

  
2932 2954
void
2933 2955
rb_iseq_trace_set(const rb_iseq_t *iseq, rb_event_flag_t turnon_events)
2934 2956
{
test/coverage/test_coverage.rb
483 483
    }
484 484
    assert_coverage(code, { methods: true }, result)
485 485
  end
486

  
487
  def test_oneshot_line_coverage
488
    result = {
489
      :oneshot_lines => [2, 6, 10, 12, 17, 18, 25, 20]
490
    }
491
    assert_coverage(<<~"end;", { oneshot_lines: true }, result)
492
      FOO = [
493
        { foo: 'bar' }, # 2
494
        { bar: 'baz' }
495
      ]
496

  
497
      'some string'.split # 6
498
                   .map(&:length)
499

  
500
      some =
501
        'value' # 10
502

  
503
      Struct.new( # 12
504
        :foo,
505
        :bar
506
      ).new
507

  
508
      class Test # 17
509
        def foo(bar) # 18
510
          {
511
            foo: bar # 20
512
          }
513
        end
514
      end
515

  
516
      Test.new.foo(Object.new) # 25
517
    end;
518
  end
519

  
520
  def test_clear_with_lines
521
    Dir.mktmpdir {|tmp|
522
      Dir.chdir(tmp) {
523
        File.open("test.rb", "w") do |f|
524
          f.puts "def foo(x)"
525
          f.puts "  if x > 0"
526
          f.puts "    :pos"
527
          f.puts "  else"
528
          f.puts "    :non_pos"
529
          f.puts "  end"
530
          f.puts "end"
531
        end
532

  
533
        exp = [
534
          "{:lines=>[1, 0, 0, nil, 0, nil, nil]}",
535
          "{:lines=>[0, 1, 1, nil, 0, nil, nil]}",
536
          "{:lines=>[0, 1, 0, nil, 1, nil, nil]}",
537
        ]
538
        assert_in_out_err(%w[-rcoverage], <<-"end;", exp, [])
539
          Coverage.start(lines: true)
540
          tmp = Dir.pwd
541
          f = tmp + "/test.rb"
542
          require f
543
          p Coverage.peek_result[f]
544
          Coverage.clear
545
          foo(1)
546
          p Coverage.peek_result[f]
547
          Coverage.clear
548
          foo(-1)
549
          p Coverage.peek_result[f]
550
        end;
551
      }
552
    }
553
  end
554

  
555
  def test_clear_with_branches
556
    Dir.mktmpdir {|tmp|
557
      Dir.chdir(tmp) {
558
        File.open("test.rb", "w") do |f|
559
          f.puts "def foo(x)"
560
          f.puts "  if x > 0"
561
          f.puts "    :pos"
562
          f.puts "  else"
563
          f.puts "    :non_pos"
564
          f.puts "  end"
565
          f.puts "end"
566
        end
567

  
568
        exp = [
569
          "{:branches=>{[:if, 0, 2, 2, 6, 5]=>{[:then, 1, 3, 4, 3, 8]=>0, [:else, 2, 5, 4, 5, 12]=>0}}}",
570
          "{:branches=>{[:if, 0, 2, 2, 6, 5]=>{[:then, 1, 3, 4, 3, 8]=>1, [:else, 2, 5, 4, 5, 12]=>0}}}",
571
          "{:branches=>{[:if, 0, 2, 2, 6, 5]=>{[:then, 1, 3, 4, 3, 8]=>0, [:else, 2, 5, 4, 5, 12]=>1}}}",
572
          "{:branches=>{[:if, 0, 2, 2, 6, 5]=>{[:then, 1, 3, 4, 3, 8]=>0, [:else, 2, 5, 4, 5, 12]=>1}}}",
573
        ]
574
        assert_in_out_err(%w[-rcoverage], <<-"end;", exp, [])
575
          Coverage.start(branches: true)
576
          tmp = Dir.pwd
577
          f = tmp + "/test.rb"
578
          require f
579
          p Coverage.peek_result[f]
580
          Coverage.clear
581
          foo(1)
582
          p Coverage.peek_result[f]
583
          Coverage.clear
584
          foo(-1)
585
          p Coverage.peek_result[f]
586
          Coverage.clear
587
          foo(-1)
588
          p Coverage.peek_result[f]
589
        end;
590
      }
591
    }
592
  end
593

  
594
  def test_clear_with_methods
595
    Dir.mktmpdir {|tmp|
596
      Dir.chdir(tmp) {
597
        File.open("test.rb", "w") do |f|
598
          f.puts "def foo(x)"
599
          f.puts "  if x > 0"
600
          f.puts "    :pos"
601
          f.puts "  else"
602
          f.puts "    :non_pos"
603
          f.puts "  end"
604
          f.puts "end"
605
        end
606

  
607
        exp = [
608
          "{:methods=>{[Object, :foo, 1, 0, 7, 3]=>0}}",
609
          "{:methods=>{[Object, :foo, 1, 0, 7, 3]=>1}}",
610
          "{:methods=>{[Object, :foo, 1, 0, 7, 3]=>1}}",
611
          "{:methods=>{[Object, :foo, 1, 0, 7, 3]=>1}}"
612
        ]
613
        assert_in_out_err(%w[-rcoverage], <<-"end;", exp, [])
614
          Coverage.start(methods: true)
615
          tmp = Dir.pwd
616
          f = tmp + "/test.rb"
617
          require f
618
          p Coverage.peek_result[f]
619
          Coverage.clear
620
          foo(1)
621
          p Coverage.peek_result[f]
622
          Coverage.clear
623
          foo(-1)
624
          p Coverage.peek_result[f]
625
          Coverage.clear
626
          foo(-1)
627
          p Coverage.peek_result[f]
628
        end;
629
      }
630
    }
631
  end
632

  
633
  def test_clear_with_oneshot_lines
634
    Dir.mktmpdir {|tmp|
635
      Dir.chdir(tmp) {
636
        File.open("test.rb", "w") do |f|
637
          f.puts "def foo(x)"
638
          f.puts "  if x > 0"
639
          f.puts "    :pos"
640
          f.puts "  else"
641
          f.puts "    :non_pos"
642
          f.puts "  end"
643
          f.puts "end"
644
        end
645

  
646
        exp = [
647
          "{:oneshot_lines=>[1]}",
648
          "{:oneshot_lines=>[2, 3]}",
649
          "{:oneshot_lines=>[5]}",
650
          "{:oneshot_lines=>[]}",
651
        ]
652
        assert_in_out_err(%w[-rcoverage], <<-"end;", exp, [])
653
          Coverage.start(oneshot_lines: true)
654
          tmp = Dir.pwd
655
          f = tmp + "/test.rb"
656
          require f
657
          p Coverage.peek_result[f]
658
          Coverage.clear
659
          foo(1)
660
          p Coverage.peek_result[f]
661
          Coverage.clear
662
          foo(-1)
663
          p Coverage.peek_result[f]
664
          Coverage.clear
665
          foo(-1)
666
          p Coverage.peek_result[f]
667
        end;
668
      }
669
    }
670
  end
671

  
672
  def test_line_stub
673
    Dir.mktmpdir {|tmp|
674
      Dir.chdir(tmp) {
675
        File.open("test.rb", "w") do |f|
676
          f.puts "def foo(x)"
677
          f.puts "  if x > 0"
678
          f.puts "    :pos"
679
          f.puts "  else"
680
          f.puts "    :non_pos"
681
          f.puts "  end"
682
          f.puts "end"
683
        end
684

  
685
        assert_equal([0, 0, 0, nil, 0, nil, 0], Coverage.line_stub("test.rb"))
686
      }
687
    }
688
  end
486 689
end
thread.c
4362 4362
    VALUE branches = RARRAY_AREF(coverage, COVERAGE_INDEX_BRANCHES);
4363 4363

  
4364 4364
    if (lines) {
4365
	for (i = 0; i < RARRAY_LEN(lines); i++) {
4366
	    if (RARRAY_AREF(lines, i) != Qnil) {
4367
		RARRAY_ASET(lines, i, INT2FIX(0));
4368
	    }
4369
	}
4365
        if (GET_VM()->coverage_mode & COVERAGE_TARGET_ONESHOT_LINES) {
4366
            rb_ary_clear(lines);
4367
        }
4368
        else {
4369
            int i;
4370
            for (i = 0; i < RARRAY_LEN(lines); i++) {
4371
                if (RARRAY_AREF(lines, i) != Qnil)
4372
                    RARRAY_ASET(lines, i, INT2FIX(0));
4373
            }
4374
        }
4370 4375
    }
4371 4376
    if (branches) {
4372 4377
	VALUE counters = RARRAY_AREF(branches, 1);
......
4378 4383
    return ST_CONTINUE;
4379 4384
}
4380 4385

  
4381
static void
4382
clear_coverage(void)
4386
void
4387
rb_clear_coverages(void)
4383 4388
{
4384 4389
    VALUE coverages = rb_get_coverages();
4385 4390
    if (RTEST(coverages)) {
......
4408 4413
    vm->fork_gen++;
4409 4414

  
4410 4415
    vm->sleeper = 0;
4411
    clear_coverage();
4416
    rb_clear_coverages();
4412 4417
}
4413 4418

  
4414 4419
static void
......
5252 5257
static void
5253 5258
update_line_coverage(VALUE data, const rb_trace_arg_t *trace_arg)
5254 5259
{
5255
    VALUE coverage = rb_iseq_coverage(GET_EC()->cfp->iseq);
5260
    const rb_control_frame_t *cfp = GET_EC()->cfp;
5261
    VALUE coverage = rb_iseq_coverage(cfp->iseq);
5256 5262
    if (RB_TYPE_P(coverage, T_ARRAY) && !RBASIC_CLASS(coverage)) {
5257 5263
	VALUE lines = RARRAY_AREF(coverage, COVERAGE_INDEX_LINES);
5258 5264
	if (lines) {
5259 5265
	    long line = rb_sourceline() - 1;
5260 5266
	    long count;
5261 5267
	    VALUE num;
5268
            void rb_iseq_clear_event_flags(const rb_iseq_t *iseq, size_t pos, rb_event_flag_t reset);
5269
            if (GET_VM()->coverage_mode & COVERAGE_TARGET_ONESHOT_LINES) {
5270
                rb_iseq_clear_event_flags(cfp->iseq, cfp->pc - cfp->iseq->body->iseq_encoded - 1, RUBY_EVENT_COVERAGE_LINE);
5271
                rb_ary_push(lines, LONG2FIX(line + 1));
5272
                return;
5273
            }
5262 5274
	    if (line >= RARRAY_LEN(lines)) { /* no longer tracked */
5263 5275
		return;
5264 5276
	    }
......
5373 5385
    return GET_VM()->coverages;
5374 5386
}
5375 5387

  
5388
int
5389
rb_get_coverage_mode(void)
5390
{
5391
    return GET_VM()->coverage_mode;
5392
}
5393

  
5376 5394
void
5377 5395
rb_set_coverages(VALUE coverages, int mode, VALUE me2counter)
5378 5396
{
......
5388 5406
}
5389 5407

  
5390 5408
/* Make coverage arrays empty so old covered files are no longer tracked. */
5391
static int
5392
reset_coverage_i(st_data_t key, st_data_t val, st_data_t dummy)
5393
{
5394
    VALUE coverage = (VALUE)val;
5395
    VALUE lines = RARRAY_AREF(coverage, COVERAGE_INDEX_LINES);
5396
    VALUE branches = RARRAY_AREF(coverage, COVERAGE_INDEX_BRANCHES);
5397
    if (lines) rb_ary_clear(lines);
5398
    if (branches) rb_ary_clear(branches);
5399
    return ST_CONTINUE;
5400
}
5401

  
5402 5409
void
5403 5410
rb_reset_coverages(void)
5404 5411
{
5405
    VALUE coverages = rb_get_coverages();
5406
    st_foreach(rb_hash_tbl_raw(coverages), reset_coverage_i, 0);
5412
    rb_clear_coverages();
5407 5413
    rb_iseq_remove_coverage_all();
5408 5414
    GET_VM()->coverages = Qfalse;
5409 5415
    rb_remove_event_hook((rb_event_hook_func_t) update_line_coverage);
vm_core.h
1822 1822

  
1823 1823
extern VALUE rb_get_coverages(void);
1824 1824
extern void rb_set_coverages(VALUE, int, VALUE);
1825
extern void rb_clear_coverages(void);
1825 1826
extern void rb_reset_coverages(void);
1826 1827

  
1827 1828
void rb_postponed_job_flush(rb_vm_t *vm);