mirror of https://github.com/sbt/sbt.git
Run scripted tests in parallel
Add binary-compatible methods to run scripted tests in parallel using parallel collections. Parallel collections are not tuned and a default of 4 cores is used for the `ForkJoinPool`. This can be a matter of experimentation in the future but for now I prefer to stay with it. The non-parallel methods are not affected by this change. The changes to the scripted plugin have not been used, only the sbt scripted plugin is using `runInParallel`. In the future, this change can also happen so that plugin authors can benefit from parallel sbt testing. This is not a priority for me now :)
This commit is contained in:
parent
23762a3ac5
commit
f497e15cd8
|
|
@ -89,12 +89,12 @@ object Scripted {
|
||||||
|
|
||||||
// Interface to cross class loader
|
// Interface to cross class loader
|
||||||
type SbtScriptedRunner = {
|
type SbtScriptedRunner = {
|
||||||
def run(resourceBaseDirectory: File,
|
def runInParallel(resourceBaseDirectory: File,
|
||||||
bufferLog: Boolean,
|
bufferLog: Boolean,
|
||||||
tests: Array[String],
|
tests: Array[String],
|
||||||
bootProperties: File,
|
bootProperties: File,
|
||||||
launchOpts: Array[String],
|
launchOpts: Array[String],
|
||||||
prescripted: java.util.List[File]): Unit
|
prescripted: java.util.List[File]): Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
def doScripted(launcher: File,
|
def doScripted(launcher: File,
|
||||||
|
|
@ -120,7 +120,12 @@ object Scripted {
|
||||||
def get(x: Int): sbt.File = ???
|
def get(x: Int): sbt.File = ???
|
||||||
def size(): Int = 0
|
def size(): Int = 0
|
||||||
}
|
}
|
||||||
bridge.run(sourcePath, bufferLog, args.toArray, launcher, launchOpts.toArray, callback)
|
bridge.runInParallel(sourcePath,
|
||||||
|
bufferLog,
|
||||||
|
args.toArray,
|
||||||
|
launcher,
|
||||||
|
launchOpts.toArray,
|
||||||
|
callback)
|
||||||
} catch { case ite: java.lang.reflect.InvocationTargetException => throw ite.getCause }
|
} catch { case ite: java.lang.reflect.InvocationTargetException => throw ite.getCause }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,21 +8,21 @@ package test
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
import scala.util.control.NonFatal
|
import scala.util.control.NonFatal
|
||||||
|
|
||||||
import sbt.internal.scripted.{
|
import sbt.internal.scripted.{
|
||||||
CommentHandler,
|
CommentHandler,
|
||||||
FileCommands,
|
FileCommands,
|
||||||
ScriptRunner,
|
ScriptRunner,
|
||||||
TestScriptParser,
|
TestException,
|
||||||
TestException
|
TestScriptParser
|
||||||
}
|
}
|
||||||
import sbt.io.{ DirectoryFilter, HiddenFileFilter }
|
import sbt.io.{ DirectoryFilter, HiddenFileFilter }
|
||||||
import sbt.io.IO.wrapNull
|
import sbt.io.IO.wrapNull
|
||||||
import sbt.internal.io.Resources
|
import sbt.internal.io.Resources
|
||||||
|
|
||||||
import sbt.internal.util.{ BufferedLogger, ConsoleLogger, FullLogger }
|
import sbt.internal.util.{ BufferedLogger, ConsoleLogger, FullLogger }
|
||||||
import sbt.util.{ AbstractLogger, Logger }
|
import sbt.util.{ AbstractLogger, Logger }
|
||||||
|
|
||||||
|
import scala.collection.parallel.mutable.ParSeq
|
||||||
|
|
||||||
final class ScriptedTests(resourceBaseDirectory: File,
|
final class ScriptedTests(resourceBaseDirectory: File,
|
||||||
bufferLog: Boolean,
|
bufferLog: Boolean,
|
||||||
launcher: File,
|
launcher: File,
|
||||||
|
|
@ -40,7 +40,7 @@ final class ScriptedTests(resourceBaseDirectory: File,
|
||||||
def scriptedTest(group: String,
|
def scriptedTest(group: String,
|
||||||
name: String,
|
name: String,
|
||||||
prescripted: File => Unit,
|
prescripted: File => Unit,
|
||||||
log: Logger): Seq[() => Option[String]] = {
|
log: Logger): Seq[TestRunner] = {
|
||||||
import sbt.io.syntax._
|
import sbt.io.syntax._
|
||||||
for (groupDir <- (resourceBaseDirectory * group).get; nme <- (groupDir * name).get) yield {
|
for (groupDir <- (resourceBaseDirectory * group).get; nme <- (groupDir * name).get) yield {
|
||||||
val g = groupDir.getName
|
val g = groupDir.getName
|
||||||
|
|
@ -119,6 +119,10 @@ final class ScriptedTests(resourceBaseDirectory: File,
|
||||||
}
|
}
|
||||||
|
|
||||||
object ScriptedTests extends ScriptedRunner {
|
object ScriptedTests extends ScriptedRunner {
|
||||||
|
|
||||||
|
/** Represents the function that runs the scripted tests. */
|
||||||
|
type TestRunner = () => Option[String]
|
||||||
|
|
||||||
val emptyCallback: File => Unit = { _ =>
|
val emptyCallback: File => Unit = { _ =>
|
||||||
()
|
()
|
||||||
}
|
}
|
||||||
|
|
@ -136,8 +140,6 @@ object ScriptedTests extends ScriptedRunner {
|
||||||
}
|
}
|
||||||
|
|
||||||
class ScriptedRunner {
|
class ScriptedRunner {
|
||||||
import ScriptedTests._
|
|
||||||
|
|
||||||
// This is called by project/Scripted.scala
|
// This is called by project/Scripted.scala
|
||||||
// Using java.util.List[File] to encode File => Unit
|
// Using java.util.List[File] to encode File => Unit
|
||||||
def run(resourceBaseDirectory: File,
|
def run(resourceBaseDirectory: File,
|
||||||
|
|
@ -166,17 +168,56 @@ class ScriptedRunner {
|
||||||
runAll(allTests)
|
runAll(allTests)
|
||||||
}
|
}
|
||||||
|
|
||||||
def runAll(tests: Seq[() => Option[String]]): Unit = {
|
def runInParallel(resourceBaseDirectory: File,
|
||||||
|
bufferLog: Boolean,
|
||||||
|
tests: Array[String],
|
||||||
|
bootProperties: File,
|
||||||
|
launchOpts: Array[String],
|
||||||
|
prescripted: java.util.List[File]): Unit = {
|
||||||
|
val logger = ConsoleLogger()
|
||||||
|
val addTestFile = (f: File) => { prescripted.add(f); () }
|
||||||
|
runInParallel(resourceBaseDirectory,
|
||||||
|
bufferLog,
|
||||||
|
tests,
|
||||||
|
logger,
|
||||||
|
bootProperties,
|
||||||
|
launchOpts,
|
||||||
|
addTestFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
def runInParallel(
|
||||||
|
resourceBaseDirectory: File,
|
||||||
|
bufferLog: Boolean,
|
||||||
|
tests: Array[String],
|
||||||
|
logger: AbstractLogger,
|
||||||
|
bootProperties: File,
|
||||||
|
launchOpts: Array[String],
|
||||||
|
prescripted: File => Unit
|
||||||
|
): Unit = {
|
||||||
|
val runner = new ScriptedTests(resourceBaseDirectory, bufferLog, bootProperties, launchOpts)
|
||||||
|
val scriptedTests = get(tests, resourceBaseDirectory, logger)
|
||||||
|
val scriptedTestRunners = scriptedTests
|
||||||
|
.flatMap(t => runner.scriptedTest(t.group, t.name, prescripted, logger))
|
||||||
|
runAllInParallel(scriptedTestRunners.toParArray)
|
||||||
|
}
|
||||||
|
|
||||||
|
def runAll(tests: Seq[ScriptedTests.TestRunner]): Unit = {
|
||||||
val errors = for (test <- tests; err <- test()) yield err
|
val errors = for (test <- tests; err <- test()) yield err
|
||||||
if (errors.nonEmpty)
|
if (errors.nonEmpty)
|
||||||
sys.error(errors.mkString("Failed tests:\n\t", "\n\t", "\n"))
|
sys.error(errors.mkString("Failed tests:\n\t", "\n\t", "\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def runAllInParallel(tests: ParSeq[ScriptedTests.TestRunner]): Unit = {
|
||||||
|
val executedTests = tests.flatMap(test => test.apply().toList).toList
|
||||||
|
if (executedTests.nonEmpty)
|
||||||
|
sys.error(executedTests.mkString("Failed tests:\n\t", "\n\t", "\n"))
|
||||||
|
}
|
||||||
|
|
||||||
def get(tests: Seq[String], baseDirectory: File, log: Logger): Seq[ScriptedTest] =
|
def get(tests: Seq[String], baseDirectory: File, log: Logger): Seq[ScriptedTest] =
|
||||||
if (tests.isEmpty) listTests(baseDirectory, log) else parseTests(tests)
|
if (tests.isEmpty) listTests(baseDirectory, log) else parseTests(tests)
|
||||||
|
|
||||||
def listTests(baseDirectory: File, log: Logger): Seq[ScriptedTest] =
|
def listTests(baseDirectory: File, log: Logger): Seq[ScriptedTest] =
|
||||||
(new ListTests(baseDirectory, _ => true, log)).listTests
|
new ListTests(baseDirectory, _ => true, log).listTests
|
||||||
|
|
||||||
def parseTests(in: Seq[String]): Seq[ScriptedTest] =
|
def parseTests(in: Seq[String]): Seq[ScriptedTest] =
|
||||||
for (testString <- in) yield {
|
for (testString <- in) yield {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue