0
# Data Serialization
1
2
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.
3
4
## Core API
5
6
### Serializer Object
7
8
```scala { .api }
9
object Serializer {
10
// Serialization methods
11
def serialize(coverage: Coverage, dataDir: String, sourceRoot: String): Unit
12
def serialize(coverage: Coverage, file: File, sourceRoot: File): Unit
13
def serialize(coverage: Coverage, writer: Writer, sourceRoot: File): Unit
14
15
// Deserialization methods
16
def deserialize(file: File, sourceRoot: File): Coverage
17
def deserialize(lines: Iterator[String], sourceRoot: File): Coverage
18
19
// File utilities
20
def coverageFile(dataDir: File): File
21
def coverageFile(dataDir: String): File
22
def clean(dataDir: File): Unit
23
def clean(dataDir: String): Unit
24
def findMeasurementFiles(dataDir: File): Array[File]
25
}
26
```
27
28
**Serialization Methods:**
29
- `serialize(coverage, dataDir, sourceRoot)` - Write coverage to default file in directory
30
- `serialize(coverage, file, sourceRoot)` - Write coverage to specific file
31
- `serialize(coverage, writer, sourceRoot)` - Write coverage to Writer (for custom streams)
32
33
**Deserialization Methods:**
34
- `deserialize(file, sourceRoot)` - Load coverage from file
35
- `deserialize(lines, sourceRoot)` - Load coverage from line iterator
36
37
**Utility Methods:**
38
- `coverageFile(dataDir)` - Get standard coverage file path for directory
39
- `clean(dataDir)` - Remove measurement files from directory
40
- `findMeasurementFiles(dataDir)` - Find all measurement files in directory
41
42
## Usage Examples
43
44
### Basic Serialization and Deserialization
45
46
```scala
47
import java.io.File
48
import scoverage.serialize.Serializer
49
import scoverage.domain.Coverage
50
51
// Create or obtain coverage data
52
val coverage: Coverage = generateCoverageData()
53
54
// Serialize to default location
55
val dataDir = "target/scoverage-data"
56
val sourceRoot = "src/main/scala"
57
Serializer.serialize(coverage, dataDir, sourceRoot)
58
59
// Later, deserialize the coverage data
60
val sourceRootFile = new File(sourceRoot)
61
val coverageFile = Serializer.coverageFile(dataDir)
62
val loadedCoverage = Serializer.deserialize(coverageFile, sourceRootFile)
63
64
println(s"Loaded coverage with ${loadedCoverage.statementCount} statements")
65
```
66
67
### Working with Specific Files
68
69
```scala
70
import java.io.File
71
import scoverage.serialize.Serializer
72
73
val coverage: Coverage = getCoverageData()
74
val sourceRoot = new File("src/main/scala")
75
76
// Serialize to a specific file
77
val customFile = new File("reports/custom-coverage.data")
78
customFile.getParentFile.mkdirs()
79
Serializer.serialize(coverage, customFile, sourceRoot)
80
81
// Deserialize from the custom file
82
val loadedCoverage = Serializer.deserialize(customFile, sourceRoot)
83
```
84
85
### Streaming Serialization
86
87
```scala
88
import java.io.{StringWriter, StringReader, BufferedReader}
89
import scoverage.serialize.Serializer
90
91
val coverage: Coverage = getCoverageData()
92
val sourceRoot = new File("src/main/scala")
93
94
// Serialize to a string (in-memory)
95
val stringWriter = new StringWriter()
96
Serializer.serialize(coverage, stringWriter, sourceRoot)
97
val serializedData = stringWriter.toString()
98
99
// Deserialize from string data
100
val reader = new BufferedReader(new StringReader(serializedData))
101
val lines = Iterator.continually(reader.readLine()).takeWhile(_ != null)
102
val deserializedCoverage = Serializer.deserialize(lines, sourceRoot)
103
```
104
105
### Build Tool Integration
106
107
```scala
108
// SBT integration example
109
import java.io.File
110
import scoverage.serialize.Serializer
111
112
val coverage: Coverage = collectCoverageFromTests()
113
val dataDir = crossTarget.value / "scoverage-data"
114
val sourceRoot = (Compile / scalaSource).value
115
116
// Ensure directory exists
117
dataDir.mkdirs()
118
119
// Serialize coverage data
120
Serializer.serialize(coverage, dataDir.getAbsolutePath, sourceRoot.getAbsolutePath)
121
122
// The coverage file can now be used by report generators
123
val coverageFile = Serializer.coverageFile(dataDir)
124
println(s"Coverage data written to: ${coverageFile.getAbsolutePath}")
125
```
126
127
## File Format Details
128
129
### Coverage File Format
130
131
The serialized coverage file uses a structured text format with version information:
132
133
```
134
# Coverage data, format version: 3.0
135
# Statement data:
136
# - id
137
# - source path
138
# - package name
139
# - class name
140
# - class type (Class, Object or Trait)
141
# - full class name
142
# - method name
143
# - start offset
144
# - end offset
145
# - line number
146
# - symbol name
147
# - tree name
148
# - is branch
149
# - invocations count
150
# - is ignored
151
# - description (can be multi-line)
152
# '\f' sign
153
# ------------------------------------------
154
1
155
src/main/scala/com/example/MyClass.scala
156
com.example
157
MyClass
158
Class
159
com.example.MyClass
160
myMethod
161
100
162
125
163
10
164
scala.Predef.println
165
Apply
166
false
167
0
168
false
169
println("Hello World")
170
```
171
172
### File Structure
173
174
Each statement is represented as a block of fields separated by newlines, with blocks separated by form feed characters (`\f`).
175
176
**Field Order:**
177
1. Statement ID (integer)
178
2. Relative source path (string)
179
3. Package name (string)
180
4. Class name (string)
181
5. Class type (Class/Object/Trait)
182
6. Full class name (string)
183
7. Method name (string)
184
8. Start character offset (integer)
185
9. End character offset (integer)
186
10. Line number (integer)
187
11. Symbol name (string)
188
12. Tree name (string)
189
13. Is branch (boolean)
190
14. Invocation count (integer)
191
15. Is ignored (boolean)
192
16. Description (multi-line string)
193
194
## File Management
195
196
### Standard File Names
197
198
```scala
199
// Default coverage file name
200
val coverageFile = Serializer.coverageFile(new File("target/scoverage-data"))
201
// Returns: target/scoverage-data/scoverage.coverage
202
203
// Get measurement files
204
val measurementFiles = Serializer.findMeasurementFiles(new File("target/scoverage-data"))
205
// Returns files matching: scoverage.measurements.*
206
```
207
208
### Cleaning Data Directories
209
210
```scala
211
import java.io.File
212
import scoverage.serialize.Serializer
213
214
// Clean measurement files but keep coverage file
215
val dataDir = new File("target/scoverage-data")
216
Serializer.clean(dataDir)
217
218
// Or using string path
219
Serializer.clean("target/scoverage-data")
220
```
221
222
### Directory Structure
223
224
A typical scoverage data directory contains:
225
226
```
227
target/scoverage-data/
228
├── scoverage.coverage # Main coverage data file
229
├── scoverage.measurements.1 # Measurement file for thread 1
230
├── scoverage.measurements.2 # Measurement file for thread 2
231
└── scoverage.measurements.N # Additional measurement files
232
```
233
234
## Path Handling
235
236
### Relative Path Conversion
237
238
During serialization, absolute source paths are converted to relative paths based on the source root:
239
240
```scala
241
// Absolute path: /project/src/main/scala/com/example/MyClass.scala
242
// Source root: /project/src/main/scala
243
// Stored as: com/example/MyClass.scala
244
```
245
246
### Path Resolution During Deserialization
247
248
During deserialization, relative paths are converted back to absolute paths:
249
250
```scala
251
// Stored path: com/example/MyClass.scala
252
// Source root: /project/src/main/scala
253
// Resolved to: /project/src/main/scala/com/example/MyClass.scala
254
```
255
256
## Advanced Usage
257
258
### Custom Source Root Handling
259
260
```scala
261
import java.io.File
262
import scoverage.serialize.Serializer
263
264
// Handle multiple source roots
265
val sourceRoots = Seq(
266
new File("module1/src/main/scala"),
267
new File("module2/src/main/scala")
268
)
269
270
// For serialization, use the common parent
271
val commonRoot = new File(".")
272
Serializer.serialize(coverage, dataDir, commonRoot.getAbsolutePath)
273
274
// For deserialization, the common root will resolve paths correctly
275
val loadedCoverage = Serializer.deserialize(coverageFile, commonRoot)
276
```
277
278
### Error Recovery
279
280
```scala
281
import java.io.File
282
import scoverage.serialize.Serializer
283
import scala.util.{Try, Success, Failure}
284
285
def loadCoverageWithRecovery(dataDir: File, sourceRoot: File): Option[Coverage] = {
286
val coverageFile = Serializer.coverageFile(dataDir)
287
288
Try(Serializer.deserialize(coverageFile, sourceRoot)) match {
289
case Success(coverage) =>
290
println(s"Successfully loaded ${coverage.statementCount} statements")
291
Some(coverage)
292
293
case Failure(exception) =>
294
println(s"Failed to load coverage data: ${exception.getMessage}")
295
None
296
}
297
}
298
```
299
300
### Batch Processing
301
302
```scala
303
// Process multiple data directories
304
val dataDirs = Seq(
305
new File("module1/target/scoverage-data"),
306
new File("module2/target/scoverage-data")
307
)
308
309
val sourceRoot = new File("src/main/scala")
310
311
val allCoverage: Seq[Coverage] = dataDirs.flatMap { dataDir =>
312
if (Serializer.coverageFile(dataDir).exists()) {
313
Try(Serializer.deserialize(Serializer.coverageFile(dataDir), sourceRoot)).toOption
314
} else {
315
None
316
}
317
}
318
319
println(s"Loaded coverage from ${allCoverage.size} modules")
320
```
321
322
## Format Version Compatibility
323
324
### Current Format Version
325
326
The current format version is 3.0, as defined in:
327
328
```
329
# Coverage data, format version: 3.0
330
```
331
332
### Version Checking
333
334
The deserializer validates the format version:
335
336
```scala
337
// Deserialization will fail with wrong format version
338
val lines = Iterator(
339
"# Coverage data, format version: 2.0", // Wrong version
340
"# Statement data:",
341
// ... rest of data
342
)
343
344
// This will throw an exception
345
val coverage = Serializer.deserialize(lines, sourceRoot)
346
```
347
348
## Error Handling
349
350
### Common Issues
351
352
**File Permission Errors:**
353
```scala
354
// Handle permission issues during serialization
355
try {
356
Serializer.serialize(coverage, dataDir, sourceRoot)
357
} catch {
358
case ex: java.io.IOException =>
359
println(s"Failed to write coverage file: ${ex.getMessage}")
360
// Handle error (create directory, check permissions, etc.)
361
}
362
```
363
364
**Malformed Coverage Files:**
365
```scala
366
// Handle corrupted or invalid coverage files
367
try {
368
val coverage = Serializer.deserialize(coverageFile, sourceRoot)
369
} catch {
370
case ex: IllegalArgumentException =>
371
println(s"Invalid coverage file format: ${ex.getMessage}")
372
case ex: NumberFormatException =>
373
println(s"Corrupted numeric data in coverage file: ${ex.getMessage}")
374
}
375
```
376
377
**Missing Source Files:**
378
```scala
379
// Handle cases where source files have moved
380
val sourceRoot = new File("src/main/scala")
381
if (!sourceRoot.exists()) {
382
println(s"Warning: Source root ${sourceRoot.getPath} does not exist")
383
// Use alternative source root or handle gracefully
384
}
385
```
386
387
### Best Practices
388
389
1. **Validate Input**: Always check that data directories and source roots exist
390
2. **Handle Exceptions**: Wrap serialization/deserialization in try-catch blocks
391
3. **Path Consistency**: Use consistent path handling across serialization and deserialization
392
4. **Format Validation**: Verify format version compatibility before processing
393
5. **Cleanup**: Use `clean()` method to remove stale measurement files when appropriate