lib/covered/capture.rb
# frozen_string_literal: true # Released under the MIT License. # Copyright, 2018-2025, by Samuel Williams. require_relative "wrapper" require "ruby/coverage" module Covered # Captures Ruby coverage data and forwards it to another coverage output. class Capture < Wrapper # Initialize capture with independent tracer state. def initialize(...) super @tracer = nil @files = {} end # Start Ruby coverage collection. def start super @files = {} @tracer = build_tracer @tracer.start end # Clear any collected coverage data without stopping coverage. def clear super @tracer&.stop @files = {} @tracer = build_tracer @tracer.start end EVAL_PATHS = { "(eval)" => true, "(irb)" => true, "eval" => true } # Stop coverage collection and add the collected results to the output. # Ignores Ruby's anonymous eval paths and files that no longer exist. def finish @tracer&.stop @files.each do |path, lines| next if EVAL_PATHS.include?(path) path = self.expand_path(path) # Skip files which don't exist. This can happen if `eval` is used with an invalid/incorrect path: if File.exist?(path) @output.mark(path, 0, lines) else # warn "Skipping coverage for #{path.inspect} because it doesn't exist!" # Ignore. end end @tracer = nil @files = {} super end # Execute the given source while capturing coverage for it. # @parameter source [Covered::Source] The source to execute. # @parameter binding [Binding] The binding used to evaluate the source. # @returns [Object] The result of evaluating the source. def execute(source, binding: TOPLEVEL_BINDING) start eval(source.code!, binding, source.path, source.line_offset) ensure finish end private def build_tracer ::Ruby::Coverage::Tracer.new do |path, iseq| @files[path] ||= begin lines = [] ::Ruby::Coverage.executable_lines(iseq).each do |line| lines[line] = 0 end lines end end end end end