Fix race condition in non-forked, parallel tests.

Non forked tests that are run in parallel groups can call into
a single instance of TestStatusReporter concurrently.

This seems to be limited to the startGroup/endGroup/testEvent
methods called in:

  a41727fb17/testing/src/main/scala/sbt/TestFramework.scala (L100-L124)

Which itself is called within:

  a41727fb17/testing/src/main/scala/sbt/TestFramework.scala (L203-L229)

Creating the `runnables` that are run in parallel (in builds so configured):

  a6eb1260c8/main-actions/src/main/scala/sbt/Tests.scala (L222-L230)

We believe this to be the cause of the hang witnessed in
the a suite of Scalacheck-framework tests in the Scala
build:

  https://github.com/scala/scala-jenkins-infra/issues/249

This commit uses a concurrent map to support concurrent
status updates.
This commit is contained in:
Jason Zaugg 2018-03-05 18:06:46 +10:00
parent 27bbde810d
commit 0bea7e9e18
2 changed files with 13 additions and 7 deletions

View File

@ -63,7 +63,8 @@ private[sbt] object Server {
val maxSocketLength = new UnixDomainSocketLibrary.SockaddrUn().sunPath.length - 1
val path = socketfile.getAbsolutePath
if (path.length > maxSocketLength)
sys.error("socket file absolute path too long; " +
sys.error(
"socket file absolute path too long; " +
"either switch to another connection type " +
"or define a short \"SBT_GLOBAL_SERVER_DIR\" value. " +
s"Current path: ${path}")

View File

@ -8,14 +8,16 @@
package sbt
import java.io.File
import sbt.io.IO
import scala.collection.mutable.Map
import sbt.io.IO
import sbt.protocol.testing.TestResult
import java.util.concurrent.ConcurrentHashMap
import scala.collection.concurrent
// Assumes exclusive ownership of the file.
private[sbt] class TestStatusReporter(f: File) extends TestsListener {
private lazy val succeeded = TestStatus.read(f)
private lazy val succeeded: concurrent.Map[String, Long] = TestStatus.read(f)
def doInit = ()
def startGroup(name: String): Unit = { succeeded remove name }
@ -32,13 +34,16 @@ private[sbt] class TestStatusReporter(f: File) extends TestsListener {
private[sbt] object TestStatus {
import java.util.Properties
def read(f: File): Map[String, Long] = {
def read(f: File): concurrent.Map[String, Long] = {
import scala.collection.JavaConverters._
val properties = new Properties
IO.load(properties, f)
properties.asScala map { case (k, v) => (k, v.toLong) }
val result = new ConcurrentHashMap[String, Long]()
properties.asScala.iterator.foreach { case (k, v) => result.put(k, v.toLong) }
result.asScala
}
def write(map: Map[String, Long], label: String, f: File): Unit = {
def write(map: collection.Map[String, Long], label: String, f: File): Unit = {
val properties = new Properties
for ((test, lastSuccessTime) <- map)
properties.setProperty(test, lastSuccessTime.toString)