模块 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
源代码
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);
rb_hash_foreach(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;
}
返回一个哈希,其中包含文件名作为键,覆盖率数组作为值。这与 `Coverage.result(stop: false, clear: false)` 相同。
{
"file.rb" => [1, 2, nil],
...
}
源代码
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;
}
返回一个哈希,其中包含文件名作为键,覆盖率数组作为值。如果 clear 为 true,则会将计数器清零。如果 stop 为 true,则会禁用覆盖率测量。
源代码
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.resume/suspend 来捕获仅从有限代码块执行的代码覆盖率,可能会产生误导性的结果。
源代码
static VALUE
rb_coverage_running(VALUE klass)
{
return current_state == RUNNING ? Qtrue : Qfalse;
}
如果当前正在收集覆盖率统计信息(在调用 Coverage.start 之后,但在调用 Coverage.result 之前),则返回 true
源代码
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.resume 启动测量。
您可能希望使用 Coverage.start 来设置,然后启动测量。
源代码
static VALUE
rb_coverage_start(int argc, VALUE *argv, VALUE klass)
{
rb_coverage_setup(argc, argv, klass);
rb_coverage_resume(klass);
return Qnil;
}
启用覆盖率测量。有关详细信息,请参阅 Coverage 类的文档。这等效于 Coverage.setup 和 Coverage.resume。
源代码
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;
}
返回覆盖率测量的状态。
源代码
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")
);
}
如果给定的模式支持覆盖率测量,则返回 true。
该模式应为以下符号之一::lines、:oneshot_lines、:branches、:methods、:eval。
示例
Coverage.supported?(:lines) #=> true Coverage.supported?(:all) #=> false
源代码
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;
}
暂停覆盖率测量。您可以使用 Coverage.resume 重新启动测量。