ZIO Test SBT Framework integration that provides test interface implementation for running ZIO-based tests within SBT build environments
—
Lightweight JavaScript test runner with master/slave architecture for distributed test execution and inter-process communication through serialized summaries. Optimized for Scala.js compilation and browser/Node.js environments.
Abstract base class for JavaScript test runners that provides common functionality for both master and slave execution modes.
/**
* Base JavaScript test runner with master/slave architecture support
* Provides lightweight execution optimized for JavaScript environments
*/
sealed abstract class ZTestRunnerJS(
val args: Array[String],
val remoteArgs: Array[String],
testClassLoader: ClassLoader,
runnerType: String
) extends Runner {
/** Abstract method for summary handling strategy */
def sendSummary: SendSummary
/** Mutable buffer for collecting test summaries */
val summaries: mutable.Buffer[Summary]
/**
* Create test tasks from discovered test definitions
* @param defs Array of SBT task definitions
* @return Array of executable JavaScript test tasks
*/
def tasks(defs: Array[TaskDef]): Array[Task]
/**
* Complete test execution and return formatted results
* @return Formatted test results or "No tests executed" message
*/
def done(): String
/**
* Receive serialized summaries from distributed execution
* @param summary Serialized summary string from SummaryProtocol
* @return Optional response message (always None for JS)
*/
override def receiveMessage(summary: String): Option[String]
/**
* Serialize task for distributed execution
* @param task Task to serialize
* @param serializer SBT-provided serialization function
* @return Serialized task string
*/
override def serializeTask(task: Task, serializer: TaskDef => String): String
/**
* Deserialize task from distributed execution
* @param task Serialized task string
* @param deserializer SBT-provided deserialization function
* @return Reconstructed test task
*/
override def deserializeTask(task: String, deserializer: String => TaskDef): Task
}Master runner for single-process JavaScript execution, typically used when running tests in a single JavaScript context.
/**
* Master JavaScript test runner for single-process execution
* Collects summaries directly without inter-process communication
*/
final class ZMasterTestRunnerJS(
args: Array[String],
remoteArgs: Array[String],
testClassLoader: ClassLoader
) extends ZTestRunnerJS(args, remoteArgs, testClassLoader, "master") {
/**
* Summary sender that collects results locally
* Used when all tests run in the same JavaScript context
*/
override val sendSummary: SendSummary
}Slave runner for distributed JavaScript execution, used when tests are executed across multiple JavaScript contexts or processes.
/**
* Slave JavaScript test runner for distributed execution
* Sends summaries to master process via serialization protocol
*/
final class ZSlaveTestRunnerJS(
args: Array[String],
remoteArgs: Array[String],
testClassLoader: ClassLoader,
val sendSummary: SendSummary
) extends ZTestRunnerJS(args, remoteArgs, testClassLoader, "slave")JavaScript-specific test task implementation with asynchronous execution support.
/**
* JavaScript-specific test task with async execution
* Optimized for JavaScript event loop and browser/Node.js environments
*/
sealed class ZTestTask(
taskDef: TaskDef,
testClassLoader: ClassLoader,
runnerType: String,
sendSummary: SendSummary,
testArgs: TestArgs,
spec: ZIOSpecAbstract
) extends BaseTestTask(
taskDef, testClassLoader, sendSummary, testArgs, spec,
Runtime.default, zio.Console.ConsoleLive
) {
/**
* Execute test with async continuation support
* Required for JavaScript's asynchronous execution model
* @param eventHandler SBT event handler for reporting
* @param loggers Array of SBT loggers
* @param continuation Continuation function called when execution completes
*/
def execute(eventHandler: EventHandler, loggers: Array[Logger], continuation: Array[Task] => Unit): Unit
}Factory methods for creating JavaScript test tasks.
object ZTestTask {
/**
* Create a JavaScript test task instance
* @param taskDef SBT task definition
* @param testClassLoader ClassLoader for loading test classes
* @param runnerType Runner type identifier ("master" or "slave")
* @param sendSummary Summary collection effect
* @param args Parsed test arguments
* @return Configured JavaScript ZTestTask instance
*/
def apply(
taskDef: TaskDef,
testClassLoader: ClassLoader,
runnerType: String,
sendSummary: SendSummary,
args: TestArgs
): ZTestTask
}Serialization protocol for inter-process communication in distributed JavaScript execution.
/**
* Serialization protocol for test summaries in JavaScript environments
* Enables communication between master and slave processes
*/
object SummaryProtocol {
/**
* Serialize test summary to string for transmission
* @param summary Test execution summary to serialize
* @return Tab-separated string representation
*/
def serialize(summary: Summary): String
/**
* Deserialize string back to test summary
* @param s Serialized summary string
* @return Optional Summary if deserialization succeeds
*/
def deserialize(s: String): Option[Summary]
/**
* Escape tab characters in tokens for serialization
* @param token String token to escape
* @return Escaped string safe for tab-separated format
*/
def escape(token: String): String
/**
* Unescape tab characters from serialized tokens
* @param token Escaped string token
* @return Unescaped original string
*/
def unescape(token: String): String
}JavaScript test tasks use asynchronous execution with continuations:
def execute(eventHandler: EventHandler, loggers: Array[Logger], continuation: Array[Task] => Unit): Unit = {
// Fork execution on default runtime
val fiber = Runtime.default.unsafe.fork {
val logic = ZIO.consoleWith { console =>
(for {
summary <- spec.runSpecAsApp(FilteredSpec(spec.spec, args), args, console)
_ <- sendSummary.provide(ZLayer.succeed(summary))
_ <- ZIO.when(summary.status == Summary.Failure) {
ZIO.attempt(eventHandler.handle(ZTestEvent(/* failure event */)))
}
} yield ()).provideLayer(sharedFilledTestLayer +!+ (Scope.default >>> spec.bootstrap))
}
logic
}
// Add observer for completion handling
fiber.unsafe.addObserver { exit =>
exit match {
case Exit.Failure(cause) => Console.err.println(s"$runnerType failed. $cause")
case _ =>
}
continuation(Array())
}
}Master Process Flow:
Slave Process Flow:
SummaryProtocolsend functionJavaScript platform provides several optimizations:
// Framework automatically creates appropriate runner type
val framework = new ZTestFramework()
// For single-process execution
val masterRunner = framework.runner(args, remoteArgs, classLoader)
// For distributed execution
val slaveRunner = framework.slaveRunner(args, remoteArgs, classLoader, summaryCallback)
// SBT handles task serialization/deserialization automatically
val serializedTask = masterRunner.serializeTask(task, serializer)
val deserializedTask = slaveRunner.deserializeTask(serializedTask, deserializer)Install with Tessl CLI
npx tessl i tessl/maven-dev-zio--zio-test-sbt-3