0
# Event Handling and Reporting
1
2
The event handling system converts ZIO test results into SBT-compatible events for reporting, IDE integration, and test result visualization. It translates ZIO's effect-based test results into the imperative event model expected by SBT's testing infrastructure.
3
4
## Capabilities
5
6
### ZTestEvent
7
8
Case class that implements SBT's `Event` interface, representing a single test result. It contains all information needed by SBT for test reporting and IDE integration.
9
10
```scala { .api }
11
/**
12
* SBT event representing a single ZIO test result
13
* Contains all information needed for SBT reporting and IDE integration
14
*/
15
case class ZTestEvent(
16
fullyQualifiedName: String,
17
selector: sbt.testing.Selector,
18
status: sbt.testing.Status,
19
maybeThrowable: Option[Throwable],
20
duration: Long,
21
fingerprint: sbt.testing.Fingerprint
22
) extends sbt.testing.Event {
23
24
/** Fully qualified name of the test class/object */
25
val fullyQualifiedName: String
26
27
/** SBT selector identifying the specific test */
28
val selector: sbt.testing.Selector
29
30
/** Test execution status (Success, Failure, Ignored, etc.) */
31
val status: sbt.testing.Status
32
33
/** Optional throwable for failed tests */
34
val maybeThrowable: Option[Throwable]
35
36
/** Test execution duration in milliseconds */
37
val duration: Long
38
39
/** Fingerprint used to discover this test */
40
val fingerprint: sbt.testing.Fingerprint
41
42
/**
43
* SBT-compatible throwable accessor
44
* @return OptionalThrowable wrapper for SBT compatibility
45
*/
46
def throwable(): sbt.testing.OptionalThrowable
47
}
48
```
49
50
**Platform Variations:**
51
52
**JVM/JavaScript Platforms:**
53
Uses standard case class with direct field access.
54
55
**Native Platform:**
56
Uses different constructor parameter names for SBT compatibility:
57
58
```scala { .api }
59
case class ZTestEvent(
60
fullyQualifiedName0: String,
61
selector0: sbt.testing.Selector,
62
status0: sbt.testing.Status,
63
maybeThrowable: Option[Throwable],
64
duration0: Long,
65
fingerprint0: sbt.testing.Fingerprint
66
) extends sbt.testing.Event {
67
override def fullyQualifiedName(): String = fullyQualifiedName0
68
override def selector(): sbt.testing.Selector = selector0
69
override def status(): sbt.testing.Status = status0
70
override def duration(): Long = duration0
71
override def fingerprint(): sbt.testing.Fingerprint = fingerprint0
72
def throwable(): sbt.testing.OptionalThrowable = /* ... */
73
}
74
```
75
76
Both implementations provide identical functionality with platform-specific adaptations for SBT integration.
77
78
**Status Mapping:**
79
80
The event system maps ZIO test results to SBT status values:
81
82
- `TestSuccess.Succeeded` → `Status.Success`
83
- `TestFailure.Assertion` → `Status.Failure` (with AssertionError)
84
- `TestFailure.Runtime` → `Status.Failure` (with RuntimeException)
85
- `TestSuccess.Ignored` → `Status.Ignored`
86
87
**Usage in Test Execution:**
88
89
```scala
90
// Events are created automatically during test execution
91
// ZTestEvent.from() converts ZIO ExecutedSpec to SBT events
92
val events = ZTestEvent.from(executedSpec, className, fingerprint)
93
events.foreach(event => eventHandler.handle(event))
94
```
95
96
### ZTestEvent Companion Object
97
98
Factory methods for creating `ZTestEvent` instances from ZIO test results.
99
100
```scala { .api }
101
object ZTestEvent {
102
/**
103
* Converts ZIO ExecutedSpec to sequence of SBT events
104
* @param executedSpec ZIO test execution results
105
* @param fullyQualifiedName Test class name
106
* @param fingerprint Fingerprint used for test discovery
107
* @return Sequence of SBT events representing all test results
108
*/
109
def from[E](
110
executedSpec: zio.test.ExecutedSpec[E],
111
fullyQualifiedName: String,
112
fingerprint: sbt.testing.Fingerprint
113
): Seq[ZTestEvent]
114
}
115
```
116
117
**Conversion Process:**
118
119
The `from` method recursively processes ZIO's `ExecutedSpec` structure:
120
121
1. **Labeled Specs**: Extracts test names and propagates to nested specs
122
2. **Multiple Specs**: Processes each spec individually and combines results
123
3. **Test Cases**: Converts individual test results to `ZTestEvent` instances
124
125
**Internal Processing Methods:**
126
127
```scala { .api }
128
// Internal methods (not part of public API but used by from())
129
private def toStatus[E](result: Either[zio.test.TestFailure[E], zio.test.TestSuccess]): sbt.testing.Status
130
131
private def toThrowable[E](
132
spec: zio.test.ExecutedSpec[E],
133
label: Option[String],
134
result: Either[zio.test.TestFailure[E], zio.test.TestSuccess]
135
): Option[Throwable]
136
137
private def render[E](spec: zio.test.ExecutedSpec[E], label: Option[String]): String
138
```
139
140
## Event Processing Examples
141
142
### Basic Test Result Conversion
143
144
```scala
145
import zio.test._
146
import zio.test.sbt.ZTestEvent
147
148
// Example ZIO test specification
149
object MyTestSpec extends DefaultRunnableSpec {
150
def spec = suite("Calculator Tests")(
151
test("addition works") {
152
assert(2 + 2)(equalTo(4))
153
},
154
test("division by zero fails") {
155
assert(10 / 0)(anything) // This will fail
156
}
157
)
158
}
159
160
// After execution, ZIO produces an ExecutedSpec
161
// ZTestEvent.from() converts this to SBT events:
162
163
val events = ZTestEvent.from(
164
executedSpec = executedSpecResult,
165
fullyQualifiedName = "com.example.MyTestSpec",
166
fingerprint = RunnableSpecFingerprint
167
)
168
169
// Results in events like:
170
// ZTestEvent(
171
// fullyQualifiedName = "com.example.MyTestSpec",
172
// selector = TestSelector("addition works"),
173
// status = Status.Success,
174
// maybeThrowable = None,
175
// duration = 15, // milliseconds
176
// fingerprint = RunnableSpecFingerprint
177
// )
178
//
179
// ZTestEvent(
180
// fullyQualifiedName = "com.example.MyTestSpec",
181
// selector = TestSelector("division by zero fails"),
182
// status = Status.Failure,
183
// maybeThrowable = Some(RuntimeException(...)),
184
// duration = 5,
185
// fingerprint = RunnableSpecFingerprint
186
// )
187
```
188
189
### Nested Suite Handling
190
191
```scala
192
// ZIO test with nested suites
193
def spec = suite("Math Operations")(
194
suite("Basic Operations")(
195
test("addition")(assert(1 + 1)(equalTo(2))),
196
test("subtraction")(assert(5 - 3)(equalTo(2)))
197
),
198
suite("Advanced Operations")(
199
test("multiplication")(assert(3 * 4)(equalTo(12))),
200
test("division")(assert(8 / 2)(equalTo(4)))
201
)
202
)
203
204
// ZTestEvent.from() flattens the structure and creates events like:
205
// - TestSelector("addition")
206
// - TestSelector("subtraction")
207
// - TestSelector("multiplication")
208
// - TestSelector("division")
209
// Each with appropriate status and timing information
210
```
211
212
### Error Reporting and Formatting
213
214
The event system provides detailed error reporting with ANSI color stripping for clean output:
215
216
```scala
217
// Failed assertion example
218
test("string comparison") {
219
assert("hello")(equalTo("world"))
220
}
221
222
// Produces ZTestEvent with:
223
// - status = Status.Failure
224
// - maybeThrowable = Some(AssertionError("expected: world, actual: hello"))
225
// - Detailed failure message with diff information
226
// - ANSI color codes stripped for clean reporting
227
```
228
229
## Integration with SBT Reporting
230
231
### Event Handler Integration
232
233
Events are processed by SBT's event handling system:
234
235
```scala
236
// In BaseTestTask.run() method:
237
val events = ZTestEvent.from(spec, taskDef.fullyQualifiedName(), taskDef.fingerprint())
238
events.foreach(event => ZIO.effect(eventHandler.handle(event)))
239
```
240
241
### IDE Integration
242
243
The event system enables IDE integration through SBT's test interface:
244
245
- **IntelliJ IDEA**: Displays individual test results with green/red indicators
246
- **VS Code**: Shows test status in test explorer panels
247
- **Eclipse**: Integrates with JUnit-style test running views
248
- **Command Line**: Provides detailed test output with timing and failure information
249
250
### Custom Event Processing
251
252
For advanced use cases, you can process events before passing to SBT:
253
254
```scala
255
// Custom event processing example
256
class CustomTestTask extends BaseTestTask {
257
override protected def run(eventHandler: EventHandler) = {
258
for {
259
spec <- specInstance.runSpec(FilteredSpec(specInstance.spec, args))
260
events = ZTestEvent.from(spec, taskDef.fullyQualifiedName(), taskDef.fingerprint())
261
262
// Custom processing - filter, transform, or aggregate events
263
processedEvents = events.filter(_.status != Status.Ignored)
264
265
_ <- ZIO.foreach(processedEvents) { event =>
266
// Custom logging, metrics collection, etc.
267
ZIO.effect {
268
logCustomMetrics(event)
269
eventHandler.handle(event)
270
}
271
}
272
} yield ()
273
}
274
275
private def logCustomMetrics(event: ZTestEvent): Unit = {
276
// Custom metric collection, external reporting, etc.
277
}
278
}
279
```
280
281
## Timing and Performance Information
282
283
Events include precise timing information extracted from ZIO's test annotations:
284
285
```scala
286
// Timing information comes from TestAnnotation.timing
287
val duration = annotations.get(TestAnnotation.timing).toMillis
288
289
// This enables:
290
// - Performance regression detection
291
// - Slow test identification
292
// - Build time optimization
293
// - Detailed timing reports in IDEs
294
```
295
296
The event system ensures that all timing information from ZIO's effect execution is preserved and made available to SBT's reporting infrastructure, enabling comprehensive test performance analysis.