Spec-Zone.ru › Ruby 3.3

модуль Coverage

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

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

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

  1. require “coverage”

  2. do Coverage.start

  3. require или загрузить файл исходного кода 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);
    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;
}

Возвращает хеш, содержащий имя файла в качестве ключа и массив покрытия в качестве значения. Это то же самое, что ‘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–2022 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.

Spec-Zone.ru

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