Class: Covered::Coverage

Inherits:
Object
  • Object
show all
Includes:
Ratio
Defined in:
lib/covered/coverage.rb

Overview

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

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Ratio

#complete?, #percentage, #ratio

Constructor Details

#initialize(source, counts = [], annotations = {}) ⇒ Coverage

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



48
49
50
51
52
# File 'lib/covered/coverage.rb', line 48

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

Instance Attribute Details

#annotationsObject (readonly)

Returns the value of attribute annotations.



61
62
63
# File 'lib/covered/coverage.rb', line 61

def annotations
  @annotations
end

#countsObject (readonly)

Returns the value of attribute counts.



58
59
60
# File 'lib/covered/coverage.rb', line 58

def counts
  @counts
end

#Line annotations indexed by line number.(annotationsindexedbylinenumber.) ⇒ Object (readonly)



61
# File 'lib/covered/coverage.rb', line 61

attr :annotations

#Line execution counts indexed by line number.(executioncountsindexedbylinenumber.) ⇒ Object (readonly)



58
# File 'lib/covered/coverage.rb', line 58

attr :counts

#sourceObject

Returns the value of attribute source.



55
56
57
# File 'lib/covered/coverage.rb', line 55

def source
  @source
end

#The covered source metadata.(coveredsourcemetadata.) ⇒ Object (readonly)



55
# File 'lib/covered/coverage.rb', line 55

attr_accessor :source

Class Method Details

.deserialize(unpacker) ⇒ Object

Deserialize a coverage object from the given unpacker.



263
264
265
266
267
268
269
# File 'lib/covered/coverage.rb', line 263

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

.for(path, **options) ⇒ Object

Build coverage for the given source path.



40
41
42
# File 'lib/covered/coverage.rb', line 40

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

Instance Method Details

#[](line_number) ⇒ Object

The raw coverage count for the given line number.



195
196
197
# File 'lib/covered/coverage.rb', line 195

def [] line_number
	@counts[line_number]
end

#annotate(line_number, annotation) ⇒ Object

Add an annotation to the given line number.



78
79
80
81
# File 'lib/covered/coverage.rb', line 78

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

#as_jsonObject

A JSON-compatible representation of this coverage object.



243
244
245
246
247
248
249
250
# File 'lib/covered/coverage.rb', line 243

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

#emptyObject

Create an empty coverage with the same source.



71
72
73
# File 'lib/covered/coverage.rb', line 71

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

#executable_countObject

The number of executable lines.



207
208
209
# File 'lib/covered/coverage.rb', line 207

def executable_count
	executable_lines.count
end

#executable_linesObject

Counts for lines that are executable.



201
202
203
# File 'lib/covered/coverage.rb', line 201

def executable_lines
	@counts.compact
end

#executed_countObject

The number of executable lines that were executed.



219
220
221
# File 'lib/covered/coverage.rb', line 219

def executed_count
	executed_lines.count
end

#executed_linesObject

Counts for executable lines that were executed.



213
214
215
# File 'lib/covered/coverage.rb', line 213

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

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



119
120
121
122
123
124
125
126
# File 'lib/covered/coverage.rb', line 119

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

#freezeObject

Freeze this coverage and its mutable collections.



171
172
173
174
175
176
177
178
# File 'lib/covered/coverage.rb', line 171

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

#fresh?Boolean

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

Returns:

  • (Boolean)


142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/covered/coverage.rb', line 142

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

#mark(line_number, value = 1) ⇒ Object

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



86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/covered/coverage.rb', line 86

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

#merge!(other) ⇒ Object

Merge another coverage object into this coverage object.



101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/covered/coverage.rb', line 101

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

#missing_countObject

The number of executable lines that were not executed.



225
226
227
# File 'lib/covered/coverage.rb', line 225

def missing_count
	executable_count - executed_count
end

#pathObject

The covered source path.



130
131
132
# File 'lib/covered/coverage.rb', line 130

def path
	@source.path
end

#path=(value) ⇒ Object

Assign the covered source path.



136
137
138
# File 'lib/covered/coverage.rb', line 136

def path= value
	@source.path = value
end

Print a human-readable coverage summary.



231
232
233
# File 'lib/covered/coverage.rb', line 231

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

#read(&block) ⇒ Object

Read the covered source.



165
166
167
# File 'lib/covered/coverage.rb', line 165

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

#serialize(packer) ⇒ Object

Serialize this coverage object with the given packer.



254
255
256
257
258
# File 'lib/covered/coverage.rb', line 254

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

#to_aObject

The raw coverage counts array.



182
183
184
# File 'lib/covered/coverage.rb', line 182

def to_a
	@counts
end

#to_sObject

A human-readable representation of this coverage object.



237
238
239
# File 'lib/covered/coverage.rb', line 237

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

#totalObject

The total number of executions across all tracked lines.



65
66
67
# File 'lib/covered/coverage.rb', line 65

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

#zero?Boolean

Whether this coverage has no executions.

Returns:

  • (Boolean)


188
189
190
# File 'lib/covered/coverage.rb', line 188

def zero?
	total.zero?
end