class Covered::Coverage
Stores line execution counts and source metadata for a single file.
def self.deserialize(unpacker)
@parameter unpacker [Object] The MessagePack-compatible unpacker.
Deserialize a coverage object from the given unpacker.
def self.deserialize(unpacker) source = unpacker.read counts = unpacker.read annotations = unpacker.read self.new(source, counts, annotations) end
def self.for(path, **options)
@parameter options [Hash] Options forwarded to {Covered::Source.for}.
@parameter path [String] The source path.
Build coverage for the given source path.
def self.for(path, **options) self.new(Source.for(path, **options)) end
def [] line_number
@parameter line_number [Integer] The line number to query.
The raw coverage count for the given line number.
def [] line_number @counts[line_number] end
def annotate(line_number, annotation)
@parameter line_number [Integer] The line number to annotate.
Add an annotation to the given line number.
def annotate(line_number, annotation) @annotations[line_number] ||= [] @annotations[line_number] << annotation end
def as_json
A JSON-compatible representation of this coverage object.
def as_json { counts: counts, executable_count: executable_count, executed_count: executed_count, percentage: percentage.to_f.round(2), } end
def empty
Create an empty coverage with the same source.
def empty self.class.new(@source, [nil] * @counts.size) end
def executable_count
The number of executable lines.
def executable_count executable_lines.count end
def executable_lines
Counts for lines that are executable.
def executable_lines @counts.compact end
def executed_count
The number of executable lines that were executed.
def executed_count executed_lines.count end
def executed_lines
Counts for executable lines that were executed.
def executed_lines executable_lines.reject(&:zero?) end
def for_lines(line_numbers)
@parameter line_numbers [Array(Integer)] The line numbers to include in the new coverage object.
Construct a new coverage object for the given line numbers. Only the given line numbers will be considered for the purposes of computing coverage.
def for_lines(line_numbers) counts = [nil] * @counts.size line_numbers.each do |line_number| counts[line_number] = @counts[line_number] end self.class.new(@source, counts, @annotations) end
def freeze
Freeze this coverage and its mutable collections.
def freeze return self if frozen? @counts.freeze @annotations.freeze super end
def fresh?
Whether the source file has not changed since this coverage was recorded.
def fresh? if @source.modified_time.nil? # We don't know when the file was last modified, so we assume it is stale: return false end unless File.exist?(@source.path) # The file no longer exists, so we assume it is stale: return false end if @source.modified_time >= File.mtime(@source.path) # The file has not been modified since we last processed it, so we assume it is fresh: return true end return false end
def initialize(source, counts = [], annotations = {})
@parameter counts [Array(Integer | Nil)] Line execution counts indexed by line number.
@parameter source [Covered::Source] The covered source metadata.
Initialize coverage with the given source, line counts and annotations.
def initialize(source, counts = [], annotations = {}) @source = source @counts = counts @annotations = annotations end
def mark(line_number, value = 1)
@parameter line_number [Integer] The first line number to mark.
Add the given execution count to one or more line numbers.
def mark(line_number, value = 1) # As currently implemented, @counts is base-zero rather than base-one. # Line numbers generally start at line 1, so the first line, line 1, is at index 1. This means that index[0] is usually nil. Array(value).each_with_index do |value, index| offset = line_number + index if @counts[offset] @counts[offset] += value else @counts[offset] = value end end end
def merge!(other)
Merge another coverage object into this coverage object.
def merge!(other) # If the counts are non-zero and don't match, that can indicate a problem. other.counts.each_with_index do |count, index| if count @counts[index] ||= 0 @counts[index] += count end end @annotations.merge!(other.annotations) do |line_number, a, b| Array(a) + Array(b) end end
def missing_count
The number of executable lines that were not executed.
def missing_count executable_count - executed_count end
def path
The covered source path.
def path @source.path end
def path= value
Assign the covered source path.
def path= value @source.path = value end
def print(output)
Print a human-readable coverage summary.
def print(output) output.puts "** #{executed_count}/#{executable_count} lines executed; #{percentage.to_f.round(2)}% covered." end
def read(&block)
@parameter file [File] The open source file.
@yields {|file| ...} If a block is given, yields an open source file.
Read the covered source.
def read(&block) @source.read(&block) end
def serialize(packer)
Serialize this coverage object with the given packer.
def serialize(packer) packer.write(@source) packer.write(@counts) packer.write(@annotations) end
def to_a
The raw coverage counts array.
def to_a @counts end
def to_s
A human-readable representation of this coverage object.
def to_s "\#<#{self.class} path=#{self.path} #{self.percentage.to_f.round(2)}% covered>" end
def total
The total number of executions across all tracked lines.
def total counts.sum{|count| count || 0} end
def zero?
Whether this coverage has no executions.
def zero? total.zero? end