From 47ea999b4689fc591478a05da1670d2008a4a705 Mon Sep 17 00:00:00 2001 From: mame Date: Sat, 20 Oct 2018 05:33:04 +0000 Subject: ext/coverage/: add the oneshot mode This patch introduces "oneshot_lines" mode for `Coverage.start`, which checks "whether each line was executed at least once or not", instead of "how many times each line was executed". A hook for each line is fired at most once, and after it is fired, the hook flag was removed; it runs with zero overhead. See [Feature #15022] in detail. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@65195 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- ext/coverage/coverage.c | 61 +++++++++++++++++++++++++++++++++++--------- ext/coverage/lib/coverage.rb | 14 ++++++++++ 2 files changed, 63 insertions(+), 12 deletions(-) create mode 100644 ext/coverage/lib/coverage.rb (limited to 'ext') diff --git a/ext/coverage/coverage.c b/ext/coverage/coverage.c index f46955aae2..cf6e0a83c8 100644 --- a/ext/coverage/coverage.c +++ b/ext/coverage/coverage.c @@ -45,9 +45,12 @@ rb_coverage_start(int argc, VALUE *argv, VALUE klass) 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) { @@ -179,9 +182,10 @@ coverage_peek_result_i(st_data_t key, st_data_t val, st_data_t h) 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) { @@ -205,6 +209,7 @@ coverage_peek_result_i(st_data_t key, st_data_t val, st_data_t h) * Coverage.peek_result => hash * * Returns a hash that contains filename as key and coverage array as value. + * This is the same as `Coverage.result(stop: false, clear: false)`. * * { * "file.rb" => [1, 2, nil], @@ -229,22 +234,54 @@ rb_coverage_peek_result(VALUE klass) 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.result => hash + * Coverage.result(stop: true, clear: true) => hash * - * Returns a hash that contains filename as key and coverage array as value - * and disables coverage measurement. + * Returns a hash that contains filename as key and coverage array as value. + * If +clear+ is true, it clears the counters to zero. + * If +stop+ is true, it disables coverage measurement. */ static VALUE -rb_coverage_result(VALUE klass) +rb_coverage_result(int argc, VALUE *argv, VALUE klass) { - VALUE ncoverages = rb_coverage_peek_result(klass); - rb_reset_coverages(); - me2counter = Qnil; + VALUE ncoverages; + VALUE opt; + int stop = 1, clear = 1; + + rb_scan_args(argc, argv, "01", &opt); + + if (argc == 1) { + opt = rb_convert_type(opt, T_HASH, "Hash", "to_hash"); + stop = RTEST(rb_hash_lookup(opt, ID2SYM(rb_intern("stop")))); + clear = RTEST(rb_hash_lookup(opt, ID2SYM(rb_intern("clear")))); + } + + ncoverages = rb_coverage_peek_result(klass); + if (stop && !clear) { + rb_warn("stop implies clear"); + clear = 1; + } + if (clear) { + rb_clear_coverages(); + if (!NIL_P(me2counter)) rb_hash_foreach(me2counter, clear_me2counter_i, Qnil); + } + if (stop) { + rb_reset_coverages(); + me2counter = Qnil; + } return ncoverages; } + /* * call-seq: * Coverage.running? => bool @@ -297,7 +334,7 @@ Init_coverage(void) { 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, "result", rb_coverage_result, -1); 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); diff --git a/ext/coverage/lib/coverage.rb b/ext/coverage/lib/coverage.rb new file mode 100644 index 0000000000..7a6ef0e100 --- /dev/null +++ b/ext/coverage/lib/coverage.rb @@ -0,0 +1,14 @@ +require "coverage.so" + +module Coverage + def self.line_stub(file) + lines = File.foreach(file).map { nil } + iseqs = [RubyVM::InstructionSequence.compile_file(file)] + until iseqs.empty? + iseq = iseqs.pop + iseq.trace_points.each {|n, _| lines[n - 1] = 0 } + iseq.each_child {|child| iseqs << child } + end + lines + end +end -- cgit v1.2.3