0
# Cross-Platform Communication
1
2
The cross-platform communication system handles test result transmission across different platforms, with serialization support for JavaScript and Native platforms. It provides a unified interface for summary transmission while handling the platform-specific requirements for inter-process communication.
3
4
## Capabilities
5
6
### SendSummary Type Alias
7
8
Type alias that represents a ZIO effect for transmitting test summaries. It provides a consistent interface for summary transmission across all platforms.
9
10
```scala { .api }
11
/**
12
* ZIO effect for transmitting test summaries
13
* Provides consistent interface across all platforms
14
*/
15
type SendSummary = zio.URIO[zio.test.Summary, Unit]
16
```
17
18
This type alias encapsulates the pattern of taking a `Summary` and performing some transmission action (logging, sending to master process, etc.) without the possibility of failure.
19
20
### SendSummary Companion Object
21
22
Factory methods for creating `SendSummary` instances from different types of transmission functions.
23
24
```scala { .api }
25
object SendSummary {
26
/**
27
* Creates SendSummary from synchronous function
28
* @param send Function that processes summaries synchronously
29
* @return SendSummary effect that executes the function
30
*/
31
def fromSend(send: zio.test.Summary => Unit): SendSummary
32
33
/**
34
* Creates SendSummary from ZIO effect function
35
* @param send Function that returns a ZIO effect for processing summaries
36
* @return SendSummary effect that chains the provided effect
37
*/
38
def fromSendM(send: zio.test.Summary => zio.UIO[Unit]): SendSummary
39
40
/**
41
* No-operation SendSummary that discards summaries
42
* @return SendSummary that performs no action
43
*/
44
def noop: SendSummary
45
}
46
```
47
48
**Usage Examples:**
49
50
```scala
51
// Simple logging example
52
val loggingSummary = SendSummary.fromSend { summary =>
53
println(s"Test completed: ${summary.success} passed, ${summary.fail} failed")
54
}
55
56
// ZIO effect example
57
val asyncSummary = SendSummary.fromSendM { summary =>
58
ZIO.effect {
59
// Send to external system, write to database, etc.
60
externalReporter.report(summary)
61
}.catchAll(_ => ZIO.unit) // Never fail
62
}
63
64
// No-op for testing
65
val testSummary = SendSummary.noop
66
```
67
68
**Platform-Specific Implementations:**
69
70
**JVM Platform:**
71
```scala
72
// JVM runner collects summaries in AtomicReference
73
val sendSummary: SendSummary = SendSummary.fromSendM(summary =>
74
ZIO.effectTotal {
75
summaries.updateAndGet(_ :+ summary)
76
()
77
}
78
)
79
```
80
81
**JavaScript/Native Master:**
82
```scala
83
// Master runner collects summaries in mutable buffer
84
val sendSummary: SendSummary = SendSummary.fromSend { summary =>
85
summaries += summary
86
()
87
}
88
```
89
90
**JavaScript/Native Slave:**
91
```scala
92
// Slave runner serializes and transmits to master
93
val sendSummary: SendSummary = SendSummary.fromSend(summary =>
94
send(SummaryProtocol.serialize(summary))
95
)
96
```
97
98
### SummaryProtocol
99
100
Serialization protocol for transmitting test summaries across process boundaries on JavaScript and Native platforms. It handles the conversion between structured `Summary` objects and string representations.
101
102
```scala { .api }
103
/**
104
* Serialization protocol for test summaries
105
* Enables inter-process communication on JS/Native platforms
106
*/
107
object SummaryProtocol {
108
/**
109
* Serializes Summary to tab-delimited string
110
* @param summary Summary object to serialize
111
* @return Tab-delimited string representation
112
*/
113
def serialize(summary: zio.test.Summary): String
114
115
/**
116
* Deserializes string back to Summary object
117
* @param s Tab-delimited string from serialize()
118
* @return Some(Summary) if valid, None if malformed
119
*/
120
def deserialize(s: String): Option[zio.test.Summary]
121
122
/**
123
* Escapes tab characters in token strings
124
* @param token String token to escape
125
* @return String with tabs escaped as \\t
126
*/
127
def escape(token: String): String
128
129
/**
130
* Unescapes tab characters in token strings
131
* @param token String token to unescape
132
* @return String with \\t unescaped as tabs
133
*/
134
def unescape(token: String): String
135
}
136
```
137
138
**Serialization Format:**
139
140
The protocol uses tab-delimited format for simplicity and reliability:
141
142
```
143
success_count\tfail_count\tignore_count\tsummary_text
144
```
145
146
Where each field is escaped to handle tabs in the summary text.
147
148
**Usage Examples:**
149
150
```scala
151
import zio.test.Summary
152
import zio.test.sbt.SummaryProtocol
153
154
// Create a summary
155
val summary = Summary(
156
success = 10,
157
fail = 2,
158
ignore = 1,
159
summary = "Test run completed\nSome tests failed"
160
)
161
162
// Serialize for transmission
163
val serialized = SummaryProtocol.serialize(summary)
164
// Result: "10\t2\t1\tTest run completed\\nSome tests failed"
165
166
// Deserialize on receiving side
167
val received = SummaryProtocol.deserialize(serialized)
168
// Result: Some(Summary(10, 2, 1, "Test run completed\nSome tests failed"))
169
170
// Handle malformed data gracefully
171
val malformed = SummaryProtocol.deserialize("invalid")
172
// Result: None
173
```
174
175
**Error Handling:**
176
177
The protocol provides robust error handling for malformed data:
178
179
```scala
180
// Valid format returns Some(Summary)
181
SummaryProtocol.deserialize("5\t1\t0\tAll tests passed")
182
// = Some(Summary(5, 1, 0, "All tests passed"))
183
184
// Invalid formats return None
185
SummaryProtocol.deserialize("not-enough-fields") // = None
186
SummaryProtocol.deserialize("5\tnot-a-number\t0\ttest") // = None (NumberFormatException)
187
SummaryProtocol.deserialize("") // = None
188
```
189
190
## Platform Integration Patterns
191
192
### JVM Platform Communication
193
194
JVM platform uses direct in-memory communication since everything runs in the same process:
195
196
```scala
197
class ZTestRunner(/* ... */) extends sbt.testing.Runner {
198
val summaries: AtomicReference[Vector[Summary]] = new AtomicReference(Vector.empty)
199
200
val sendSummary: SendSummary = SendSummary.fromSendM(summary =>
201
ZIO.effectTotal {
202
summaries.updateAndGet(_ :+ summary) // Thread-safe collection
203
()
204
}
205
)
206
207
def done(): String = {
208
val allSummaries = summaries.get
209
// Process summaries for final report
210
formatSummaries(allSummaries)
211
}
212
}
213
```
214
215
### JavaScript/Native Platform Communication
216
217
JavaScript and Native platforms support distributed execution with serialization:
218
219
```scala
220
class ZSlaveTestRunner(/* ... */, send: String => Unit) extends ZTestRunner {
221
// Serializes summaries and sends to master process
222
val sendSummary: SendSummary = SendSummary.fromSend(summary =>
223
send(SummaryProtocol.serialize(summary))
224
)
225
}
226
227
class ZMasterTestRunner(/* ... */) extends ZTestRunner {
228
val summaries: mutable.Buffer[Summary] = mutable.Buffer.empty
229
230
// Receives serialized summaries from slave processes
231
override def receiveMessage(summary: String): Option[String] = {
232
SummaryProtocol.deserialize(summary).foreach(s => summaries += s)
233
None // No response needed
234
}
235
236
// Also handles local execution
237
val sendSummary: SendSummary = SendSummary.fromSend { summary =>
238
summaries += summary
239
()
240
}
241
}
242
```
243
244
### Task Serialization Support
245
246
The runners also support task serialization for distributed execution:
247
248
```scala
249
// Serialize task for sending to slave process
250
def serializeTask(task: Task, serializer: TaskDef => String): String = {
251
serializer(task.taskDef())
252
}
253
254
// Deserialize task received from master process
255
def deserializeTask(task: String, deserializer: String => TaskDef): Task = {
256
new ZTestTask(deserializer(task), testClassLoader, runnerType, sendSummary, TestArgs.parse(args))
257
}
258
```
259
260
## Advanced Communication Patterns
261
262
### Custom Summary Processing
263
264
You can implement custom summary processing by creating custom `SendSummary` instances:
265
266
```scala
267
// Custom summary processor with external reporting
268
val customSendSummary = SendSummary.fromSendM { summary =>
269
for {
270
_ <- ZIO.effect(logToFile(summary)) // Log to file
271
_ <- ZIO.effect(sendToMetrics(summary)) // Send to metrics system
272
_ <- ZIO.effect(updateDatabase(summary)) // Update test database
273
} yield ()
274
}
275
276
// Error-resilient version
277
val resilientSendSummary = SendSummary.fromSendM { summary =>
278
ZIO.effect(processExternally(summary))
279
.catchAll(error => ZIO.effect(println(s"Summary processing failed: $error")))
280
.as(()) // Always succeeds
281
}
282
```
283
284
### Multi-Platform Summary Collection
285
286
For applications that need to collect summaries across multiple platforms:
287
288
```scala
289
// Centralized summary collector
290
class SummaryCollector {
291
private val summaries = new ConcurrentLinkedQueue[Summary]()
292
293
def createSendSummary(): SendSummary = SendSummary.fromSend { summary =>
294
summaries.offer(summary)
295
()
296
}
297
298
def getAllSummaries(): List[Summary] = summaries.asScala.toList
299
300
def getAggregatedSummary(): Summary = {
301
val all = getAllSummaries()
302
Summary(
303
success = all.map(_.success).sum,
304
fail = all.map(_.fail).sum,
305
ignore = all.map(_.ignore).sum,
306
summary = all.map(_.summary).filter(_.nonEmpty).mkString("\n")
307
)
308
}
309
}
310
311
// Usage across platforms
312
val collector = new SummaryCollector()
313
val sendSummary = collector.createSendSummary()
314
// Use sendSummary in test runners across all platforms
315
```
316
317
## Utility Functions
318
319
### Colored Output
320
321
Utility function for handling ANSI color codes in test output across different platforms.
322
323
```scala { .api }
324
/**
325
* Inserts ANSI escape codes at the beginning of each line
326
* Ensures colored output displays correctly in SBT test loggers
327
* @param s String potentially containing ANSI color codes
328
* @return String with color codes properly positioned for multi-line output
329
*/
330
private[sbt] def colored(s: String): String
331
```
332
333
**Usage:**
334
335
```scala
336
// Used internally by test runners for formatted output
337
val coloredSummary = colored(summary.summary)
338
println(coloredSummary) // Maintains colors across multiple lines
339
```
340
341
The `colored` function ensures that ANSI color codes are properly maintained when displaying multi-line test output, which is essential for readable test reports with syntax highlighting and status indicators.
342
343
The communication system ensures reliable test result transmission while maintaining platform abstraction and providing flexibility for custom integration patterns.