CoveredSourceCoveredCoverage

class Coverage

Stores line execution counts and source metadata for a single file.

Definitions

def self.for(path, **options)

Build coverage for the given source path.

Signature

parameter path String

The source path.

parameter options Hash

Options forwarded to Covered::Source.for.

returns Covered::Coverage

The coverage object for the source path.

Implementation

def self.for(path, **options)
	self.new(Source.for(path, **options))
end

def initialize(source, counts = [], annotations = {})

Initialize coverage with the given source, line counts and annotations.

Signature

parameter source Covered::Source

The covered source metadata.

parameter counts Array(Integer | Nil)

Line execution counts indexed by line number.

parameter annotations Hash(Integer, Array(String))

Line annotations indexed by line number.

Implementation

def initialize(source, counts = [], annotations = {})
	@source = source
	@counts = counts
	@annotations = annotations
end

attr_accessor :source

Signature

attribute Covered::Source

The covered source metadata.

attr :counts

Signature

attribute Array(Integer | Nil)

Line execution counts indexed by line number.

attr :annotations

Signature

attribute Hash(Integer, Array(String))

Line annotations indexed by line number.

def total

The total number of executions across all tracked lines.

Signature

returns Integer

The sum of all non-nil execution counts.

Implementation

def total
	counts.sum{|count| count || 0}
end

def empty

Create an empty coverage with the same source.

Signature

returns Covered::Coverage

Coverage with the same source and nil counts.

Implementation

def empty
	self.class.new(@source, [nil] * @counts.size)
end

def annotate(line_number, annotation)

Add an annotation to the given line number.

Signature

parameter line_number Integer

The line number to annotate.

parameter annotation String

The annotation text.

Implementation

def annotate(line_number, annotation)
	@annotations[line_number] ||= []
	@annotations[line_number] << annotation
end

def mark(line_number, value = 1)

Add the given execution count to one or more line numbers.

Signature

parameter line_number Integer

The first line number to mark.

parameter value Integer | Array(Integer)

The execution count or counts to add.

Implementation

def mark(line_number, value = 1)
	Array(value).each_with_index do |value, index|
		offset = line_number + index
		next if offset < 1
		
		if @counts[offset]
			@counts[offset] += value
		else
			@counts[offset] = value
		end
	end
end

def merge!(other)

Merge another coverage object into this coverage object.

Signature

parameter other Covered::Coverage

The coverage object to merge.

Implementation

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 for_lines(line_numbers)

Construct a new coverage object for the given line numbers. Only the given line numbers will be considered for the purposes of computing coverage.

Signature

parameter line_numbers Array(Integer)

The line numbers to include in the new coverage object.

returns Covered::Coverage

A coverage object containing counts for the selected lines.

Implementation

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 path

The covered source path.

Signature

returns String

The covered source path.

Implementation

def path
	@source.path
end

def path=(value)

Assign the covered source path.

Signature

parameter value String

The new covered source path.

Implementation

def path= value
	@source.path = value
end

def fresh?

Whether the source file has not changed since this coverage was recorded.

Signature

returns Boolean

Whether the source exists and its modification time is not newer than the recorded time.

Implementation

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 read(&block)

Read the covered source.

Signature

yields {|file| ...}

If a block is given, yields an open source file.

parameter file File

The open source file.

returns String | Object

The source contents without a block, or the block result with a block.

Implementation

def read(&block)
	@source.read(&block)
end

def freeze

Freeze this coverage and its mutable collections.

Signature

returns Covered::Coverage

This frozen coverage object.

Implementation

def freeze
	return self if frozen?
	
	@counts.freeze
	@annotations.freeze
	
	super
end

def to_a

The raw coverage counts array.

Signature

returns Array(Integer | Nil)

The raw coverage counts.

Implementation

def to_a
	@counts
end

def zero?

Whether this coverage has no executions.

Signature

returns Boolean

Whether the total execution count is zero.

Implementation

def zero?
	total.zero?
end

def [](line_number)

The raw coverage count for the given line number.

Signature

parameter line_number Integer

The line number to query.

returns Integer | Nil

The execution count for the line.

Implementation

def [] line_number
	@counts[line_number]
end

def executable_lines

Counts for lines that are executable.

Signature

returns Array(Integer)

Execution counts for executable lines.

Implementation

def executable_lines
	@counts.compact
end

def executable_count

The number of executable lines.

Signature

returns Integer

The number of executable lines.

Implementation

def executable_count
	executable_lines.count
end

def executed_lines

Counts for executable lines that were executed.

Signature

returns Array(Integer)

Non-zero execution counts for executable lines.

Implementation

def executed_lines
	executable_lines.reject(&:zero?)
end

def executed_count

The number of executable lines that were executed.

Signature

returns Integer

The number of executed lines.

Implementation

def executed_count
	executed_lines.count
end

def missing_count

The number of executable lines that were not executed.

Signature

returns Integer

The number of missing executable lines.

Implementation

def missing_count
	executable_count - executed_count
end

def print(output)

Print a human-readable coverage summary.

Signature

parameter output IO

The output stream.

Implementation

def print(output)
	output.puts "** #{executed_count}/#{executable_count} lines executed; #{percentage.to_f.round(2)}% covered."
end

def to_s

A human-readable representation of this coverage object.

Signature

returns String

A summary including the source path and percentage.

Implementation

def to_s
	"\#<#{self.class} path=#{self.path} #{self.percentage.to_f.round(2)}% covered>"
end

def as_json

A JSON-compatible representation of this coverage object.

Signature

returns Hash

The coverage counts and summary statistics.

Implementation

def as_json
	{
		counts: counts,
		executable_count: executable_count,
		executed_count: executed_count,
		percentage: percentage.to_f.round(2),
	}
end

def serialize(packer)

Serialize this coverage object with the given packer.

Signature

parameter packer Object

The MessagePack-compatible packer.

Implementation

def serialize(packer)
	packer.write(@source)
	packer.write(@counts)
	packer.write(@annotations)
end

def self.deserialize(unpacker)

Deserialize a coverage object from the given unpacker.

Signature

parameter unpacker Object

The MessagePack-compatible unpacker.

returns Covered::Coverage

The deserialized coverage object.

Implementation

def self.deserialize(unpacker)
	source = unpacker.read
	counts = unpacker.read
	annotations = unpacker.read
	
	self.new(source, counts, annotations)
end