From 51bf4ba97981fb02701c398e2e49ea627f9616e6 Mon Sep 17 00:00:00 2001 From: Antoine Gourlay Date: Thu, 23 Feb 2017 17:59:52 +0100 Subject: [PATCH] Add synchronization when sending events to sbt (fix ArrayIOBException). Partest shows a single `Task` to sbt and does all the concurrency and running subtasks itself, which isn't really how this should work, so sbt doesn't expect the task to send back events concurrently: `EventHandler.handle` adds events to an `ArrayList` when forking (see [ForkMain][2]), and is called after each test, and if it happens on two threads at exactly the wrong moment: ``` Caused by: sbt.ForkMain$ForkError: java.lang.ArrayIndexOutOfBoundsException: 15 at java.util.ArrayList.add(ArrayList.java:459) at sbt.ForkMain$Run$2$1.handle(ForkMain.java:294) at scala.tools.partest.sbt.SBTRunner$$anon$1.onFinishTest(SBTRunner.scala:70) at scala.tools.partest.nest.SuiteRunner.runTest(Runner.scala:781) at scala.tools.partest.nest.SuiteRunner.$anonfun$runTestsForFiles$2(Runner.scala:788) at scala.tools.partest.package$$anon$2.call(package.scala:135) ``` See scala/scala#5663 ([build log][1] or the failed run). [1]: https://scala-ci.typesafe.com/job/scala-2.12.x-validate-test/4300/consoleFull [2]: https://github.com/sbt/sbt/blob/v0.13.13/testing/agent/src/main/java/sbt/ForkMain.java#L294 --- .../scala/tools/partest/sbt/SBTRunner.scala | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/main/scala/scala/tools/partest/sbt/SBTRunner.scala b/src/main/scala/scala/tools/partest/sbt/SBTRunner.scala index d42d082..8981d3d 100644 --- a/src/main/scala/scala/tools/partest/sbt/SBTRunner.scala +++ b/src/main/scala/scala/tools/partest/sbt/SBTRunner.scala @@ -64,16 +64,18 @@ class SBTRunner(val config: RunnerSpec.Config, javacCmdPath = Option(javacCmd).map(_.getAbsolutePath) getOrElse PartestDefaults.javacCmd, scalacExtraArgs = scalacArgs, javaOpts = javaOpts, - scalacOpts = scalacOpts) { + scalacOpts = scalacOpts) { self => override def onFinishTest(testFile: File, result: TestState): TestState = { - eventHandler.handle(new Event { - def fullyQualifiedName: String = testFile.testIdent - def fingerprint: Fingerprint = partestFingerprint - def selector: Selector = new TestSelector(testFile.testIdent) - val (status, throwable) = makeStatus(result) - def duration: Long = -1 - }) + self.synchronized { + eventHandler.handle(new Event { + def fullyQualifiedName: String = testFile.testIdent + def fingerprint: Fingerprint = partestFingerprint + def selector: Selector = new TestSelector(testFile.testIdent) + val (status, throwable) = makeStatus(result) + def duration: Long = -1 + }) + } result } }