模組 Coverage
Coverage
提供 Ruby 的涵蓋率量測功能。此功能為實驗性質,因此這些 API 可能會在未來變更。
注意事項:目前僅支援程序全域涵蓋率量測。您無法量測每個執行緒的涵蓋率。
用法¶ ↑
-
require “coverage”
-
require 或載入 Ruby 原始檔
-
Coverage.result
會傳回一個雜湊,其中包含檔案名稱作為鍵,以及涵蓋率陣列作為值。涵蓋率陣列會針對每一行提供由直譯器執行的行數。nil
值表示此行已停用涵蓋率(例如else
和end
等行)。
範例¶ ↑
[foo.rb] s = 0 10.times do |x| s += x end if s == 45 p :ok else p :ng end [EOF] require "coverage" Coverage.start require "foo.rb" p Coverage.result #=> {"foo.rb"=>[1, 1, 10, nil, nil, 1, 1, nil, 0, nil]}
程式碼行 Coverage
¶ ↑
如果在開始涵蓋率時未明確指定涵蓋率模式,則會執行程式碼行涵蓋率。它會報告每一行的執行次數。
require "coverage" Coverage.start(lines: true) require "foo.rb" p Coverage.result #=> {"foo.rb"=>{:lines=>[1, 1, 10, nil, nil, 1, 1, nil, 0, nil]}}
程式碼行涵蓋率結果的值是一個陣列,其中包含每一行執行的次數。此陣列中的順序很重要。例如,此陣列中的第一個項目(索引為 0)會報告在執行涵蓋率期間,此檔案中的第 1 行執行了幾次(在此範例中為一次)。
nil
值表示此行已停用涵蓋率(例如 else
和 end
等行)。
一次性程式碼行 Coverage
¶ ↑
單次執行行數覆蓋率在覆蓋率執行期間追蹤並回報已執行的行數。它不會回報一行已執行多少次,只會回報它已執行。
require "coverage" Coverage.start(oneshot_lines: true) require "foo.rb" p Coverage.result #=> {"foo.rb"=>{:oneshot_lines=>[1, 2, 3, 6, 7]}}
單次執行行數覆蓋率結果的值是一個包含已執行行號的陣列。
分支 Coverage
¶ ↑
分支覆蓋率回報每個條件式中每個分支已執行的次數。
require "coverage" Coverage.start(branches: true) require "foo.rb" p Coverage.result #=> {"foo.rb"=>{:branches=>{[:if, 0, 6, 0, 10, 3]=>{[:then, 1, 7, 2, 7, 7]=>1, [:else, 2, 9, 2, 9, 7]=>0}}}}
分支雜湊中的每個項目都是一個條件式,其值是另一個雜湊,其中每個項目都是該條件式中的分支。這些值是方法已執行的次數,而金鑰則是關於分支的識別資訊。
組成識別分支或條件式的每個金鑰的資訊如下,由左至右
-
分支或條件式的類型標籤。
-
一個唯一識別碼。
-
它出現在檔案中的起始行號。
-
它出現在檔案中的起始欄號。
-
它出現在檔案中的結束行號。
-
它出現在檔案中的結束欄號。
方法 Coverage
¶ ↑
方法覆蓋率回報每個方法已執行的次數。
[foo_method.rb] class Greeter def greet "welcome!" end end def hello "Hi" end hello() Greeter.new.greet() [EOF] require "coverage" Coverage.start(methods: true) require "foo_method.rb" p Coverage.result #=> {"foo_method.rb"=>{:methods=>{[Object, :hello, 7, 0, 9, 3]=>1, [Greeter, :greet, 2, 2, 4, 5]=>1}}}
方法雜湊中的每個項目代表一個方法。此雜湊中的值是方法已執行的次數,而金鑰則是關於方法的識別資訊。
組成識別方法的每個金鑰的資訊如下,由左至右
-
類別。
-
方法名稱。
-
方法出現在檔案中的起始行號。
-
方法出現在檔案中的起始欄號。
-
方法出現在檔案中的結束行號。
-
方法出現在檔案中的結束欄號。
所有 Coverage
模式¶ ↑
您也可以使用此捷徑同時執行所有涵蓋模式。請注意,執行所有涵蓋模式並不會同時執行行和一次性行。這些模式無法同時執行。在此情況下,會執行行涵蓋,因為您仍可以使用它來確定是否執行某一行。
require "coverage" Coverage.start(:all) require "foo.rb" p Coverage.result #=> {"foo.rb"=>{:lines=>[1, 1, 10, nil, nil, 1, 1, nil, 0, nil], :branches=>{[:if, 0, 6, 0, 10, 3]=>{[:then, 1, 7, 2, 7, 7]=>1, [:else, 2, 9, 2, 9, 7]=>0}}, :methods=>{}}}
公用類別方法
# File ext/coverage/lib/coverage.rb, line 4 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, type| lines[n - 1] = 0 if type == :line } iseq.each_child {|child| iseqs << child } end lines end
傳回一個雜湊,其中包含檔案名稱作為鍵和涵蓋陣列作為值。這與「Coverage.result(stop: false, clear: false)」相同。
{ "file.rb" => [1, 2, nil], ... }
static VALUE rb_coverage_peek_result(VALUE klass) { VALUE coverages = rb_get_coverages(); VALUE ncoverages = rb_hash_new(); if (!RTEST(coverages)) { rb_raise(rb_eRuntimeError, "coverage measurement is not enabled"); } OBJ_WB_UNPROTECT(coverages); st_foreach(RHASH_TBL_RAW(coverages), coverage_peek_result_i, ncoverages); if (current_mode & COVERAGE_TARGET_METHODS) { rb_objspace_each_objects(method_coverage_i, &ncoverages); } rb_hash_freeze(ncoverages); return ncoverages; }
傳回一個雜湊,其中包含檔案名稱作為鍵和涵蓋陣列作為值。如果 clear
為 true,它會將計數器清除為零。如果 stop
為 true,它會停用涵蓋量測。
static VALUE rb_coverage_result(int argc, VALUE *argv, VALUE klass) { VALUE ncoverages; 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) { 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) { if (current_state == RUNNING) { rb_coverage_suspend(klass); } rb_reset_coverages(); me2counter = Qnil; current_state = IDLE; } return ncoverages; }
開始/繼續涵蓋量測。
注意事項:目前僅支援處理序全域涵蓋量測。您無法量測每個執行緒的涵蓋。如果您的處理序有多個執行緒,使用 Coverage.resume
/suspend 來擷取僅從有限程式碼區塊執行的程式碼涵蓋,可能會產生誤導性結果。
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; }
如果目前正在收集涵蓋統計資料(在 Coverage.start
呼叫之後,但在 Coverage.result
呼叫之前),則傳回 true
static VALUE rb_coverage_running(VALUE klass) { return current_state == RUNNING ? Qtrue : Qfalse; }
Set
設定涵蓋量測。
請注意,此方法不會啟動量測本身。使用 Coverage.resume
來啟動量測。
您可能想使用 Coverage.start
來設定,然後啟動量測。
static VALUE 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) { mode = 0; /* compatible mode */ } else if (opt == ID2SYM(rb_intern("all"))) { mode = COVERAGE_TARGET_LINES | COVERAGE_TARGET_BRANCHES | COVERAGE_TARGET_METHODS | COVERAGE_TARGET_EVAL; } else { mode = 0; opt = rb_convert_type(opt, T_HASH, "Hash", "to_hash"); if (RTEST(rb_hash_lookup(opt, ID2SYM(rb_intern("lines"))))) mode |= COVERAGE_TARGET_LINES; if (RTEST(rb_hash_lookup(opt, ID2SYM(rb_intern("branches"))))) mode |= COVERAGE_TARGET_BRANCHES; if (RTEST(rb_hash_lookup(opt, ID2SYM(rb_intern("methods"))))) mode |= COVERAGE_TARGET_METHODS; 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 (RTEST(rb_hash_lookup(opt, ID2SYM(rb_intern("eval"))))) mode |= COVERAGE_TARGET_EVAL; } if (mode & COVERAGE_TARGET_METHODS) { me2counter = rb_ident_hash_new(); } else { me2counter = Qnil; } coverages = rb_get_coverages(); if (!RTEST(coverages)) { coverages = rb_hash_new(); rb_obj_hide(coverages); 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; }
啟用覆蓋率量測。詳見 Coverage
類別的文件。這等同於 Coverage.setup
和 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; }
傳回覆蓋率量測的狀態。
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; }
如果給定模式支援覆蓋率量測,則傳回 true。
模式應為下列符號之一::lines
、:oneshot_lines
、:branches
、:methods
、:eval
。
範例
Coverage.supported?(:lines) #=> true Coverage.supported?(:all) #=> false
static VALUE rb_coverage_supported(VALUE self, VALUE _mode) { ID mode = RB_SYM2ID(_mode); return RBOOL( mode == rb_intern("lines") || mode == rb_intern("oneshot_lines") || mode == rb_intern("branches") || mode == rb_intern("methods") || mode == rb_intern("eval") ); }
暫停覆蓋率量測。您可以使用 Coverage.resume
重新開始量測。
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; }