From 8eb2d7389d5367d6bea91df0975747aa942f15a1 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Wed, 21 Mar 2018 10:16:01 -0700 Subject: [PATCH 1/2] Add test for async utest TestSuites Sometimes when utest runs async tests (i.e. tests that return a future) the test suite will fail even when none of the individual tests do. This is due to a data race in sbt. Most, but not all, of the time, this test will induce that race. --- sbt/src/sbt-test/tests/fork-async/build.sbt | 8 +++++++ .../src/test/scala/ForkAsyncTest.scala | 21 +++++++++++++++++++ sbt/src/sbt-test/tests/fork-async/test | 1 + 3 files changed, 30 insertions(+) create mode 100644 sbt/src/sbt-test/tests/fork-async/build.sbt create mode 100644 sbt/src/sbt-test/tests/fork-async/src/test/scala/ForkAsyncTest.scala create mode 100644 sbt/src/sbt-test/tests/fork-async/test diff --git a/sbt/src/sbt-test/tests/fork-async/build.sbt b/sbt/src/sbt-test/tests/fork-async/build.sbt new file mode 100644 index 000000000..bb42b0c17 --- /dev/null +++ b/sbt/src/sbt-test/tests/fork-async/build.sbt @@ -0,0 +1,8 @@ +testFrameworks += new TestFramework("utest.runner.Framework") + +lazy val root = (project in file(".")). + settings( + scalaVersion := "2.12.4", + libraryDependencies += "com.lihaoyi" %% "utest" % "0.6.4" % Test, + fork in Test := true + ) diff --git a/sbt/src/sbt-test/tests/fork-async/src/test/scala/ForkAsyncTest.scala b/sbt/src/sbt-test/tests/fork-async/src/test/scala/ForkAsyncTest.scala new file mode 100644 index 000000000..a8bc4c0f2 --- /dev/null +++ b/sbt/src/sbt-test/tests/fork-async/src/test/scala/ForkAsyncTest.scala @@ -0,0 +1,21 @@ +import utest._ + +import utest._ +import utest.framework._ + +import scala.concurrent.{ ExecutionContext, Promise } +import scala.util.Success + +object ForkAsyncTest extends TestSuite { + val g = ExecutionContext.global + val n = 10 + val (testNames, promises) = (1 to n).map(i => Tree(s"$i") -> Promise[Unit]).unzip + val testTrees = promises.zipWithIndex.map { case (p, i) => + new TestCallTree(Left { + if (i == (n - 1)) promises.foreach(p => g.execute(() => p.tryComplete(Success(())))) + p.future + }) + } + val tests = + Tests(nameTree = Tree("async", testNames: _*), callTree = new TestCallTree(Right(testTrees))) +} diff --git a/sbt/src/sbt-test/tests/fork-async/test b/sbt/src/sbt-test/tests/fork-async/test new file mode 100644 index 000000000..dfffb838b --- /dev/null +++ b/sbt/src/sbt-test/tests/fork-async/test @@ -0,0 +1 @@ +> test From 9b24e9f9ebffadfa7d715b457b9fb1b8d51c3bdb Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Tue, 20 Mar 2018 18:06:03 -0700 Subject: [PATCH 2/2] Use ConcurrentLinkedDeque for EventHandler ArrayList::add is not thread safe. I ran into cases where async tests using utests would fail even when all of the individual tests passed. This was because multiple threads called back into the handle method of the handler instance variable, which just delegated to eventList::add. When this happened, one of the events would get added to the list as a null reference, which would manifest as an NPE upstream on the master process. After this change, my tests stopped failing. --- testing/agent/src/main/java/sbt/ForkMain.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/testing/agent/src/main/java/sbt/ForkMain.java b/testing/agent/src/main/java/sbt/ForkMain.java index 24acaf52b..a92d02506 100644 --- a/testing/agent/src/main/java/sbt/ForkMain.java +++ b/testing/agent/src/main/java/sbt/ForkMain.java @@ -17,6 +17,7 @@ import java.net.Socket; import java.net.InetAddress; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.concurrent.*; @@ -294,7 +295,7 @@ final public class ForkMain { Task[] nestedTasks; final TaskDef taskDef = task.taskDef(); try { - final List eventList = new ArrayList(); + final Collection eventList = new ConcurrentLinkedDeque(); final EventHandler handler = new EventHandler() { public void handle(final Event e){ eventList.add(new ForkEvent(e)); } }; logDebug(os, " Running " + taskDef); nestedTasks = task.execute(handler, loggers);