From 9434a7333c2a23c680a977331a60ca7c502c1ac0 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 17 Sep 2022 20:19:57 +1200 Subject: [PATCH] Enable coverage for eval. --- compile.c | 6 ++-- iseq.c | 13 ++++++++- spec/ruby/library/coverage/result_spec.rb | 34 +++++++++++++++++------ test/coverage/test_coverage.rb | 17 +++++++++++- vm_eval.c | 8 ++++++ 5 files changed, 64 insertions(+), 14 deletions(-) diff --git a/compile.c b/compile.c index 45cb116983..7d553bfba1 100644 --- a/compile.c +++ b/compile.c @@ -2308,9 +2308,9 @@ iseq_set_sequence(rb_iseq_t *iseq, LINK_ANCHOR *const anchor) if (ISEQ_COVERAGE(iseq)) { if (ISEQ_LINE_COVERAGE(iseq) && (events & RUBY_EVENT_COVERAGE_LINE) && !(rb_get_coverage_mode() & COVERAGE_TARGET_ONESHOT_LINES)) { - int line = iobj->insn_info.line_no; - if (line >= 1) { - RARRAY_ASET(ISEQ_LINE_COVERAGE(iseq), line - 1, INT2FIX(0)); + int line = iobj->insn_info.line_no - 1; + if (line >= 0 && line < RARRAY_LEN(ISEQ_LINE_COVERAGE(iseq))) { + RARRAY_ASET(ISEQ_LINE_COVERAGE(iseq), line, INT2FIX(0)); } } if (ISEQ_BRANCH_COVERAGE(iseq) && (events & RUBY_EVENT_COVERAGE_BRANCH)) { diff --git a/iseq.c b/iseq.c index 4892d93df1..9359fcfe4e 100644 --- a/iseq.c +++ b/iseq.c @@ -697,7 +697,6 @@ prepare_iseq_build(rb_iseq_t *iseq, ISEQ_COMPILE_DATA(iseq)->ivar_cache_table = NULL; ISEQ_COMPILE_DATA(iseq)->builtin_function_table = GET_VM()->builtin_function_table; - if (option->coverage_enabled) { VALUE coverages = rb_get_coverages(); if (RTEST(coverages)) { @@ -928,6 +927,18 @@ rb_iseq_new_main(const rb_ast_body_t *ast, VALUE path, VALUE realpath, const rb_ rb_iseq_t * rb_iseq_new_eval(const rb_ast_body_t *ast, VALUE name, VALUE path, VALUE realpath, VALUE first_lineno, const rb_iseq_t *parent, int isolated_depth) { + VALUE coverages = rb_get_coverages(); + if (RTEST(coverages) && RTEST(path) && !RTEST(rb_hash_has_key(coverages, path))) { + int line_offset = RB_NUM2INT(first_lineno) - 1; + int line_count = line_offset + ast_line_count(ast); + + if (line_count >= 0) { + int len = (rb_get_coverage_mode() & COVERAGE_TARGET_ONESHOT_LINES) ? 0 : line_count; + VALUE coverage = rb_default_coverage(len); + rb_hash_aset(coverages, path, coverage); + } + } + return rb_iseq_new_with_opt(ast, name, path, realpath, first_lineno, parent, isolated_depth, ISEQ_TYPE_EVAL, &COMPILE_OPTION_DEFAULT); } diff --git a/spec/ruby/library/coverage/result_spec.rb b/spec/ruby/library/coverage/result_spec.rb index 4cc43e8462..61283e4545 100644 --- a/spec/ruby/library/coverage/result_spec.rb +++ b/spec/ruby/library/coverage/result_spec.rb @@ -91,15 +91,31 @@ describe 'Coverage.result' do Coverage.result.should_not include(@config_file) end - it 'returns the correct results when eval is used' do - Coverage.start - require @eval_code_file.chomp('.rb') - result = Coverage.result + ruby_version_is '3.1'...'3.2' do + it 'returns the correct results when eval is used' do + Coverage.start + require @eval_code_file.chomp('.rb') + result = Coverage.result - result.should == { - @eval_code_file => [ - 1, nil, 1, nil, 1, nil, nil, nil, nil, nil, 1 - ] - } + result.should == { + @eval_code_file => [ + 1, nil, 1, nil, 1, nil, nil, nil, nil, nil, 1 + ] + } + end + end + + ruby_version_is '3.2' do + it 'returns the correct results when eval is used' do + Coverage.start + require @eval_code_file.chomp('.rb') + result = Coverage.result + + result.should == { + @eval_code_file => [ + 1, nil, 1, nil, 1, 1, nil, nil, nil, nil, 1 + ] + } + end end end diff --git a/test/coverage/test_coverage.rb b/test/coverage/test_coverage.rb index eefe7e7da6..1bb85b67b3 100644 --- a/test/coverage/test_coverage.rb +++ b/test/coverage/test_coverage.rb @@ -136,7 +136,7 @@ class TestCoverage < Test::Unit::TestCase f.puts 'REPEATS = 400' f.puts 'def add_method(target)' f.puts ' REPEATS.times do' - f.puts ' target.class_eval(<<~RUBY, __FILE__, __LINE__ + 1)' + f.puts ' target.class_eval(<<~RUBY)' f.puts ' def foo' f.puts ' #{"\n" * rand(REPEATS)}' f.puts ' end' @@ -157,6 +157,21 @@ class TestCoverage < Test::Unit::TestCase } end + def test_eval_coverage + assert_in_out_err(%w[-rcoverage], <<-"end;", ["[1, nil, 1, nil]"], []) + Coverage.start + + eval(<<-RUBY, TOPLEVEL_BINDING, "test.rb") + s = String.new + begin + s << "foo + bar".freeze; end + RUBY + + p Coverage.result["test.rb"] + end; + end + def test_nocoverage_optimized_line assert_ruby_status(%w[], "#{<<-"begin;"}\n#{<<-'end;'}") begin; diff --git a/vm_eval.c b/vm_eval.c index c7ad71e279..9d7a736ad9 100644 --- a/vm_eval.c +++ b/vm_eval.c @@ -1672,6 +1672,8 @@ eval_make_iseq(VALUE src, VALUE fname, int line, const rb_binding_t *bind, rb_iseq_t *iseq = NULL; rb_ast_t *ast; int isolated_depth = 0; + int coverage_enabled = Qtrue; + { int depth = 1; const VALUE *ep = vm_block_ep(base_block); @@ -1703,11 +1705,17 @@ eval_make_iseq(VALUE src, VALUE fname, int line, const rb_binding_t *bind, rb_gc_register_mark_object(eval_default_path); } fname = eval_default_path; + coverage_enabled = Qfalse; } rb_parser_set_context(parser, parent, FALSE); ast = rb_parser_compile_string_path(parser, fname, src, line); if (ast->body.root) { + if (ast->body.compile_option == Qnil) { + ast->body.compile_option = rb_obj_hide(rb_ident_hash_new()); + } + rb_hash_aset(ast->body.compile_option, rb_sym_intern_ascii_cstr("coverage_enabled"), coverage_enabled); + iseq = rb_iseq_new_eval(&ast->body, ISEQ_BODY(parent)->location.label, fname, Qnil, INT2FIX(line),