[前][次][番号順一覧][スレッド一覧]

ruby-changes:69413

From: Yusuke <ko1@a...>
Date: Mon, 25 Oct 2021 20:01:08 +0900 (JST)
Subject: [ruby-changes:69413] 86e3d77abb (master): Make Coverage suspendable (#4856)

https://git.ruby-lang.org/ruby.git/commit/?id=86e3d77abb

From 86e3d77abb8a033650937710d1ab009e98647494 Mon Sep 17 00:00:00 2001
From: Yusuke Endoh <mame@r...>
Date: Mon, 25 Oct 2021 20:00:51 +0900
Subject: Make Coverage suspendable (#4856)

* Make Coverage suspendable

Add `Coverage.suspend`, `Coverage.resume` and some methods.

[Feature #18176] [ruby-core:105321]
---
 ext/coverage/coverage.c                   | 123 ++++++++++++++++++++++++-
 spec/ruby/library/coverage/result_spec.rb |  23 ++++-
 test/coverage/test_coverage.rb            | 146 ++++++++++++++++++++++++++++++
 thread.c                                  |  23 ++++-
 vm.c                                      |   2 +
 vm_core.h                                 |   4 +-
 6 files changed, 305 insertions(+), 16 deletions(-)

diff --git a/ext/coverage/coverage.c b/ext/coverage/coverage.c
index 0af5579ffc5..f948f623078 100644
--- a/ext/coverage/coverage.c
+++ b/ext/coverage/coverage.c
@@ -15,21 +15,38 @@ https://github.com/ruby/ruby/blob/trunk/ext/coverage/coverage.c#L15
 #include "ruby.h"
 #include "vm_core.h"
 
+static enum {
+    IDLE,
+    SUSPENDED,
+    RUNNING
+} current_state = IDLE;
 static int current_mode;
 static VALUE me2counter = Qnil;
 
 /*
  * call-seq:
- *    Coverage.start  => nil
+ *    Coverage.setup                                              => nil
+ *    Coverage.setup(:all)                                        => nil
+ *    Coverage.setup(lines: bool, branches: bool, methods: bool)  => nil
+ *    Coverage.setup(oneshot_lines: true)                         => nil
  *
- * Enables coverage measurement.
+ * Set up the coverage measurement.
+ *
+ * Note that this method does not start the measurement itself.
+ * Use Coverage.resume to start the measurement.
+ *
+ * You may want to use Coverage.start to setup and then start the measurement.
  */
 static VALUE
-rb_coverage_start(int argc, VALUE *argv, VALUE klass)
+rb_coverage_setup(int argc, VALUE *argv, VALUE klass)
 {
     VALUE coverages, opt;
     int mode;
 
+    if (current_state != IDLE) {
+	rb_raise(rb_eRuntimeError, "coverage measurement is already setup");
+    }
+
     rb_scan_args(argc, argv, "01", &opt);
 
     if (argc == 0) {
@@ -70,10 +87,57 @@ rb_coverage_start(int argc, VALUE *argv, VALUE klass) https://github.com/ruby/ruby/blob/trunk/ext/coverage/coverage.c#L87
 	current_mode = mode;
 	if (mode == 0) mode = COVERAGE_TARGET_LINES;
 	rb_set_coverages(coverages, mode, me2counter);
+        current_state = SUSPENDED;
     }
     else if (current_mode != mode) {
 	rb_raise(rb_eRuntimeError, "cannot change the measuring target during coverage measurement");
     }
+
+
+    return Qnil;
+}
+
+/*
+ * call-seq:
+ *    Coverage.resume  => nil
+ *
+ * Start/resume the coverage measurement.
+ *
+ * Caveat: Currently, only process-global coverage measurement is supported.
+ * You cannot measure per-thread covearge. If your process has multiple thread,
+ * using Coverage.resume/suspend to capture code coverage executed from only
+ * a limited code block, may yield misleading results.
+ */
+VALUE
+rb_coverage_resume(VALUE klass)
+{
+    if (current_state == IDLE) {
+	rb_raise(rb_eRuntimeError, "coverage measurement is not set up yet");
+    }
+    if (current_state == RUNNING) {
+	rb_raise(rb_eRuntimeError, "coverage measurement is already running");
+    }
+    rb_resume_coverages();
+    current_state = RUNNING;
+    return Qnil;
+}
+
+/*
+ * call-seq:
+ *    Coverage.start                                              => nil
+ *    Coverage.start(:all)                                        => nil
+ *    Coverage.start(lines: bool, branches: bool, methods: bool)  => nil
+ *    Coverage.start(oneshot_lines: true)                         => nil
+ *
+ * Enables the coverage measurement.
+ * See the documentation of Coverage class in detail.
+ * This is equivalent to Coverage.setup and Coverage.resume.
+ */
+static VALUE
+rb_coverage_start(int argc, VALUE *argv, VALUE klass)
+{
+    rb_coverage_setup(argc, argv, klass);
+    rb_coverage_resume(klass);
     return Qnil;
 }
 
@@ -279,6 +343,24 @@ clear_me2counter_i(VALUE key, VALUE value, VALUE unused) https://github.com/ruby/ruby/blob/trunk/ext/coverage/coverage.c#L343
     return ST_CONTINUE;
 }
 
+/*
+ * call-seq:
+ *    Coverage.suspend  => nil
+ *
+ * Suspend the coverage measurement.
+ * You can use Coverage.resumt to restart the measurement.
+ */
+VALUE
+rb_coverage_suspend(VALUE klass)
+{
+    if (current_state != RUNNING) {
+	rb_raise(rb_eRuntimeError, "coverage measurement is not running");
+    }
+    rb_suspend_coverages();
+    current_state = SUSPENDED;
+    return Qnil;
+}
+
 /*
  *  call-seq:
  *     Coverage.result(stop: true, clear: true)  => hash
@@ -294,6 +376,10 @@ rb_coverage_result(int argc, VALUE *argv, VALUE klass) https://github.com/ruby/ruby/blob/trunk/ext/coverage/coverage.c#L376
     VALUE opt;
     int stop = 1, clear = 1;
 
+    if (current_state == IDLE) {
+	rb_raise(rb_eRuntimeError, "coverage measurement is not enabled");
+    }
+
     rb_scan_args(argc, argv, "01", &opt);
 
     if (argc == 1) {
@@ -312,13 +398,34 @@ rb_coverage_result(int argc, VALUE *argv, VALUE klass) https://github.com/ruby/ruby/blob/trunk/ext/coverage/coverage.c#L398
         if (!NIL_P(me2counter)) rb_hash_foreach(me2counter, clear_me2counter_i, Qnil);
     }
     if (stop) {
+        if (current_state == RUNNING) {
+            rb_coverage_suspend(klass);
+        }
         rb_reset_coverages();
         me2counter = Qnil;
+        current_state = IDLE;
     }
     return ncoverages;
 }
 
 
+/*
+ *  call-seq:
+ *     Coverage.state  => :idle, :suspended, :running
+ *
+ * Returns the state of the coverage measurement.
+ */
+static VALUE
+rb_coverage_state(VALUE klass)
+{
+    switch (current_state) {
+        case IDLE: return ID2SYM(rb_intern("idle"));
+        case SUSPENDED: return ID2SYM(rb_intern("suspended"));
+        case RUNNING: return ID2SYM(rb_intern("running"));
+    }
+    return Qnil;
+}
+
 /*
  *  call-seq:
  *     Coverage.running?  => bool
@@ -329,13 +436,15 @@ rb_coverage_result(int argc, VALUE *argv, VALUE klass) https://github.com/ruby/ruby/blob/trunk/ext/coverage/coverage.c#L436
 static VALUE
 rb_coverage_running(VALUE klass)
 {
-    VALUE coverages = rb_get_coverages();
-    return RTEST(coverages) ? Qtrue : Qfalse;
+    return current_state == RUNNING ? Qtrue : Qfalse;
 }
 
 /* Coverage provides coverage measurement feature for Ruby.
  * This feature is experimental, so these APIs may be changed in future.
  *
+ * Caveat: Currently, only process-global coverage measurement is supported.
+ * You cannot measure per-thread covearge.
+ *
  * = Usage
  *
  * 1. require "coverage"
@@ -480,9 +589,13 @@ void https://github.com/ruby/ruby/blob/trunk/ext/coverage/coverage.c#L589
 Init_coverage(void)
 {
     VALUE rb_mCoverage = rb_define_module("Coverage");
+    rb_define_module_function(rb_mCoverage, "setup", rb_coverage_setup, -1);
     rb_define_module_function(rb_mCoverage, "start", rb_coverage_start, -1);
+    rb_define_module_function(rb_mCoverage, "resume", rb_coverage_resume, 0);
+    rb_define_module_function(rb_mCoverage, "suspend", rb_coverage_suspend, 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, "state", rb_coverage_state, 0);
     rb_define_module_function(rb_mCoverage, "running?", rb_coverage_running, 0);
     rb_global_variable(&me2counter);
 }
diff --git a/spec/ruby/library/coverage/result_spec.rb b/spec/ruby/library/coverage/result_spec.rb
index 6cf5be13469..4cc43e8462a 100644
--- a/spec/ruby/library/coverage/result_spec.rb
+++ b/spec/ruby/library/coverage/result_spec.rb
@@ -65,12 +65,25 @@ describe 'Coverage.result' do https://github.com/ruby/ruby/blob/trunk/spec/ruby/library/coverage/result_spec.rb#L65
     result.should == {}
   end
 
-  it 'second Coverage.start does nothing' do
-    Coverage.start
-    require @config_file.chomp('.rb')
-    result = Coverage.result
+  ruby_version_is ''...'3.1' do
+    it 'second Coverage.start does nothing' do
+      Coverage.start
+      require @config_file.chomp('.rb')
+      result = Coverage.result
+
+      result.should == { @config_file => [1, 1, 1] }
+    end
+  end
 
-    result.should == { @config_file => [1, 1, 1] }
+  ruby_version_is '3.1' do
+    it 'second Coverage.start give exception' do
+      Coverage.start
+      -> {
+        require @config_file.chomp('.rb')
+      }.should raise_error(RuntimeError, 'coverage measurement is already setup')
+    ensure
+      Coverage.result
+    end
   end
 
   it 'does not include the file starting coverage since it is not tracked' do
diff --git a/test/coverage/test_coverage.rb b/test/coverage/test_coverage.rb
index 22557bd9d8b..882368363a8 100644
--- a/test/coverage/test_coverage.rb
+++ b/test/coverage/test_coverage.rb
@@ -774,4 +774,150 @@ class TestCoverage < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/coverage/test_coverage.rb#L774
       end
     end;
   end
+
+  def test_coverage_suspendable
+    Dir.mktmpdir {|tmp|
+      Dir.chdir(tmp) {
+        File.open("test.rb", "w") do |f|
+          f.puts <<-EOS
+            def foo
+              :ok
+            end
+
+            def bar
+              :ok
+            end
+
+            def baz
+              :ok
+            end
+          EOS
+        end
+
+        cov1 = "[0, 0, nil, nil, 0, 1, nil, nil, 0, 0, nil]"
+        cov2 = "[0, 0, nil, nil, 0, 1, nil, nil, 0, 1, nil]"
+        assert_in_out_err(%w[-rcoverage], <<-"end;", [cov1, cov2], [])
+          Coverage.setup
+          tmp = Dir.pwd
+          require tmp + "/test.rb"
+          foo
+          Coverage.resume
+          bar
+          Coverage.suspend
+          baz
+          p Coverage.peek_result[tmp + "/test.rb"]
+          Coverage.resume
+          baz
+          p Coverage.result[tmp + "/test.rb"]
+        end;
+
+        cov1 =  (... truncated)

--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]