Spec-Zone.ru › Ruby 3.4

модуль Coverage

Coverage предоставляет функцию измерения покрытия кода для Ruby. Эта функция является экспериментальной, поэтому в будущем API могут измениться.

Предупреждение: в настоящее время поддерживается только измерение покрытия на уровне всего процесса. Измерять покрытие для отдельных потоков нельзя.

Использование

  1. require “coverage”

  2. вызовите Coverage.start

  3. подключите или загрузите исходный файл Ruby

  4. 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}}}}

Каждая запись в хеше ветвей соответствует условному выражению; её значением является другой хеш, каждая запись которого соответствует ветви этого условного выражения. Значения — это количество выполнений метода, а ключи содержат идентификационную информацию о ветви.

Информация, составляющая каждый ключ, идентифицирующий ветвь или условное выражение, перечислена ниже слева направо:

  1. Метка типа ветви или условного выражения.

  2. Уникальный идентификатор.

  3. Номер начальной строки, на которой элемент находится в файле.

  4. Номер начального столбца, в котором элемент находится в файле.

  5. Номер конечной строки, на которой элемент находится в файле.

  6. Номер конечного столбца, в котором элемент находится в файле.

Покрытие методов 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}}}

Каждая запись в хеше методов соответствует методу. Значения в этом хеше — это количество выполнений метода, а ключи содержат идентификационную информацию о методе.

Информация, составляющая каждый ключ, идентифицирующий метод, перечислена ниже слева направо:

  1. Класс.

  2. Имя метода.

  3. Номер начальной строки, на которой метод находится в файле.

  4. Номер начального столбца, в котором метод находится в файле.

  5. Номер конечной строки, на которой метод находится в файле.

  6. Номер конечного столбца, в котором метод находится в файле.

Все режимы 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=>{}}}

Публичные методы класса

line_stub (file) Показать исходный код
# 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
peek_result → hash Показать исходный код
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],
  ...
}
result(stop: true, clear: true) → hash Показать исходный код
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, измерение покрытия отключается.

resume → nil Показать исходный код
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 для сбора данных о покрытии кода только в ограниченном блоке кода может привести к недостоверным результатам.

running? → bool Показать исходный код
static VALUE
rb_coverage_running(VALUE klass)
{
    return current_state == RUNNING ? Qtrue : Qfalse;
}

Возвращает true, если в данный момент собирается статистика покрытия (после вызова Coverage.start, но до вызова Coverage.result).

setup → nil Показать исходный код
setup(:all) → nil
setup(lines: bool, branches: bool, methods: bool, eval: bool) → nil
setup(oneshot_lines: true) → nil
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.

start → nil Показать исходный код
start(:all) → nil
start(lines: bool, branches: bool, methods: bool, eval: bool) → nil
start(oneshot_lines: true) → nil
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.

state → :idle, :suspended, :running Показать исходный код
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;
}

Возвращает состояние измерения покрытия.

supported?(mode) → true or 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")
    );
}

Возвращает true, если измерение покрытия поддерживается для указанного режима.

Режимом должен быть один из следующих символов: :lines, :oneshot_lines, :branches, :methods, :eval.

Пример:

Coverage.supported?(:lines)  #=> true
Coverage.supported?(:all)    #=> false
suspend → nil Показать исходный код
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.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API