модуль Coverage
Coverage предоставляет функцию измерения покрытия кода для Ruby. Эта функция является экспериментальной, поэтому в будущем API могут измениться.
Предупреждение: в настоящее время поддерживается только измерение покрытия на уровне всего процесса. Измерять покрытие для отдельных потоков нельзя.
Использование
-
require “coverage”
-
вызовите
Coverage.start -
подключите или загрузите исходный файл 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).
Покрытие строк в режиме oneshot Coverage
Измерение покрытия строк в режиме oneshot отслеживает и сообщает о строках, выполненных во время измерения покрытия. Оно не сообщает, сколько раз была выполнена строка, а только указывает, что она была выполнена.
require "coverage"
Coverage.start(oneshot_lines: true)
require "foo.rb"
p Coverage.result #=> {"foo.rb"=>{:oneshot_lines=>[1, 2, 3, 6, 7]}}
Результат измерения покрытия строк в режиме oneshot представляет собой массив, содержащий номера выполненных строк.
Покрытие ветвей 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 измерения покрытия
С помощью этого сокращённого варианта можно также одновременно запустить все режимы измерения покрытия. Обратите внимание, что запуск всех режимов измерения покрытия не включает одновременно режимы покрытия строк и покрытия строк в режиме oneshot. Эти режимы нельзя запускать одновременно. В этом случае запускается покрытие строк, поскольку оно позволяет определить, была ли выполнена строка.
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;
} Возвращает true, если в данный момент собирается статистика покрытия (после вызова Coverage.start, но до вызова Coverage.result).
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.
Ruby Core © 1993–2025 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.