Comprehensive code coverage tool for Scala providing statement and branch coverage through compiler plugin instrumentation and report generation
—
The Serializer provides functionality for persisting and loading coverage data to/from files, enabling report generation and analysis across different build phases. It handles the conversion between in-memory Coverage objects and the standardized scoverage file format.
object Serializer {
// Serialization methods
def serialize(coverage: Coverage, dataDir: String, sourceRoot: String): Unit
def serialize(coverage: Coverage, file: File, sourceRoot: File): Unit
def serialize(coverage: Coverage, writer: Writer, sourceRoot: File): Unit
// Deserialization methods
def deserialize(file: File, sourceRoot: File): Coverage
def deserialize(lines: Iterator[String], sourceRoot: File): Coverage
// File utilities
def coverageFile(dataDir: File): File
def coverageFile(dataDir: String): File
def clean(dataDir: File): Unit
def clean(dataDir: String): Unit
def findMeasurementFiles(dataDir: File): Array[File]
}Serialization Methods:
serialize(coverage, dataDir, sourceRoot) - Write coverage to default file in directoryserialize(coverage, file, sourceRoot) - Write coverage to specific fileserialize(coverage, writer, sourceRoot) - Write coverage to Writer (for custom streams)Deserialization Methods:
deserialize(file, sourceRoot) - Load coverage from filedeserialize(lines, sourceRoot) - Load coverage from line iteratorUtility Methods:
coverageFile(dataDir) - Get standard coverage file path for directoryclean(dataDir) - Remove measurement files from directoryfindMeasurementFiles(dataDir) - Find all measurement files in directoryimport java.io.File
import scoverage.serialize.Serializer
import scoverage.domain.Coverage
// Create or obtain coverage data
val coverage: Coverage = generateCoverageData()
// Serialize to default location
val dataDir = "target/scoverage-data"
val sourceRoot = "src/main/scala"
Serializer.serialize(coverage, dataDir, sourceRoot)
// Later, deserialize the coverage data
val sourceRootFile = new File(sourceRoot)
val coverageFile = Serializer.coverageFile(dataDir)
val loadedCoverage = Serializer.deserialize(coverageFile, sourceRootFile)
println(s"Loaded coverage with ${loadedCoverage.statementCount} statements")import java.io.File
import scoverage.serialize.Serializer
val coverage: Coverage = getCoverageData()
val sourceRoot = new File("src/main/scala")
// Serialize to a specific file
val customFile = new File("reports/custom-coverage.data")
customFile.getParentFile.mkdirs()
Serializer.serialize(coverage, customFile, sourceRoot)
// Deserialize from the custom file
val loadedCoverage = Serializer.deserialize(customFile, sourceRoot)import java.io.{StringWriter, StringReader, BufferedReader}
import scoverage.serialize.Serializer
val coverage: Coverage = getCoverageData()
val sourceRoot = new File("src/main/scala")
// Serialize to a string (in-memory)
val stringWriter = new StringWriter()
Serializer.serialize(coverage, stringWriter, sourceRoot)
val serializedData = stringWriter.toString()
// Deserialize from string data
val reader = new BufferedReader(new StringReader(serializedData))
val lines = Iterator.continually(reader.readLine()).takeWhile(_ != null)
val deserializedCoverage = Serializer.deserialize(lines, sourceRoot)// SBT integration example
import java.io.File
import scoverage.serialize.Serializer
val coverage: Coverage = collectCoverageFromTests()
val dataDir = crossTarget.value / "scoverage-data"
val sourceRoot = (Compile / scalaSource).value
// Ensure directory exists
dataDir.mkdirs()
// Serialize coverage data
Serializer.serialize(coverage, dataDir.getAbsolutePath, sourceRoot.getAbsolutePath)
// The coverage file can now be used by report generators
val coverageFile = Serializer.coverageFile(dataDir)
println(s"Coverage data written to: ${coverageFile.getAbsolutePath}")The serialized coverage file uses a structured text format with version information:
# Coverage data, format version: 3.0
# Statement data:
# - id
# - source path
# - package name
# - class name
# - class type (Class, Object or Trait)
# - full class name
# - method name
# - start offset
# - end offset
# - line number
# - symbol name
# - tree name
# - is branch
# - invocations count
# - is ignored
# - description (can be multi-line)
# '\f' sign
# ------------------------------------------
1
src/main/scala/com/example/MyClass.scala
com.example
MyClass
Class
com.example.MyClass
myMethod
100
125
10
scala.Predef.println
Apply
false
0
false
println("Hello World")Each statement is represented as a block of fields separated by newlines, with blocks separated by form feed characters (\f).
Field Order:
// Default coverage file name
val coverageFile = Serializer.coverageFile(new File("target/scoverage-data"))
// Returns: target/scoverage-data/scoverage.coverage
// Get measurement files
val measurementFiles = Serializer.findMeasurementFiles(new File("target/scoverage-data"))
// Returns files matching: scoverage.measurements.*import java.io.File
import scoverage.serialize.Serializer
// Clean measurement files but keep coverage file
val dataDir = new File("target/scoverage-data")
Serializer.clean(dataDir)
// Or using string path
Serializer.clean("target/scoverage-data")A typical scoverage data directory contains:
target/scoverage-data/
├── scoverage.coverage # Main coverage data file
├── scoverage.measurements.1 # Measurement file for thread 1
├── scoverage.measurements.2 # Measurement file for thread 2
└── scoverage.measurements.N # Additional measurement filesDuring serialization, absolute source paths are converted to relative paths based on the source root:
// Absolute path: /project/src/main/scala/com/example/MyClass.scala
// Source root: /project/src/main/scala
// Stored as: com/example/MyClass.scalaDuring deserialization, relative paths are converted back to absolute paths:
// Stored path: com/example/MyClass.scala
// Source root: /project/src/main/scala
// Resolved to: /project/src/main/scala/com/example/MyClass.scalaimport java.io.File
import scoverage.serialize.Serializer
// Handle multiple source roots
val sourceRoots = Seq(
new File("module1/src/main/scala"),
new File("module2/src/main/scala")
)
// For serialization, use the common parent
val commonRoot = new File(".")
Serializer.serialize(coverage, dataDir, commonRoot.getAbsolutePath)
// For deserialization, the common root will resolve paths correctly
val loadedCoverage = Serializer.deserialize(coverageFile, commonRoot)import java.io.File
import scoverage.serialize.Serializer
import scala.util.{Try, Success, Failure}
def loadCoverageWithRecovery(dataDir: File, sourceRoot: File): Option[Coverage] = {
val coverageFile = Serializer.coverageFile(dataDir)
Try(Serializer.deserialize(coverageFile, sourceRoot)) match {
case Success(coverage) =>
println(s"Successfully loaded ${coverage.statementCount} statements")
Some(coverage)
case Failure(exception) =>
println(s"Failed to load coverage data: ${exception.getMessage}")
None
}
}// Process multiple data directories
val dataDirs = Seq(
new File("module1/target/scoverage-data"),
new File("module2/target/scoverage-data")
)
val sourceRoot = new File("src/main/scala")
val allCoverage: Seq[Coverage] = dataDirs.flatMap { dataDir =>
if (Serializer.coverageFile(dataDir).exists()) {
Try(Serializer.deserialize(Serializer.coverageFile(dataDir), sourceRoot)).toOption
} else {
None
}
}
println(s"Loaded coverage from ${allCoverage.size} modules")The current format version is 3.0, as defined in:
# Coverage data, format version: 3.0The deserializer validates the format version:
// Deserialization will fail with wrong format version
val lines = Iterator(
"# Coverage data, format version: 2.0", // Wrong version
"# Statement data:",
// ... rest of data
)
// This will throw an exception
val coverage = Serializer.deserialize(lines, sourceRoot)File Permission Errors:
// Handle permission issues during serialization
try {
Serializer.serialize(coverage, dataDir, sourceRoot)
} catch {
case ex: java.io.IOException =>
println(s"Failed to write coverage file: ${ex.getMessage}")
// Handle error (create directory, check permissions, etc.)
}Malformed Coverage Files:
// Handle corrupted or invalid coverage files
try {
val coverage = Serializer.deserialize(coverageFile, sourceRoot)
} catch {
case ex: IllegalArgumentException =>
println(s"Invalid coverage file format: ${ex.getMessage}")
case ex: NumberFormatException =>
println(s"Corrupted numeric data in coverage file: ${ex.getMessage}")
}Missing Source Files:
// Handle cases where source files have moved
val sourceRoot = new File("src/main/scala")
if (!sourceRoot.exists()) {
println(s"Warning: Source root ${sourceRoot.getPath} does not exist")
// Use alternative source root or handle gracefully
}clean() method to remove stale measurement files when appropriateInstall with Tessl CLI
npx tessl i tessl/maven-org-scoverage--scalac-scoverage-plugin