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

org.scalajs.testinterface.internal.Slave.scala Maven / Gradle / Ivy

There is a newer version: 1.0.0-M2
Show newest version
package org.scalajs.testinterface.internal

import scala.scalajs.js
import js.annotation.JSExport

import sbt.testing._

import scala.collection.mutable

import scala.util.control.NonFatal
import scala.util.{Try, Success, Failure}

import org.scalajs.testinterface.ScalaJSClassLoader

@JSExport
final class Slave(frameworkName: String, args: js.Array[String],
    remoteArgs: js.Array[String]) extends BridgeBase(frameworkName) {

  // State

  private[this] var canSendRunnerMessage = false

  /** Queue for messages from the slave runner to the master runner */
  private[this] val messageQueue = mutable.Queue.empty[String]

  private[this] var runner: Runner = _

  protected def handleMsgImpl(cmd: String, strArg: => String): Unit = {
    def jsonArg = js.JSON.parse(strArg)
    allowSendRunnerMessage {
      cmd match {
        case "newRunner" =>
          reply(newRunner())
        case "execute" =>
          // No reply here. execute is async
          execute(jsonArg)
        case "stopSlave" =>
          reply(stopSlave())
        case "msg" =>
          val res = incomingRunnerMessage(strArg)
          // Only reply if something failed
          if (res.isFailure)
            reply(res)
        case cmd =>
          throw new IllegalArgumentException(s"Unknown command: $cmd")
      }
    }
  }

  // Runner message handler methods

  private def outboundRunnerMessage(msg: String): Unit =
    if (canSendRunnerMessage) sendOutboundRunnerMessage(msg)
    else messageQueue.enqueue(msg)

  private def sendOutboundRunnerMessage(msg: String): Unit = {
    assert(canSendRunnerMessage)
    Com.send(s"msg:$msg")
  }

  private def allowSendRunnerMessage[T](body: => T): T = {
    try {
      canSendRunnerMessage = true

      // Flush the queue
      while (!messageQueue.isEmpty)
        sendOutboundRunnerMessage(messageQueue.dequeue)

      body
    } finally {
      canSendRunnerMessage = false
    }
  }

  // Message handler methods

  private def newRunner(): Try[Unit] = {
    val loader = new ScalaJSClassLoader(js.Dynamic.global)
    Try(runner = framework.slaveRunner(args.toArray, remoteArgs.toArray,
        loader, outboundRunnerMessage))
  }

  private def execute(data: js.Dynamic): Unit = {
    ensureRunnerExists()

    val sTask = data.serializedTask.asInstanceOf[String]
    val task = runner.deserializeTask(sTask, str =>
      TaskDefSerializer.deserialize(js.JSON.parse(str)))

    val eventHandler = new RemoteEventHandler

    val colorSupport = data.loggerColorSupport.asInstanceOf[js.Array[Boolean]]
    val loggers = for {
      (withColor, i) <- colorSupport.zipWithIndex
    } yield new RemoteLogger(i, withColor)

    def cont(tasks: Array[Task]) = {
      val result = Try(js.JSON.stringify(tasks2TaskInfos(tasks, runner)))
      eventHandler.invalidate()
      loggers.foreach(_.invalidate())
      reply(result)
    }

    val launched = Try(task.execute(eventHandler, loggers.toArray, cont))

    if (launched.isFailure)
      reply(launched)
  }

  private def stopSlave(): Try[Unit] = {
    ensureRunnerExists()
    val res = Try { runner.done(); () }
    runner = null
    res
  }

  private def incomingRunnerMessage(msg: String): Try[Unit] = {
    ensureRunnerExists()
    Try { runner.receiveMessage(msg); () }
  }

  // Private helper classes

  private abstract class Invalidatable {
    private[this] var valid = true

    private[Slave] def invalidate(): Unit = valid = false

    protected def ensureValid(): Unit =
      if (!valid) throw new IllegalStateException(s"$this has been invalidated")
  }

  private class RemoteEventHandler extends Invalidatable with EventHandler {
    def handle(event: Event): Unit = {
      ensureValid()
      val serEvent = EventSerializer.serialize(event)
      Com.send("event:" + js.JSON.stringify(serEvent))
    }
  }

  private class RemoteLogger(index: Int,
      val ansiCodesSupported: Boolean) extends Invalidatable with Logger {

    def error(msg: String): Unit = send("error", msg)
    def warn(msg: String): Unit = send("warn", msg)
    def info(msg: String): Unit = send("info", msg)
    def debug(msg: String): Unit = send("debug", msg)

    def trace(t: Throwable): Unit =
      send("trace", js.JSON.stringify(ThrowableSerializer.serialize(t)))

    private def send(cmd: String, data: String): Unit = {
      ensureValid()
      Com.send(s"$cmd:$index:$data")
    }
  }

  // Utility methods

  private def ensureRunnerExists(): Unit = {
    if (runner == null)
      throw new IllegalStateException("No runner created")
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy