From 9702d3d3186ae70c0713276e3d98cbac01fe1dc0 Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Mon, 28 Jan 2013 17:14:53 -0500 Subject: [PATCH] Handle exceptions not caught by the test framework when forking. Fixes #653. --- .../sbt-test/tests/fork-uncaught/build.sbt | 10 +++ .../fork-uncaught/src/main/scala/Foo.scala | 3 + .../src/test/scala/FooTest.scala | 15 ++++ sbt/src/sbt-test/tests/fork-uncaught/test | 3 + testing/agent/src/main/java/sbt/ForkMain.java | 76 +++++++++++++++---- 5 files changed, 91 insertions(+), 16 deletions(-) create mode 100644 sbt/src/sbt-test/tests/fork-uncaught/build.sbt create mode 100644 sbt/src/sbt-test/tests/fork-uncaught/src/main/scala/Foo.scala create mode 100644 sbt/src/sbt-test/tests/fork-uncaught/src/test/scala/FooTest.scala create mode 100644 sbt/src/sbt-test/tests/fork-uncaught/test diff --git a/sbt/src/sbt-test/tests/fork-uncaught/build.sbt b/sbt/src/sbt-test/tests/fork-uncaught/build.sbt new file mode 100644 index 000000000..111c25103 --- /dev/null +++ b/sbt/src/sbt-test/tests/fork-uncaught/build.sbt @@ -0,0 +1,10 @@ +scalaVersion := "2.9.2" + +organization := "org.example" + +name := "fork-uncaught" + +fork := true + +libraryDependencies += "org.scalatest" % "scalatest_2.9.2" % "2.0.M3" % "test" intransitive() + diff --git a/sbt/src/sbt-test/tests/fork-uncaught/src/main/scala/Foo.scala b/sbt/src/sbt-test/tests/fork-uncaught/src/main/scala/Foo.scala new file mode 100644 index 000000000..6904c903f --- /dev/null +++ b/sbt/src/sbt-test/tests/fork-uncaught/src/main/scala/Foo.scala @@ -0,0 +1,3 @@ +package foo.test + +object Foo { val foo = 5 } \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/fork-uncaught/src/test/scala/FooTest.scala b/sbt/src/sbt-test/tests/fork-uncaught/src/test/scala/FooTest.scala new file mode 100644 index 000000000..838ea44c4 --- /dev/null +++ b/sbt/src/sbt-test/tests/fork-uncaught/src/test/scala/FooTest.scala @@ -0,0 +1,15 @@ +package foo.test + +import org.scalatest.{FunSpec, ShouldMatchers} + +class FooTest extends FunSpec with ShouldMatchers { + + if(java.lang.Boolean.getBoolean("test.init.fail")) + sys.error("exception during construction") + + describe("Foo.foo should") { + it("always return 5") { + Foo.foo should equal (5) + } + } +} diff --git a/sbt/src/sbt-test/tests/fork-uncaught/test b/sbt/src/sbt-test/tests/fork-uncaught/test new file mode 100644 index 000000000..0f38f68bc --- /dev/null +++ b/sbt/src/sbt-test/tests/fork-uncaught/test @@ -0,0 +1,3 @@ +> test +> 'set javaOptions in Test += "-Dtest.init.fail=true"' +-> test diff --git a/testing/agent/src/main/java/sbt/ForkMain.java b/testing/agent/src/main/java/sbt/ForkMain.java index 2fe088a49..11a03927f 100755 --- a/testing/agent/src/main/java/sbt/ForkMain.java +++ b/testing/agent/src/main/java/sbt/ForkMain.java @@ -114,6 +114,12 @@ public class ForkMain { throw new RunAborted(e); } } + void logError(ObjectOutputStream os, String message) { + write(os, new Object[]{ForkTags.Error, message}); + } + void writeEvents(ObjectOutputStream os, ForkTestDefinition test, ForkEvent[] events) { + write(os, new Object[]{test.name, events}); + } void runTests(ObjectInputStream is, final ObjectOutputStream os) throws Exception { final boolean ansiCodesSupported = is.readBoolean(); final ForkTestDefinition[] tests = (ForkTestDefinition[]) is.readObject(); @@ -121,7 +127,7 @@ public class ForkMain { Logger[] loggers = { new Logger() { public boolean ansiCodesSupported() { return ansiCodesSupported; } - public void error(String s) { write(os, new Object[]{ForkTags.Error, s}); } + public void error(String s) { logError(os, s); } public void warn(String s) { write(os, new Object[]{ForkTags.Warn, s}); } public void info(String s) { write(os, new Object[]{ForkTags.Info, s}); } public void debug(String s) { write(os, new Object[]{ForkTags.Debug, s}); } @@ -137,7 +143,7 @@ public class ForkMain { try { framework = (Framework) Class.forName(implClassName).newInstance(); } catch (ClassNotFoundException e) { - write(os, new Object[]{ForkTags.Error, "Framework implementation '" + implClassName + "' not present."}); + logError(os, "Framework implementation '" + implClassName + "' not present."); continue; } @@ -148,28 +154,66 @@ public class ForkMain { } } final org.scalatools.testing.Runner runner = framework.testRunner(getClass().getClassLoader(), loggers); - for (ForkTestDefinition test : filteredTests) { - final List events = new ArrayList(); - EventHandler handler = new EventHandler() { public void handle(Event e){ events.add(new ForkEvent(e)); } }; - if (runner instanceof Runner2) { - ((Runner2) runner).run(test.name, test.fingerprint, handler, frameworkArgs); - } else if (test.fingerprint instanceof TestFingerprint) { - runner.run(test.name, (TestFingerprint) test.fingerprint, handler, frameworkArgs); - } else { - write(os, new Object[]{ForkTags.Error, "Framework '" + framework + "' does not support test '" + test.name + "'"}); - } - write(os, new Object[]{test.name, events.toArray(new ForkEvent[events.size()])}); - } + for (ForkTestDefinition test : filteredTests) + runTestSafe(test, runner, framework, frameworkArgs, os); } write(os, ForkTags.Done); is.readObject(); } - void run(ObjectInputStream is, final ObjectOutputStream os) throws Exception { + void runTestSafe(ForkTestDefinition test, org.scalatools.testing.Runner runner, Framework framework, String[] frameworkArgs, ObjectOutputStream os) { + ForkEvent[] events; + try { + events = runTest(test, runner, framework, frameworkArgs, os); + } catch (Throwable t) { + events = new ForkEvent[] { testError(os, test, "Uncaught exception when running " + test.name + ": " + t.toString(), t) }; + } + writeEvents(os, test, events); + } + ForkEvent[] runTest(ForkTestDefinition test, org.scalatools.testing.Runner runner, Framework framework, String[] frameworkArgs, ObjectOutputStream os) { + final List events = new ArrayList(); + EventHandler handler = new EventHandler() { public void handle(Event e){ events.add(new ForkEvent(e)); } }; + if (runner instanceof Runner2) { + ((Runner2) runner).run(test.name, test.fingerprint, handler, frameworkArgs); + } else if (test.fingerprint instanceof TestFingerprint) { + runner.run(test.name, (TestFingerprint) test.fingerprint, handler, frameworkArgs); + } else { + events.add(testError(os, test, "Framework '" + framework + "' does not support test '" + test.name + "'")); + } + return events.toArray(new ForkEvent[events.size()]); + } + void run(ObjectInputStream is, ObjectOutputStream os) throws Exception { try { runTests(is, os); } catch (RunAborted e) { - System.err.println("Internal error when running tests: " + e.getMessage()); + internalError(e); + } catch (Throwable t) { + try { + logError(os, "Uncaught exception when running tests: " + t.toString()); + write(os, t); + } catch (Throwable t2) { + internalError(t2); + } } } + void internalError(Throwable t) { + System.err.println("Internal error when running tests: " + t.toString()); + } + ForkEvent testEvent(final String name, final String desc, final Result r, final Throwable err) { + return new ForkEvent(new Event() { + public String testName() { return name; } + public String description() { return desc; } + public Result result() { return r; } + public Throwable error() { return err; } + }); + } + ForkEvent testError(ObjectOutputStream os, ForkTestDefinition test, String message) { + logError(os, message); + return testEvent(test.name, message, Result.Error, null); + } + ForkEvent testError(ObjectOutputStream os, ForkTestDefinition test, String message, Throwable t) { + logError(os, message); + write(os, t); + return testEvent(test.name, message, Result.Error, t); + } } }