From 1debcebb7b84574c6c64c24b8f55d6191a1d213e Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sat, 10 Jul 2021 10:28:20 +0200 Subject: [PATCH 1/2] Bump minor version for upcoming functionality changes --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 2d3b688..315d041 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ val previousVersion: Option[String] = Some("1.1.1") val newScalaBinaryVersionsInThisRelease: Set[String] = Set() inThisBuild(Def.settings( - version := "1.1.2-SNAPSHOT", + version := "1.2.0-SNAPSHOT", organization := "org.scala-js", scalaVersion := "2.12.11", crossScalaVersions := Seq("2.11.12", "2.12.11", "2.13.2"), From d562afd3c100f07e5aba20cce2e97b4fba803885 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sat, 10 Jul 2021 10:28:39 +0200 Subject: [PATCH 2/2] Fix #6: Throw a structured exception when a command fails to start --- .../org/scalajs/jsenv/ExternalJSRun.scala | 11 ++- .../org/scalajs/jsenv/ExternalJSRunTest.scala | 93 +++++++++++++++++++ 2 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 js-envs/src/test/scala/org/scalajs/jsenv/ExternalJSRunTest.scala diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/ExternalJSRun.scala b/js-envs/src/main/scala/org/scalajs/jsenv/ExternalJSRun.scala index ae40578..a519d9b 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/ExternalJSRun.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/ExternalJSRun.scala @@ -140,9 +140,18 @@ object ExternalJSRun { config.logger.debug("Starting process: " + command.mkString(" ")) - builder.start() + try { + builder.start() + } catch { + case NonFatal(t) => + throw new FailedToStartException(command, t) + } } + final case class FailedToStartException( + command: List[String], cause: Throwable) + extends Exception(s"failed to start command $command", cause) + final case class NonZeroExitException(retVal: Int) extends Exception(s"exited with code $retVal") diff --git a/js-envs/src/test/scala/org/scalajs/jsenv/ExternalJSRunTest.scala b/js-envs/src/test/scala/org/scalajs/jsenv/ExternalJSRunTest.scala new file mode 100644 index 0000000..6e81e07 --- /dev/null +++ b/js-envs/src/test/scala/org/scalajs/jsenv/ExternalJSRunTest.scala @@ -0,0 +1,93 @@ +/* + * Scala.js JS Envs (https://github.com/scala-js/scala-js-js-envs) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.jsenv + +import org.junit.Test +import org.junit.Assert._ + +import scala.concurrent.{Await, Future} +import scala.concurrent.duration._ + +import scala.util.Failure + +class ExternalJSRunTest { + + private def assertFails(future: Future[Unit])( + pf: PartialFunction[Throwable, Unit]): Unit = { + Await.ready(future, 1.seconds).value.get match { + case Failure(t) if pf.isDefinedAt(t) => // OK + + case result => + result.get + fail("run succeeded unexpectedly") + } + } + + private val silentConfig = { + val runConfig = RunConfig() + .withInheritOut(false) + .withInheritErr(false) + .withOnOutputStream((_, _) => ()) + + ExternalJSRun.Config() + .withRunConfig(runConfig) + } + + @Test + def nonExistentCommand: Unit = { + val cmd = List("nonexistent-cmd") + val run = ExternalJSRun.start(cmd, silentConfig) { _ => + fail("unexpected call to input") + } + + assertFails(run.future) { + case ExternalJSRun.FailedToStartException(`cmd`, _) => // OK + } + } + + @Test + def failingCommand: Unit = { + val run = ExternalJSRun.start(List("node", "non-existent-file.js"), + silentConfig)(_.close()) + + assertFails(run.future) { + case ExternalJSRun.NonZeroExitException(_) => // OK + } + } + + @Test + def abortedCommand: Unit = { + val run = ExternalJSRun.start(List("node"), silentConfig) { _ => + // Do not close stdin so node keeps running + } + + run.close() + + assertFails(run.future) { + case ExternalJSRun.ClosedException() => // OK + } + } + + @Test + def abortedCommandOK: Unit = { + val config = silentConfig + .withClosingFails(false) + + val run = ExternalJSRun.start(List("node"), config) { _ => + // Do not close stdin so node keeps running + } + + run.close() + Await.result(run.future, 1.second) + } +}