module Coverage - Documentation for Ruby 4.1
Description: module Coverage: Coverage provides coverage measurement feature for Ruby. Only process-global coverage measurement is supported, meaning that coverage cannot be measur
Keywords: ruby,module,Coverage

🔍

No recent searches

# module Coverage

Coverage provides coverage measurement feature for Ruby.

Only process-global coverage measurement is supported, meaning that coverage cannot be measure on a per-thread basis.

# Quick Start

1. Load coverage using `require "coverage"`.

2. Call `Coverage.start` to set up and begin coverage measurement.

3. All Ruby code loaded following the call to `Coverage.start` will have coverage measurement.

4. `Coverage` results can be fetched by calling `Coverage.result`, which returns a hash that contains filenames as the keys and coverage arrays as the values. Each element of the coverage array gives the number of times each line was executed. A `nil` value means coverage was disabled for that line (e.g. lines like `else` and `end`).

# Examples

In file `fib.rb`:

    def fibonacci(n)
      if n == 0
        0
      elsif n == 1
        1
      else
        fibonacci(n - 1) + fibonacci(n - 2)
      end
    end

    puts fibonacci(10)

In another file, coverage can be measured:

    require "coverage"
    Coverage.start
    require "fib.rb"
    Coverage.result # => {"fib.rb" => [1, 177, 34, 143, 55, nil, 88, nil, nil, nil, 1]}

## Lines Coverage

Lines coverage reports the number of line executions for each line. If the coverage mode is not explicitly specified when starting coverage, lines coverage is used as the default.

    require "coverage"
    Coverage.start(lines: true)
    require "fib"
    Coverage.result # => {"fib.rb" => {lines: [1, 177, 34, 143, 55, nil, 88, nil, nil, nil, 1]}}

The returned hash differs depending on how `Coverage.setup` or `Coverage.start` was executed.

If `Coverage.start` or `Coverage.setup` was called with no arguments, it returns a hash which contains filenames as the keys and coverage arrays as the values.

If `Coverage.start` or `Coverage.setup` was called with `line: true`, it returns a hash which contains filenames as the keys and hashes as the values. The value hash has a key `:lines` where the value is a coverage array.

Each element of the coverage array gives the number of times the line was executed. A `nil` value in the coverage array means coverage was disabled for that line (e.g. lines like `else` and `end`).

## Oneshot Lines Coverage

Oneshot lines coverage is similar to lines coverage, but instead of reporting the number of times a line was executed, it only reports the lines that were executed.

    require "coverage"
    Coverage.start(oneshot_lines: true)
    require "fib"
    Coverage.result # => {"fib.rb" => {oneshot_lines: [1, 11, 2, 4, 7, 5, 3]}}

The value of the oneshot lines coverage result is an array containing the line numbers that were executed.

## Branches Coverage

Branches coverage reports the number of times each branch within each conditional was executed.

    require "coverage"
    Coverage.start(branches: true)
    require "fib"
    Coverage.result
    # => {"fib.rb" => {
    #      branches: {
    #        [:if, 0, 2, 2, 8, 5] => {
    #          [:then, 1, 3, 4, 3, 5] => 34,
    #          [:else, 2, 4, 2, 8, 5] => 143},
    #        [:if, 3, 4, 2, 8, 5] => {
    #          [:then, 4, 5, 4, 5, 5] => 55,
    #          [:else, 5, 7, 4, 7, 39] => 88}}}}

Each entry within the branches hash is a conditional, the value of which is another hash where each entry is a branch in that conditional. The keys are arrays containing information about the branch and the values are the number of times the branch was executed.

The information that makes up the array that are the keys for conditional or branches are the following, from left to right:

1. A label for the type of branch or conditional (e.g. `:if`, `:then`, `:else`).

2. A unique identifier.

3. Starting line number.

4. Starting column number.

5. Ending line number.

6. Ending column number.

## Methods Coverage

Methods coverage reports how many times each method was executed.

    require "coverage"
    Coverage.start(methods: true)
    require "fib"
    p Coverage.result #=> {"fib.rb" => {methods: {[Object, :fibonacci, 1, 0, 9, 3] => 177}}}

Each entry within the methods hash represents a method. The keys are arrays containing hash are the number of times the method was executed, and the keys are identifying information about the method.

The information that makes up each key identifying a method is the following, from left to right:

1. Class that the method was defined in.

2. Method name as a `Symbol`.

3. Starting line number of the method.

4. Starting column number of the method.

5. Ending line number of the method.

6. Ending column number of the method.

## Eval Coverage

Eval coverage can be combined with the coverage types above to track coverage for eval.

    require "coverage"
    Coverage.start(eval: true, lines: true)

    eval(<<~RUBY, nil, "eval 1")
      ary = []
      10.times do |i|
        ary << "hello" * i
      end
    RUBY

    Coverage.result # => {"eval 1" => {lines: [1, 1, 10, nil]}}

Note that the eval must have a filename assigned, otherwise coverage will not be measured.

    require "coverage"
    Coverage.start(eval: true, lines: true)

    eval(<<~RUBY)
      ary = []
      10.times do |i|
        ary << "hello" * i
      end
    RUBY

    Coverage.result # => {"(eval)" => {lines: [nil, nil, nil, nil]}}

Also note that if a line number is assigned to the eval and it is not 1, then the resulting coverage will be padded with `nil` if the line number is greater than 1, and truncated if the line number is less than 1.

     require "coverage"
     Coverage.start(eval: true, lines: true)

     eval(<<~RUBY, nil, "eval 1", 3)
       ary = []
       10.times do |i|
         ary << "hello" * i
       end
     RUBY

    eval(<<~RUBY, nil, "eval 2", -1)
       ary = []
       10.times do |i|
         ary << "hello" * i
       end
     RUBY

     Coverage.result
     # => {"eval 1" => {lines: [nil, nil, 1, 1, 10, nil]}, "eval 2" => {lines: [10, nil]}}

## All Coverage Modes

All modes of coverage can be enabled simultaneously using the `Symbol` `:all`. However, note that this mode runs lines coverage and not oneshot lines since they cannot be ran simultaneously.

    require "coverage"
    Coverage.start(:all)
    require "fib"
    Coverage.result
    # => {"fib.rb" => {
    #      lines: [1, 177, 34, 143, 55, nil, 88, nil, nil, nil, 1],
    #      branches: {
    #        [:if, 0, 2, 2, 8, 5] => {
    #          [:then, 1, 3, 4, 3, 5] => 34,
    #          [:else, 2, 4, 2, 8, 5] => 143},
    #        [:if, 3, 4, 2, 8, 5] => {
    #          [:then, 4, 5, 4, 5, 5] => 55,
    #          [:else, 5, 7, 4, 7, 39] => 88}}}},
    #      methods: {[Object, :fibonacci, 1, 0, 9, 3] => 177}}}

### Public Class Methods

line_stub(file) → array
Source

    # File ext/coverage/lib/coverage.rb, line 9
    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

A simple helper function that creates the “stub” of line coverage from a given source code.

peek_result → hash
Source

    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");
        }

        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;
    }

Returns a hash that contains filename as key and coverage array as value. This is the same as `Coverage.result(stop: false, clear: false)`.

    {
      "file.rb" => [1, 2, nil],
      ...
    }

result(stop: true, clear: true) → hash
Source

    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;
    }

Returns a hash that contains filename as key and coverage array as value. If `clear` is true, it clears the counters to zero. If `stop` is true, it disables coverage measurement.

resume → nil
Source

    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;
    }

Start/resume the coverage measurement.

Caveat: Currently, only process-global coverage measurement is supported. You cannot measure per-thread coverage. If your process has multiple thread, using `Coverage.resume`/suspend to capture code coverage executed from only a limited code block, may yield misleading results.

running? → bool
Source

    static VALUE
    rb_coverage_running(VALUE klass)
    {
        return current_state == RUNNING ? Qtrue : Qfalse;
    }

Returns true if coverage stats are currently being collected (after `Coverage.start` call, but before `Coverage.result` call)

setup → nil
setup(type) → nil
setup(lines: false, branches: false, methods: false, eval: false, oneshot_lines: false) → nil
Source

    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;
    }

Performs setup for coverage measurement, but does not start coverage measurement. To start coverage measurement, use `Coverage.resume`.

To perform both setup and start coverage measurement, `Coverage.start` can be used.

With argument `type` given and `type` is symbol `:all`, enables all types of coverage (lines, branches, methods, and eval).

Keyword arguments or hash `type` can be given with each of the following keys:

- `lines`: Enables line coverage that records the number of times each line was executed. If `lines` is enabled, `oneshot_lines` cannot be enabled. See Lines Coverage.

- `branches`: Enables branch coverage that records the number of times each branch in each conditional was executed. See Branches Coverage.

- `methods`: Enables method coverage that records the number of times each method was exectued. See Methods Coverage.

- `eval`: Enables coverage for evaluations (e.g. `Kernel#eval`, `Module#class_eval`). See Eval Coverage.

start → nil
start(type) → nil
start(lines: false, branches: false, methods: false, eval: false, oneshot_lines: false) → nil
Source

    static VALUE
    rb_coverage_start(int argc, VALUE *argv, VALUE klass)
    {
        rb_coverage_setup(argc, argv, klass);
        rb_coverage_resume(klass);
        return Qnil;
    }

Enables coverage measurement. This method is equivalent to calling `Coverage.setup` with the arguments provided, and then calling `Coverage.resume`. See their respective documentation for more details.

state → :idle, :suspended, :running
Source

    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;
    }

Returns the state of the coverage measurement.

supported?(mode) → true or false
Source

    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")
        );
    }

Returns true if coverage measurement is supported for the given mode.

The mode should be one of the following symbols: `:lines`, `:oneshot_lines`, `:branches`, `:methods`, `:eval`.

Example:

    Coverage.supported?(:lines)  #=> true
    Coverage.supported?(:all)    #=> false

suspend → nil
Source

    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;
    }

Suspend the coverage measurement. You can use `Coverage.resume` to restart the measurement.