CoveredSourceCoveredPersist

class Persist

Persists coverage records to a MessagePack database.

Definitions

def initialize(output, path = DEFAULT_PATH)

Initialize persistence for the given output and database path.

Signature

parameter output Covered::Base

The output to wrap.

parameter path String

The coverage database path.

Implementation

def initialize(output, path = DEFAULT_PATH)
	super(output)
	
	@path = self.expand_path(path)
end

def apply(record, ignore_mtime: false)

Apply a persisted record to the output. Records with stale source modification times are ignored unless ignore_mtime is true.

Signature

parameter record Hash

The persisted coverage record.

parameter ignore_mtime Boolean

Whether to apply records even if their source appears stale.

returns Boolean

Whether the record was applied.

Implementation

def apply(record, ignore_mtime: false)
	if coverage = record[:coverage]
		if path = record[:path]
			path = self.expand_path(path)
			coverage.path = path
		end
		
		if ignore_mtime || coverage.fresh?
			add(coverage)
			return true
		end
	end
	
	return false
end

def serialize(coverage)

Convert coverage into a database record.

Signature

parameter coverage Covered::Coverage

The coverage object to serialize.

returns Hash

A MessagePack-compatible record.

Implementation

def serialize(coverage)
	{
		# We want to use relative paths so that moving the repo won't break everything:
		pid: Process.pid,
		path: relative_path(coverage.path),
		# relative_path: relative_path(coverage.path),
		coverage: coverage,
	}
end

def load!(**options)

Load persisted coverage records into the output.

Signature

parameter options Hash

Options forwarded to Covered::Persist#apply.

raises LoadError

If the database exists but cannot be decoded.

Implementation

def load!(**options)
	return unless File.exist?(@path)
	
	# Load existing coverage information and mark all files:
	File.open(@path, "rb") do |file|
		file.flock(File::LOCK_SH)
		
		make_unpacker(file).each do |record|
			# pp load: record
			self.apply(record, **options)
		end
	end
rescue
	raise LoadError, "Failed to load coverage from #{@path}, maybe old format or corrupt!"
end

def save!

Save all output coverage records to the database.

Implementation

def save!
	# Dump all coverage:
	File.open(@path, "ab") do |file|
		file.flock(File::LOCK_EX)
		
		packer = make_packer(file)
		
		@output.each do |coverage|
			# pp save: coverage
			packer.write(serialize(coverage))
		end
		
		packer.flush
	end
end

def finish

Finish the wrapped output and save the coverage database.

Implementation

def finish
	super
	
	self.save!
end

def each(&block)

Reload persisted coverage and enumerate the wrapped output.

Signature

yields {|coverage| ...}

Each coverage object from the reloaded output.

parameter coverage Covered::Coverage

The current coverage object.

returns Enumerator | Nil

An enumerator without a block.

Implementation

def each(&block)
	return to_enum unless block_given?
	
	@output.clear
	self.load!
	
	super
end

def make_factory

Build the MessagePack factory used for coverage records.

Signature

returns MessagePack::Factory

The configured MessagePack factory.

Implementation

def make_factory
	factory = MessagePack::Factory.new
	
	factory.register_type(0x00, Symbol)
	
	factory.register_type(0x01, Time,
		packer: MessagePack::Time::Packer,
		unpacker: MessagePack::Time::Unpacker
	)
	
	factory.register_type(0x20, Source,
		recursive: true,
		packer: :serialize,
		unpacker: :deserialize,
	)
	
	factory.register_type(0x21, Coverage,
		recursive: true,
		packer: :serialize,
		unpacker: :deserialize,
	)
	
	return factory
end

def make_packer(io)

Build a MessagePack packer for the given IO.

Signature

parameter io IO

The IO to write packed records to.

returns MessagePack::Packer

A packer configured for coverage records.

Implementation

def make_packer(io)
	return make_factory.packer(io)
end

def make_unpacker(io)

Build a MessagePack unpacker for the given IO.

Signature

parameter io IO

The IO to read packed records from.

returns MessagePack::Unpacker

An unpacker configured for coverage records.

Implementation

def make_unpacker(io)
	return make_factory.unpacker(io)
end