Spec-Zone.ru › Ruby 3.4

модуль Coverage

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

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

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

  1. require “coverage”

  2. do 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).

Однократное выполнение строк 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}}}}

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

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

  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 режимы

Вы также можете запустить все режимы покрытия одновременно с помощью этого сокращения. Обратите внимание, что запуск всех режимов покрытия не запускает и линии, и однократные линии. Эти режимы нельзя запустить одновременно. В этом случае запускается измерение покрытия по строкам, потому что его можно использовать, чтобы определить, была ли строка выполнена или нет.

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–2024 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.

Spec-Zone.ru

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