Skip to content

Commit c79a923

Browse files
authored
Merge pull request #7 from gzm0/structured-failure
Fix #6: Throw a structured exception when a command fails to start
2 parents 1262dbe + d562afd commit c79a923

File tree

3 files changed

+104
-2
lines changed

3 files changed

+104
-2
lines changed

build.sbt

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ val previousVersion: Option[String] = Some("1.1.1")
22
val newScalaBinaryVersionsInThisRelease: Set[String] = Set()
33

44
inThisBuild(Def.settings(
5-
version := "1.1.2-SNAPSHOT",
5+
version := "1.2.0-SNAPSHOT",
66
organization := "org.scala-js",
77
scalaVersion := "2.12.11",
88
crossScalaVersions := Seq("2.11.12", "2.12.11", "2.13.2"),

js-envs/src/main/scala/org/scalajs/jsenv/ExternalJSRun.scala

+10-1
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,18 @@ object ExternalJSRun {
140140

141141
config.logger.debug("Starting process: " + command.mkString(" "))
142142

143-
builder.start()
143+
try {
144+
builder.start()
145+
} catch {
146+
case NonFatal(t) =>
147+
throw new FailedToStartException(command, t)
148+
}
144149
}
145150

151+
final case class FailedToStartException(
152+
command: List[String], cause: Throwable)
153+
extends Exception(s"failed to start command $command", cause)
154+
146155
final case class NonZeroExitException(retVal: Int)
147156
extends Exception(s"exited with code $retVal")
148157

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Scala.js JS Envs (https://github.com/scala-js/scala-js-js-envs)
3+
*
4+
* Copyright EPFL.
5+
*
6+
* Licensed under Apache License 2.0
7+
* (https://www.apache.org/licenses/LICENSE-2.0).
8+
*
9+
* See the NOTICE file distributed with this work for
10+
* additional information regarding copyright ownership.
11+
*/
12+
13+
package org.scalajs.jsenv
14+
15+
import org.junit.Test
16+
import org.junit.Assert._
17+
18+
import scala.concurrent.{Await, Future}
19+
import scala.concurrent.duration._
20+
21+
import scala.util.Failure
22+
23+
class ExternalJSRunTest {
24+
25+
private def assertFails(future: Future[Unit])(
26+
pf: PartialFunction[Throwable, Unit]): Unit = {
27+
Await.ready(future, 1.seconds).value.get match {
28+
case Failure(t) if pf.isDefinedAt(t) => // OK
29+
30+
case result =>
31+
result.get
32+
fail("run succeeded unexpectedly")
33+
}
34+
}
35+
36+
private val silentConfig = {
37+
val runConfig = RunConfig()
38+
.withInheritOut(false)
39+
.withInheritErr(false)
40+
.withOnOutputStream((_, _) => ())
41+
42+
ExternalJSRun.Config()
43+
.withRunConfig(runConfig)
44+
}
45+
46+
@Test
47+
def nonExistentCommand: Unit = {
48+
val cmd = List("nonexistent-cmd")
49+
val run = ExternalJSRun.start(cmd, silentConfig) { _ =>
50+
fail("unexpected call to input")
51+
}
52+
53+
assertFails(run.future) {
54+
case ExternalJSRun.FailedToStartException(`cmd`, _) => // OK
55+
}
56+
}
57+
58+
@Test
59+
def failingCommand: Unit = {
60+
val run = ExternalJSRun.start(List("node", "non-existent-file.js"),
61+
silentConfig)(_.close())
62+
63+
assertFails(run.future) {
64+
case ExternalJSRun.NonZeroExitException(_) => // OK
65+
}
66+
}
67+
68+
@Test
69+
def abortedCommand: Unit = {
70+
val run = ExternalJSRun.start(List("node"), silentConfig) { _ =>
71+
// Do not close stdin so node keeps running
72+
}
73+
74+
run.close()
75+
76+
assertFails(run.future) {
77+
case ExternalJSRun.ClosedException() => // OK
78+
}
79+
}
80+
81+
@Test
82+
def abortedCommandOK: Unit = {
83+
val config = silentConfig
84+
.withClosingFails(false)
85+
86+
val run = ExternalJSRun.start(List("node"), config) { _ =>
87+
// Do not close stdin so node keeps running
88+
}
89+
90+
run.close()
91+
Await.result(run.future, 1.second)
92+
}
93+
}

0 commit comments

Comments
 (0)