All Downloads are FREE. Search and download functionalities are using the official Maven repository.

zio.test.sbt.ZTestRunnerJS.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2019-2024 John A. De Goes and the ZIO Contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package zio.test.sbt

import sbt.testing._
import zio.test.{FilteredSpec, Summary, TestArgs, TestEnvironment, TestLogger, ZIOSpecAbstract}
import zio.{Exit, Layer, Runtime, Scope, Trace, Unsafe, ZEnvironment, ZIO, ZIOAppArgs, ZLayer}

import scala.collection.mutable

sealed abstract class ZTestRunnerJS(
  val args: Array[String],
  val remoteArgs: Array[String],
  testClassLoader: ClassLoader,
  runnerType: String
) extends Runner {
  def sendSummary: SendSummary

  val summaries: mutable.Buffer[Summary] = mutable.Buffer.empty

  def done(): String = {
    val total  = summaries.map(_.total).sum
    val ignore = summaries.map(_.ignore).sum

    if (summaries.isEmpty || total == ignore)
      s"${Console.YELLOW}No tests were executed${Console.RESET}"
    else
      summaries
        .map(_.failureDetails)
        .filter(_.nonEmpty)
        .flatMap(s => colored(s) :: "\n" :: Nil)
        .mkString("", "", "Done")
  }

  def tasks(defs: Array[TaskDef]): Array[Task] =
    defs.map(ZTestTask(_, testClassLoader, runnerType, sendSummary, TestArgs.parse(args)))

  override def receiveMessage(summary: String): Option[String] = {
    SummaryProtocol.deserialize(summary).foreach(s => summaries += s)

    None
  }

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

  // This is what prevents us from utilizing merged Specs.
  // When we try to round trip, we only deserialize the first task, so all the others
  // that were merged in are lost.
  override def deserializeTask(task: String, deserializer: String => TaskDef): Task =
    ZTestTask(deserializer(task), testClassLoader, runnerType, sendSummary, TestArgs.parse(args))
}

final class ZMasterTestRunnerJS(args: Array[String], remoteArgs: Array[String], testClassLoader: ClassLoader)
    extends ZTestRunnerJS(args, remoteArgs, testClassLoader, "master") {

  //This implementation seems to be used when there's only single spec to run
  override val sendSummary: SendSummary = SendSummary.fromSend { summary =>
    summaries += summary
    ()
  }

}

final class ZSlaveTestRunnerJS(
  args: Array[String],
  remoteArgs: Array[String],
  testClassLoader: ClassLoader,
  val sendSummary: SendSummary
) extends ZTestRunnerJS(args, remoteArgs, testClassLoader, "slave") {}

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
    ) {

  def execute(eventHandler: EventHandler, loggers: Array[Logger], continuation: Array[Task] => Unit): Unit = {
    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))
            // TODO Confirm if/how these events needs to be handled in #6481
            //    Check XML behavior
            _ <- ZIO.when(summary.status == Summary.Failure) {
                   ZIO.attempt(
                     eventHandler.handle(
                       ZTestEvent(
                         taskDef.fullyQualifiedName(),
                         // taskDef.selectors() is "one to many" so we can expect nonEmpty here
                         taskDef.selectors().head,
                         Status.Failure,
                         None,
                         0L,
                         ZioSpecFingerprint
                       )
                     )
                   )
                 }
          } yield ())
            .provideLayer(
              sharedFilledTestLayer +!+ (Scope.default >>> spec.bootstrap)
            )
        }
      logic
    }(Trace.empty, Unsafe.unsafe)
    fiber.unsafe.addObserver { exit =>
      exit match {
        case Exit.Failure(cause) => Console.err.println(s"$runnerType failed. $cause")
        case _                   =>
      }
      continuation(Array())
    }(Unsafe.unsafe)
  }
}
object ZTestTask {
  def apply(
    taskDef: TaskDef,
    testClassLoader: ClassLoader,
    runnerType: String,
    sendSummary: SendSummary,
    args: TestArgs
  ): ZTestTask = {
    val zioSpec = disectTask(taskDef, testClassLoader)
    new ZTestTask(taskDef, testClassLoader, runnerType, sendSummary, args, zioSpec)
  }

  private def disectTask(taskDef: TaskDef, testClassLoader: ClassLoader): ZIOSpecAbstract = {
    import org.portablescala.reflect._
    val fqn = taskDef.fullyQualifiedName().stripSuffix("$") + "$"
    Reflect
      .lookupLoadableModuleClass(fqn, testClassLoader)
      .getOrElse(throw new ClassNotFoundException("failed to load object: " + fqn))
      .loadModule()
      .asInstanceOf[ZIOSpecAbstract]
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy