or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

cross-platform.mdevent-handling.mdindex.mdsbt-framework.mdtest-runners.md

cross-platform.mddocs/

0

# Cross-Platform Support

1

2

This document covers ZIO Test SBT's support for JavaScript and Native platforms, including specialized runners and communication protocols.

3

4

## Platform Architecture

5

6

ZIO Test SBT supports three platforms with unified APIs but platform-specific optimizations:

7

8

- **JVM**: Full-featured implementation with signal handling and advanced runtime features

9

- **JavaScript (Scala.js)**: Asynchronous execution with master/slave runner patterns

10

- **Native (Scala Native)**: Optimized for native compilation with efficient concurrency primitives

11

12

## Summary Protocol

13

14

JavaScript and Native platforms use string-based communication for distributed testing scenarios.

15

16

```scala { .api }

17

object SummaryProtocol {

18

def serialize(summary: zio.test.Summary): String

19

def deserialize(s: String): Option[zio.test.Summary]

20

def escape(token: String): String

21

def unescape(token: String): String

22

}

23

```

24

25

### serialize

26

27

Converts a ZIO test summary to a serialized string format.

28

29

```scala

30

val summary = zio.test.Summary(success = 5, fail = 1, ignore = 2, failureDetails = "Test failed")

31

val serialized = SummaryProtocol.serialize(summary)

32

// Returns: "5\t1\t2\tTest failed" (tab-separated values)

33

```

34

35

**Format**: `success\tfail\tignore\tfailureDetails`

36

37

### deserialize

38

39

Parses a serialized summary string back to a `Summary` object.

40

41

```scala

42

val serialized = "5\t1\t2\tTest failed"

43

val summary = SummaryProtocol.deserialize(serialized)

44

// Returns: Some(Summary(5, 1, 2, "Test failed"))

45

```

46

47

**Returns**: `Some(Summary)` if parsing succeeds, `None` if format is invalid

48

49

### escape / unescape

50

51

Handle tab character escaping in summary strings.

52

53

```scala

54

val withTabs = "Message\twith\ttabs"

55

val escaped = SummaryProtocol.escape(withTabs)

56

// Returns: "Message\\twith\\ttabs"

57

58

val unescaped = SummaryProtocol.unescape(escaped)

59

// Returns: "Message\twith\ttabs"

60

```

61

62

## JavaScript Platform

63

64

### Framework Implementation

65

66

JavaScript implementation supports distributed testing with master/slave runners.

67

68

```scala { .api }

69

final class ZTestFramework extends sbt.testing.Framework {

70

override final val name: String

71

val fingerprints: Array[sbt.testing.Fingerprint]

72

73

override def runner(args: Array[String], remoteArgs: Array[String], testClassLoader: ClassLoader): sbt.testing.Runner

74

override def slaveRunner(args: Array[String], remoteArgs: Array[String], testClassLoader: ClassLoader, send: String => Unit): sbt.testing.Runner

75

}

76

```

77

78

### Master Runner

79

80

Manages single-process test execution.

81

82

```scala { .api }

83

final class ZMasterTestRunnerJS(

84

args: Array[String],

85

remoteArgs: Array[String],

86

testClassLoader: ClassLoader

87

) extends ZTestRunnerJS(args, remoteArgs, testClassLoader, "master") {

88

override val sendSummary: SendSummary

89

}

90

```

91

92

**Summary Handling**: Collects summaries in a mutable buffer for local aggregation.

93

94

```scala

95

override val sendSummary: SendSummary = SendSummary.fromSend { summary =>

96

summaries += summary

97

()

98

}

99

```

100

101

### Slave Runner

102

103

Handles distributed test execution with custom summary transmission.

104

105

```scala { .api }

106

final class ZSlaveTestRunnerJS(

107

args: Array[String],

108

remoteArgs: Array[String],

109

testClassLoader: ClassLoader,

110

val sendSummary: SendSummary

111

) extends ZTestRunnerJS(args, remoteArgs, testClassLoader, "slave")

112

```

113

114

**Summary Handling**: Uses provided `SendSummary` that typically serializes and sends summaries to master process.

115

116

### Base JavaScript Runner

117

118

```scala { .api }

119

sealed abstract class ZTestRunnerJS(

120

val args: Array[String],

121

val remoteArgs: Array[String],

122

testClassLoader: ClassLoader,

123

runnerType: String

124

) extends sbt.testing.Runner {

125

126

def sendSummary: SendSummary

127

val summaries: scala.collection.mutable.Buffer[zio.test.Summary]

128

129

def done(): String

130

def tasks(defs: Array[sbt.testing.TaskDef]): Array[sbt.testing.Task]

131

def receiveMessage(summary: String): Option[String]

132

def serializeTask(task: sbt.testing.Task, serializer: sbt.testing.TaskDef => String): String

133

def deserializeTask(task: String, deserializer: String => sbt.testing.TaskDef): sbt.testing.Task

134

}

135

```

136

137

#### receiveMessage

138

139

Handles incoming summary messages from distributed testing.

140

141

```scala

142

override def receiveMessage(summary: String): Option[String] = {

143

SummaryProtocol.deserialize(summary).foreach(s => summaries += s)

144

None

145

}

146

```

147

148

#### Task Serialization

149

150

```scala

151

override def serializeTask(task: sbt.testing.Task, serializer: sbt.testing.TaskDef => String): String =

152

serializer(task.taskDef())

153

154

override def deserializeTask(task: String, deserializer: String => sbt.testing.TaskDef): sbt.testing.Task =

155

ZTestTask(deserializer(task), testClassLoader, runnerType, sendSummary, TestArgs.parse(args))

156

```

157

158

### JavaScript Test Task

159

160

Asynchronous test execution with callback-based completion.

161

162

```scala { .api }

163

sealed class ZTestTask(

164

taskDef: sbt.testing.TaskDef,

165

testClassLoader: ClassLoader,

166

runnerType: String,

167

sendSummary: SendSummary,

168

testArgs: zio.test.TestArgs,

169

spec: zio.test.ZIOSpecAbstract

170

) extends BaseTestTask(taskDef, testClassLoader, sendSummary, testArgs, spec, zio.Runtime.default, zio.Console.ConsoleLive)

171

```

172

173

#### Asynchronous Execution

174

175

```scala

176

def execute(eventHandler: sbt.testing.EventHandler, loggers: Array[sbt.testing.Logger], continuation: Array[sbt.testing.Task] => Unit): Unit = {

177

val fiber = Runtime.default.unsafe.fork { /* test execution logic */ }

178

fiber.unsafe.addObserver { exit =>

179

exit match {

180

case Exit.Failure(cause) => Console.err.println(s"$runnerType failed. $cause")

181

case _ =>

182

}

183

continuation(Array())

184

}

185

}

186

```

187

188

## Native Platform

189

190

### Framework Implementation

191

192

Similar to JavaScript but optimized for native compilation.

193

194

```scala { .api }

195

final class ZTestFramework extends sbt.testing.Framework {

196

override def name(): String

197

override def fingerprints(): Array[sbt.testing.Fingerprint]

198

override def runner(args: Array[String], remoteArgs: Array[String], testClassLoader: ClassLoader): sbt.testing.Runner

199

override def slaveRunner(args: Array[String], remoteArgs: Array[String], testClassLoader: ClassLoader, send: String => Unit): sbt.testing.Runner

200

}

201

```

202

203

### Native Runners

204

205

```scala { .api }

206

final class ZMasterTestRunner(

207

args: Array[String],

208

remoteArgs: Array[String],

209

testClassLoader: ClassLoader

210

) extends ZTestRunnerNative(args, remoteArgs, testClassLoader, "master")

211

212

final class ZSlaveTestRunner(

213

args: Array[String],

214

remoteArgs: Array[String],

215

testClassLoader: ClassLoader,

216

sendSummary: SendSummary

217

) extends ZTestRunnerNative(args, remoteArgs, testClassLoader, "slave")

218

```

219

220

### Base Native Runner

221

222

Uses `ConcurrentLinkedQueue` for thread-safe summary collection.

223

224

```scala { .api }

225

sealed abstract class ZTestRunnerNative(

226

val args: Array[String],

227

remoteArgs0: Array[String],

228

testClassLoader: ClassLoader,

229

runnerType: String

230

) extends sbt.testing.Runner {

231

232

def remoteArgs(): Array[String]

233

val summaries: java.util.concurrent.ConcurrentLinkedQueue[zio.test.Summary]

234

235

def done(): String

236

}

237

```

238

239

#### Summary Collection

240

241

```scala

242

def done(): String = {

243

val log = new StringBuilder

244

var summary = summaries.poll()

245

var total = 0

246

var ignore = 0

247

val isEmpty = summary eq null

248

249

while (summary ne null) {

250

total += summary.total

251

ignore += summary.ignore

252

val details = summary.failureDetails

253

if (!details.isBlank) {

254

log append colored(details)

255

log append '\n'

256

}

257

summary = summaries.poll()

258

}

259

260

if (isEmpty || total == ignore)

261

s"${Console.YELLOW}No tests were executed${Console.RESET}"

262

else

263

log.append("Done").result()

264

}

265

```

266

267

### Native Test Task

268

269

Blocking execution model suitable for native compilation.

270

271

```scala { .api }

272

sealed class ZTestTask(

273

taskDef: sbt.testing.TaskDef,

274

testClassLoader: ClassLoader,

275

runnerType: String,

276

sendSummary: SendSummary,

277

testArgs: zio.test.TestArgs,

278

spec: zio.test.ZIOSpecAbstract

279

) extends BaseTestTask(taskDef, testClassLoader, sendSummary, testArgs, spec, zio.Runtime.default, zio.Console.ConsoleLive)

280

```

281

282

#### Blocking Execution

283

284

```scala

285

override def execute(eventHandler: sbt.testing.EventHandler, loggers: Array[sbt.testing.Logger]): Array[sbt.testing.Task] = {

286

var resOutter: CancelableFuture[Unit] = null

287

try {

288

resOutter = Runtime.default.unsafe.runToFuture { /* test execution logic */ }

289

Await.result(resOutter, Duration.Inf)

290

Array()

291

} catch {

292

case t: Throwable =>

293

if (resOutter != null) resOutter.cancel()

294

throw t

295

}

296

}

297

```

298

299

## Platform-Specific Optimizations

300

301

### JVM Platform

302

- **Signal Handling**: SIGINFO/SIGUSR1 for fiber dumping

303

- **Advanced Runtime**: Full ZIO runtime with all features

304

- **Parallel Execution**: Thread-based parallelism

305

- **Memory Management**: JVM garbage collection

306

307

### JavaScript Platform

308

- **Asynchronous Execution**: Callback-based completion

309

- **Single-Threaded**: Event loop concurrency

310

- **Master/Slave Pattern**: Distributed testing support

311

- **Lightweight Runtime**: Reduced ZIO runtime for browser/Node.js

312

313

### Native Platform

314

- **Optimized Runtime**: Minimal ZIO runtime for native compilation

315

- **Blocking Execution**: Synchronous execution model

316

- **Memory Efficiency**: Manual memory management optimizations

317

- **Fast Startup**: Ahead-of-time compilation benefits

318

319

## Usage Examples

320

321

### Platform Detection

322

323

```scala

324

// Detect current platform

325

val platform = scala.util.Properties.propOrEmpty("java.specification.name") match {

326

case name if name.contains("Java") => "JVM"

327

case _ =>

328

if (scala.util.Properties.propOrEmpty("scala.scalajs.runtime.name").nonEmpty) "JavaScript"

329

else "Native"

330

}

331

332

println(s"Running on: $platform")

333

```

334

335

### Cross-Platform Runner Configuration

336

337

```scala

338

import zio.test.sbt._

339

340

// Create appropriate runner for platform

341

val runner: sbt.testing.Runner = platform match {

342

case "JVM" => new ZTestRunnerJVM(args, remoteArgs, classLoader)

343

case "JavaScript" => new ZMasterTestRunnerJS(args, remoteArgs, classLoader)

344

case "Native" => new ZMasterTestRunner(args, remoteArgs, classLoader)

345

}

346

```

347

348

### Summary Protocol Usage

349

350

```scala

351

// Master process: serialize and send summary

352

val summary = zio.test.Summary(success = 10, fail = 2, ignore = 1, failureDetails = "Some tests failed")

353

val serialized = SummaryProtocol.serialize(summary)

354

sendToSlave(serialized)

355

356

// Slave process: receive and deserialize summary

357

def receiveSummary(data: String): Unit = {

358

SummaryProtocol.deserialize(data) match {

359

case Some(summary) =>

360

println(s"Received: ${summary.total} tests, ${summary.fail} failures")

361

case None =>

362

println("Invalid summary format")

363

}

364

}

365

```