CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-scoverage--scalac-scoverage-plugin

Comprehensive code coverage tool for Scala providing statement and branch coverage through compiler plugin instrumentation and report generation

Pending
Overview
Eval results
Files

serialization.mddocs/

Data Serialization

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.

Core API

Serializer Object

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 directory
  • serialize(coverage, file, sourceRoot) - Write coverage to specific file
  • serialize(coverage, writer, sourceRoot) - Write coverage to Writer (for custom streams)

Deserialization Methods:

  • deserialize(file, sourceRoot) - Load coverage from file
  • deserialize(lines, sourceRoot) - Load coverage from line iterator

Utility Methods:

  • coverageFile(dataDir) - Get standard coverage file path for directory
  • clean(dataDir) - Remove measurement files from directory
  • findMeasurementFiles(dataDir) - Find all measurement files in directory

Usage Examples

Basic Serialization and Deserialization

import 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")

Working with Specific Files

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)

Streaming Serialization

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)

Build Tool Integration

// 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}")

File Format Details

Coverage File Format

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")

File Structure

Each statement is represented as a block of fields separated by newlines, with blocks separated by form feed characters (\f).

Field Order:

  1. Statement ID (integer)
  2. Relative source path (string)
  3. Package name (string)
  4. Class name (string)
  5. Class type (Class/Object/Trait)
  6. Full class name (string)
  7. Method name (string)
  8. Start character offset (integer)
  9. End character offset (integer)
  10. Line number (integer)
  11. Symbol name (string)
  12. Tree name (string)
  13. Is branch (boolean)
  14. Invocation count (integer)
  15. Is ignored (boolean)
  16. Description (multi-line string)

File Management

Standard File Names

// 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.*

Cleaning Data Directories

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")

Directory Structure

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 files

Path Handling

Relative Path Conversion

During 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.scala

Path Resolution During Deserialization

During 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.scala

Advanced Usage

Custom Source Root Handling

import 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)

Error Recovery

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
  }
}

Batch Processing

// 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")

Format Version Compatibility

Current Format Version

The current format version is 3.0, as defined in:

# Coverage data, format version: 3.0

Version Checking

The 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)

Error Handling

Common Issues

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
}

Best Practices

  1. Validate Input: Always check that data directories and source roots exist
  2. Handle Exceptions: Wrap serialization/deserialization in try-catch blocks
  3. Path Consistency: Use consistent path handling across serialization and deserialization
  4. Format Validation: Verify format version compatibility before processing
  5. Cleanup: Use clean() method to remove stale measurement files when appropriate

Install with Tessl CLI

npx tessl i tessl/maven-org-scoverage--scalac-scoverage-plugin

docs

aggregation.md

cobertura-reports.md

coverage-model.md

html-reports.md

index.md

io-utils.md

plugin.md

runtime.md

serialization.md

xml-reports.md

tile.json