0
# Scalac Scoverage Runtime
1
2
Scalac Scoverage Runtime is the runtime support library for scoverage, a code coverage tool for Scala. This library provides the essential instrumentation infrastructure that collects coverage data during program execution when code has been instrumented by the scalac-scoverage-plugin compiler plugin. It supports both JVM and JavaScript platforms through Scala.js.
3
4
## Package Information
5
6
- **Package Name**: scalac-scoverage-runtime_2.11
7
- **Package Type**: Maven
8
- **Language**: Scala
9
- **Platforms**: JVM, JavaScript (Scala.js)
10
- **Installation**:
11
- SBT: `libraryDependencies += "org.scoverage" %% "scalac-scoverage-runtime" % "1.4.11"`
12
- Maven: `<artifactId>scalac-scoverage-runtime_2.11</artifactId>`
13
14
## Core Imports
15
16
```scala
17
import scoverage.Invoker
18
import scoverage.Platform._
19
```
20
21
For JavaScript-specific functionality:
22
23
```scala
24
import scalajssupport.{File, FileWriter, Source}
25
```
26
27
## Basic Usage
28
29
The runtime library is typically used automatically by instrumented code, but can also be used directly for coverage analysis:
30
31
```scala
32
import scoverage.Invoker
33
import scoverage.Platform._
34
35
// Record statement execution (called by instrumented code)
36
Invoker.invoked(statementId = 42, dataDir = "/path/to/coverage/data")
37
38
// Find measurement files for analysis
39
val dataDir = "/path/to/coverage/data"
40
val measurementFiles = Invoker.findMeasurementFiles(dataDir)
41
42
// Load invoked statement IDs from files
43
val invokedIds = Invoker.invoked(measurementFiles)
44
println(s"Covered statements: ${invokedIds.size}")
45
```
46
47
## Architecture
48
49
The runtime library uses a cross-platform architecture with several key components:
50
51
- **Platform Abstraction**: `scoverage.Platform` provides unified interfaces across JVM and JavaScript with compile-time type resolution
52
- **Coverage Collection**: `scoverage.Invoker` handles thread-safe recording of statement executions with per-thread measurement files
53
- **UUID-based File Naming**: Uses runtime UUID (`runtimeUUID`) combined with thread ID to create unique measurement files
54
- **File System Abstraction**: JavaScript support includes comprehensive file system abstractions that auto-detect and adapt to different JS runtimes (Node.js, Rhino, PhantomJS)
55
- **Thread Safety**: Uses ThreadLocal storage and concurrent TrieMap data structures on JVM, simplified HashMap collections on JavaScript
56
- **Deduplication**: In-memory tracking of recorded statement IDs prevents duplicate writes within the same JVM execution
57
58
## Capabilities
59
60
### Coverage Data Collection
61
62
Core instrumentation runtime that records which statements have been executed during program execution.
63
64
```scala { .api }
65
object Invoker {
66
/**
67
* Records that a statement has been executed
68
* @param id the ID of the statement that was invoked
69
* @param dataDir the directory where measurement data is stored
70
* @param reportTestName whether to include test name information
71
*/
72
def invoked(id: Int, dataDir: String, reportTestName: Boolean = false): Unit
73
74
/**
75
* Gets the measurement file path for a given data directory
76
* @param dataDir the data directory as File
77
* @return measurement file for current thread
78
*/
79
def measurementFile(dataDir: File): File
80
81
/**
82
* Gets the measurement file path for a given data directory
83
* @param dataDir the data directory path as String
84
* @return measurement file for current thread
85
*/
86
def measurementFile(dataDir: String): File
87
88
/**
89
* Finds all measurement files in the given directory
90
* @param dataDir the directory to search as String
91
* @return array of measurement files
92
*/
93
def findMeasurementFiles(dataDir: String): Array[File]
94
95
/**
96
* Finds all measurement files in the given directory
97
* @param dataDir the directory to search as File
98
* @return array of measurement files
99
*/
100
def findMeasurementFiles(dataDir: File): Array[File]
101
102
/**
103
* Loads all invoked statement IDs from measurement files
104
* @param files sequence of measurement files to read
105
* @return set of statement IDs that were executed
106
* @note Each line may contain just the ID, or ID followed by test name (space-separated)
107
*/
108
def invoked(files: Seq[File]): Set[Int]
109
110
/**
111
* Utility method to identify calling ScalaTest suite
112
* @return name of calling test suite or empty string
113
*/
114
def getCallingScalaTest: String
115
}
116
```
117
118
### Platform Abstraction
119
120
Provides unified cross-platform interfaces for file operations and concurrent data structures.
121
122
```scala { .api }
123
object Platform {
124
// JVM Platform (when compiling to JVM bytecode)
125
type ThreadSafeMap[A, B] = scala.collection.concurrent.TrieMap[A, B]
126
lazy val ThreadSafeMap: TrieMap.type = TrieMap
127
type File = java.io.File
128
type FileWriter = java.io.FileWriter
129
type FileFilter = java.io.FileFilter
130
lazy val Source: scala.io.Source.type = scala.io.Source
131
132
// JavaScript Platform (when compiling to JavaScript via Scala.js)
133
type ThreadSafeMap[A, B] = scala.collection.mutable.HashMap[A, B]
134
lazy val ThreadSafeMap: HashMap.type = HashMap
135
type File = scalajssupport.File
136
type FileWriter = scalajssupport.FileWriter
137
type FileFilter = scalajssupport.FileFilter
138
lazy val Source: scalajssupport.Source.type = scalajssupport.Source
139
}
140
```
141
142
### JavaScript File System Support
143
144
JavaScript-compatible file system abstractions that emulate Java I/O APIs for Scala.js environments.
145
146
```scala { .api }
147
// JavaScript File abstraction
148
class File(path: String) {
149
/**
150
* Creates a File with parent path and child name
151
* @param path parent directory path
152
* @param child child file/directory name
153
*/
154
def this(path: String, child: String)
155
156
/** Deletes the file or directory */
157
def delete(): Unit
158
159
/** Returns the absolute path */
160
def getAbsolutePath(): String
161
162
/** Returns the file name */
163
def getName(): String
164
165
/** Returns the file path */
166
def getPath(): String
167
168
/** Checks if this is a directory */
169
def isDirectory(): Boolean
170
171
/** Creates directory structure */
172
def mkdirs(): Unit
173
174
/** Lists all files in directory */
175
def listFiles(): Array[File]
176
177
/** Lists files matching filter */
178
def listFiles(filter: FileFilter): Array[File]
179
180
/** Reads entire file content as string */
181
def readFile(): String
182
}
183
184
object File {
185
/** Joins path components */
186
def pathJoin(path: String, child: String): String
187
188
/** Writes data to file */
189
def write(path: String, data: String, mode: String = "a"): Unit
190
}
191
192
// JavaScript FileWriter abstraction
193
class FileWriter(file: File, append: Boolean) {
194
/** Create FileWriter for file without append mode */
195
def this(file: File)
196
/** Create FileWriter for file path without append mode */
197
def this(file: String)
198
/** Create FileWriter for file path with append mode */
199
def this(file: String, append: Boolean)
200
201
/** Appends character sequence, returns this for chaining */
202
def append(csq: CharSequence): FileWriter
203
204
/** Closes the writer */
205
def close(): Unit
206
207
/** Flushes the writer */
208
def flush(): Unit
209
}
210
211
// JavaScript Source abstraction
212
object Source {
213
/** Creates source reader from file */
214
def fromFile(file: File): scala.io.Source
215
}
216
217
// File filtering interface
218
trait FileFilter {
219
/** Returns true if file should be included */
220
def accept(file: File): Boolean
221
}
222
223
// JavaScript File trait hierarchy
224
trait JsFile {
225
/** Deletes the file or directory */
226
def delete(): Unit
227
/** Returns the absolute path */
228
def getAbsolutePath(): String
229
/** Returns the file name */
230
def getName(): String
231
/** Returns the file path */
232
def getPath(): String
233
/** Checks if this is a directory */
234
def isDirectory(): Boolean
235
/** Creates directory structure */
236
def mkdirs(): Unit
237
/** Lists all files in directory */
238
def listFiles(): Array[File]
239
/** Lists files matching filter */
240
def listFiles(filter: FileFilter): Array[File]
241
/** Reads entire file content as string */
242
def readFile(): String
243
}
244
245
trait JsFileObject {
246
/** Writes data to file with mode (default append) */
247
def write(path: String, data: String, mode: String = "a"): Unit
248
/** Joins path components */
249
def pathJoin(path: String, child: String): String
250
/** Creates JsFile instance for path */
251
def apply(path: String): JsFile
252
}
253
```
254
255
## Types
256
257
```scala { .api }
258
// Core platform types (resolved at compile time based on target platform)
259
type ThreadSafeMap[A, B] // TrieMap on JVM, HashMap on JS
260
type File // java.io.File on JVM, scalajssupport.File on JS
261
type FileWriter // java.io.FileWriter on JVM, scalajssupport.FileWriter on JS
262
type FileFilter // java.io.FileFilter on JVM, scalajssupport.FileFilter on JS
263
```
264
265
## Error Handling
266
267
The library handles errors gracefully across both platforms:
268
269
- **File operations**: JavaScript implementations catch exceptions and return appropriate defaults (e.g., `false` for `isDirectory()` if file doesn't exist)
270
- **Thread safety**:
271
- JVM: Synchronized blocks prevent race conditions in concurrent ID tracking using `dataDirToIds.synchronized` to guard against SI-7943
272
- JavaScript: Single-threaded environment eliminates race conditions
273
- **Measurement files**: Each thread writes to separate files (using thread ID in filename) to avoid write conflicts, especially important on Windows
274
- **Empty lines**: The `invoked(files)` method skips empty lines in measurement files using `if (!line.isEmpty)`
275
- **ThreadLocal cleanup**: ThreadLocal storage for FileWriter instances is properly managed per thread
276
- **File parsing**: Measurement file lines are parsed as integers with proper error handling for malformed data
277
278
## Platform-Specific Behavior
279
280
### JVM Platform
281
- Uses `java.util.concurrent.TrieMap` for thread-safe collections
282
- Leverages standard Java I/O classes
283
- Supports true concurrent access across multiple threads
284
- Uses `ThreadLocal` storage for file writers
285
286
### JavaScript Platform
287
- Uses `scala.collection.mutable.HashMap` (JavaScript is single-threaded)
288
- Provides file system abstractions for different JS environments
289
- Automatically detects and adapts to Node.js, Rhino, or PhantomJS environments using global object detection
290
- File operations delegate to environment-specific implementations (`NodeFile`, `RhinoFile`, `PhantomFile`)
291
- Environment detection: checks for `Packages` (Rhino), `callPhantom` (PhantomJS), or defaults to Node.js
292
293
### Measurement File Format
294
- **File naming**: `scoverage.measurements.<UUID>.<threadId>`
295
- **Content format**: Each line contains either:
296
- Just the statement ID: `"123"`
297
- ID with test name: `"123 MySuite"` (when `reportTestName = true`)
298
- **Multiple files**: One file per thread to avoid write conflicts
299
- **Parsing**: Only the first part (before space) is used as the statement ID
300
301
## Usage Examples
302
303
**Recording coverage data (typically done by instrumented code):**
304
305
```scala
306
import scoverage.Invoker
307
308
// Record that statement 123 was executed
309
Invoker.invoked(123, "/tmp/scoverage-data")
310
311
// Record with test name information
312
Invoker.invoked(456, "/tmp/scoverage-data", reportTestName = true)
313
```
314
315
**Analyzing coverage data:**
316
317
```scala
318
import scoverage.Invoker
319
import scoverage.Platform._
320
321
val dataDir = "/tmp/scoverage-data"
322
323
// Find all measurement files
324
val files = Invoker.findMeasurementFiles(dataDir)
325
println(s"Found ${files.length} measurement files")
326
327
// Load executed statement IDs
328
val executedIds = Invoker.invoked(files.toSeq)
329
println(s"Total statements executed: ${executedIds.size}")
330
331
// Check if specific statement was executed
332
val statementId = 42
333
if (executedIds.contains(statementId)) {
334
println(s"Statement $statementId was executed")
335
}
336
```
337
338
**Cross-platform file operations:**
339
340
```scala
341
import scoverage.Platform._
342
343
// This works on both JVM and JavaScript
344
val file = new File("/path/to/data", "measurements.txt")
345
val writer = new FileWriter(file, append = true)
346
writer.append("42\n")
347
writer.close()
348
349
// Read measurement data
350
val source = Source.fromFile(file)
351
val lines = source.getLines().toList
352
source.close()
353
```