JVM-specific test runner implementation with advanced features including signal handling, shared runtime management, and comprehensive test lifecycle control. The JVM platform provides the most feature-rich implementation of ZIO Test SBT integration.
Main JVM test runner that manages the complete test execution lifecycle, including runtime creation, task coordination, and result aggregation.
/**
* JVM-specific test runner with advanced lifecycle management
* Extends sbt.testing.Runner
*/
class ZTestRunnerJVM(
val args: Array[String],
val remoteArgs: Array[String],
testClassLoader: ClassLoader
) extends Runner {
/** Current test renderer (mutable, updated based on test args) */
@volatile var renderer: TestRenderer
/** Optional cleanup hook for runtime shutdown */
@volatile var shutdownHook: Option[() => Unit]
/** Thread-safe collection of test execution summaries */
val summaries: AtomicReference[Vector[Summary]]
/** Creates test tasks from SBT task definitions */
def tasks(defs: Array[TaskDef]): Array[Task]
/** Returns final aggregated test results and performs cleanup */
def done(): String
/** Internal task creation with ZIO Console integration */
def tasksZ(
defs: Array[TaskDef],
console: zio.Console
): Array[ZTestTask[TestOutput]]
}Usage Example:
The runner is created automatically by the framework, but you can observe its behavior:
// The runner processes all discovered test classes
// and creates individual tasks for execution
sbt test // Triggers runner.tasks() -> task.execute() -> runner.done()JVM-specific test task implementation that extends the base task with signal handling capabilities for debugging and monitoring.
/**
* JVM test task with signal handling support
* Extends BaseTestTask with JVM-specific features
*/
class ZTestTask[T](
taskDef: TaskDef,
testClassLoader: ClassLoader,
sendSummary: SendSummary,
testArgs: TestArgs,
spec: ZIOSpecAbstract,
runtime: zio.Runtime[T],
console: zio.Console
) extends BaseTestTask(
taskDef, testClassLoader, sendSummary, testArgs, spec, runtime, console
) {
/** Enhanced test execution with signal handler installation */
private[zio] def run(eventHandlerZ: ZTestEventHandler)(implicit trace: Trace): ZIO[Any, Throwable, Unit]
/** Installs signal handlers for fiber debugging (JVM-specific) */
private def installSignalHandlers()(implicit trace: Trace): Unit
}Signal Handling:
The JVM implementation installs signal handlers for debugging:
INT signal dumps fiber informationINFO and USR1 signals dump fiber information# Send signal to dump running fibers (Unix/Linux)
kill -USR1 <pid>
# On Windows, Ctrl+C triggers fiber dumpThe JVM runner creates sophisticated runtime environments with proper resource management:
/**
* Creates scoped runtime with shared layers and proper cleanup
*/
val runtime: Runtime.Scoped[TestOutput] =
zio.Runtime.unsafe.fromLayer(sharedLayer)(Trace.empty, Unsafe.unsafe)
val shutdownHook: Option[() => Unit] =
Some(() => runtime.unsafe.shutdown()(Unsafe.unsafe))Resource Lifecycle:
ZLayer combining all test spec bootstrap layersThe JVM runner processes and applies test arguments for filtering and configuration:
/**
* Processes command-line arguments into structured TestArgs
*/
val testArgs: TestArgs = TestArgs.parse(args)
// Updates renderer based on arguments
renderer = testArgs.testRenderer
// Creates event printer layer with specified renderer
val sharedTestOutputLayer =
ExecutionEventPrinter.live(console, testArgs.testEventRenderer) >>>
TestOutput.liveCommon Test Arguments:
# Run tests with specific renderer
sbt "test -- --format pretty"
# Run tests matching pattern
sbt "test -- --grep 'user login'"
# Run tests with tags
sbt "test -- --tags integration"The JVM runner collects and aggregates test results with comprehensive reporting:
/**
* Thread-safe summary collection
*/
val summaries: AtomicReference[Vector[Summary]] =
new AtomicReference(Vector.empty)
def sendSummary: SendSummary = SendSummary.fromSendZIO { summary =>
ZIO.succeed {
summaries.updateAndGet(_ :+ summary)
()
}
}
/**
* Generates final test report with colored output
*/
def done(): String = {
val allSummaries = summaries.get
val total = allSummaries.map(_.total).sum
val ignore = allSummaries.map(_.ignore).sum
val compositeSummary = allSummaries.foldLeft(Summary.empty)(_.add(_))
val renderedSummary = renderer.renderSummary(compositeSummary)
// Returns colored summary or appropriate message for no tests
if (allSummaries.nonEmpty && total != ignore)
colored(renderedSummary)
else if (ignore > 0)
s"${Console.YELLOW}All eligible tests are currently ignored${Console.RESET}"
else
s"${Console.YELLOW}No tests were executed${Console.RESET}"
}The JVM implementation includes several optimizations specific to the JVM platform: