mirror of https://github.com/sbt/sbt.git
Merge pull request #3151 from scalacenter/parallel-scripted-tests
Add parallel batch mode to scripted tests
This commit is contained in:
commit
a265600154
22
.travis.yml
22
.travis.yml
|
|
@ -16,29 +16,18 @@ matrix:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
matrix:
|
matrix:
|
||||||
- SBT_CMD=";test:compile;scalafmtCheck"
|
- SBT_CMD=";mimaReportBinaryIssues;test:compile;scalafmtCheck;safeUnitTests;otherUnitTests"
|
||||||
- SBT_CMD="mimaReportBinaryIssues"
|
|
||||||
- SBT_CMD="safeUnitTests"
|
|
||||||
- SBT_CMD="otherUnitTests"
|
|
||||||
- SBT_CMD="scripted actions/*"
|
- SBT_CMD="scripted actions/*"
|
||||||
- SBT_CMD="scripted apiinfo/*"
|
- SBT_CMD="scripted apiinfo/* compiler-project/* ivy-deps-management/*"
|
||||||
- SBT_CMD="scripted compiler-project/*"
|
|
||||||
- SBT_CMD="scripted dependency-management/*1of4"
|
- SBT_CMD="scripted dependency-management/*1of4"
|
||||||
- SBT_CMD="scripted dependency-management/*2of4"
|
- SBT_CMD="scripted dependency-management/*2of4"
|
||||||
- SBT_CMD="scripted dependency-management/*3of4"
|
- SBT_CMD="scripted dependency-management/*3of4"
|
||||||
- SBT_CMD="scripted dependency-management/*4of4"
|
- SBT_CMD="scripted dependency-management/*4of4"
|
||||||
- SBT_CMD="scripted ivy-deps-management/*"
|
- SBT_CMD="scripted java/* package/* reporter/* run/* project-load/*"
|
||||||
- SBT_CMD="scripted java/*"
|
|
||||||
- SBT_CMD="scripted package/*"
|
|
||||||
- SBT_CMD="scripted project/*1of2"
|
- SBT_CMD="scripted project/*1of2"
|
||||||
- SBT_CMD="scripted project/*2of2"
|
- SBT_CMD="scripted project/*2of2"
|
||||||
- SBT_CMD="scripted reporter/*"
|
- SBT_CMD="scripted source-dependencies/*"
|
||||||
- SBT_CMD="scripted run/*"
|
|
||||||
- SBT_CMD="scripted source-dependencies/*1of3"
|
|
||||||
- SBT_CMD="scripted source-dependencies/*2of3"
|
|
||||||
- SBT_CMD="scripted source-dependencies/*3of3"
|
|
||||||
- SBT_CMD="scripted tests/*"
|
- SBT_CMD="scripted tests/*"
|
||||||
- SBT_CMD="scripted project-load/*"
|
|
||||||
- SBT_CMD="repoOverrideTest:scripted dependency-management/*"
|
- SBT_CMD="repoOverrideTest:scripted dependency-management/*"
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
|
|
@ -46,7 +35,8 @@ notifications:
|
||||||
- sbt-dev-bot@googlegroups.com
|
- sbt-dev-bot@googlegroups.com
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- sbt -J-XX:ReservedCodeCacheSize=128m "$SBT_CMD"
|
# It doesn't need that much memory because compile and run are forked
|
||||||
|
- sbt -J-XX:ReservedCodeCacheSize=128m -J-Xmx800M -J-Xms800M -J-server "$SBT_CMD"
|
||||||
|
|
||||||
before_cache:
|
before_cache:
|
||||||
- find $HOME/.ivy2 -name "ivydata-*.properties" -print -delete
|
- find $HOME/.ivy2 -name "ivydata-*.properties" -print -delete
|
||||||
|
|
|
||||||
11
build.sbt
11
build.sbt
|
|
@ -53,7 +53,9 @@ def commonSettings: Seq[Setting[_]] =
|
||||||
mimaBinaryIssueFilters ++= {
|
mimaBinaryIssueFilters ++= {
|
||||||
import com.typesafe.tools.mima.core._, ProblemFilters._
|
import com.typesafe.tools.mima.core._, ProblemFilters._
|
||||||
Seq()
|
Seq()
|
||||||
}
|
},
|
||||||
|
fork in compile := true,
|
||||||
|
fork in run := true
|
||||||
) flatMap (_.settings)
|
) flatMap (_.settings)
|
||||||
|
|
||||||
def minimalSettings: Seq[Setting[_]] =
|
def minimalSettings: Seq[Setting[_]] =
|
||||||
|
|
@ -352,7 +354,7 @@ def otherRootSettings =
|
||||||
scriptedUnpublished := scriptedUnpublishedTask.evaluated,
|
scriptedUnpublished := scriptedUnpublishedTask.evaluated,
|
||||||
scriptedSource := (sourceDirectory in sbtProj).value / "sbt-test",
|
scriptedSource := (sourceDirectory in sbtProj).value / "sbt-test",
|
||||||
// scriptedPrescripted := { addSbtAlternateResolver _ },
|
// scriptedPrescripted := { addSbtAlternateResolver _ },
|
||||||
scriptedLaunchOpts := List("-XX:MaxPermSize=256M", "-Xmx1G"),
|
scriptedLaunchOpts := List("-Xmx1500M", "-Xms512M", "-server"),
|
||||||
publishAll := { val _ = (publishLocal).all(ScopeFilter(inAnyProject)).value },
|
publishAll := { val _ = (publishLocal).all(ScopeFilter(inAnyProject)).value },
|
||||||
publishLocalBinAll := { val _ = (publishLocalBin).all(ScopeFilter(inAnyProject)).value },
|
publishLocalBinAll := { val _ = (publishLocalBin).all(ScopeFilter(inAnyProject)).value },
|
||||||
aggregate in bintrayRelease := false
|
aggregate in bintrayRelease := false
|
||||||
|
|
@ -362,8 +364,9 @@ def otherRootSettings =
|
||||||
()
|
()
|
||||||
},
|
},
|
||||||
scriptedLaunchOpts := {
|
scriptedLaunchOpts := {
|
||||||
List("-XX:MaxPermSize=256M",
|
List("-Xmx1500M",
|
||||||
"-Xmx1G",
|
"-Xms512M",
|
||||||
|
"-server",
|
||||||
"-Dsbt.override.build.repos=true",
|
"-Dsbt.override.build.repos=true",
|
||||||
s"""-Dsbt.repository.config=${scriptedSource.value / "repo.config"}""")
|
s"""-Dsbt.repository.config=${scriptedSource.value / "repo.config"}""")
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package internal
|
||||||
package parser
|
package parser
|
||||||
|
|
||||||
import sbt.internal.util.{ LineRange, MessageOnlyException }
|
import sbt.internal.util.{ LineRange, MessageOnlyException }
|
||||||
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
|
|
@ -14,10 +13,9 @@ import scala.reflect.internal.util.{ BatchSourceFile, Position }
|
||||||
import scala.reflect.io.VirtualDirectory
|
import scala.reflect.io.VirtualDirectory
|
||||||
import scala.reflect.internal.Positions
|
import scala.reflect.internal.Positions
|
||||||
import scala.tools.nsc.{ CompilerCommand, Global }
|
import scala.tools.nsc.{ CompilerCommand, Global }
|
||||||
import scala.tools.nsc.reporters.{ Reporter, StoreReporter }
|
import scala.tools.nsc.reporters.{ ConsoleReporter, Reporter, StoreReporter }
|
||||||
import scala.util.Random
|
import scala.util.Random
|
||||||
|
import scala.util.{ Failure, Success }
|
||||||
import scala.util.{ Success, Failure }
|
|
||||||
|
|
||||||
private[sbt] object SbtParser {
|
private[sbt] object SbtParser {
|
||||||
val END_OF_LINE_CHAR = '\n'
|
val END_OF_LINE_CHAR = '\n'
|
||||||
|
|
@ -78,9 +76,10 @@ private[sbt] object SbtParser {
|
||||||
|
|
||||||
private def getReporter(fileName: String) = {
|
private def getReporter(fileName: String) = {
|
||||||
val reporter = reporters.get(fileName)
|
val reporter = reporters.get(fileName)
|
||||||
if (reporter == null)
|
if (reporter == null) {
|
||||||
sys.error(s"Sbt parser failure: no reporter for $fileName.")
|
scalacGlobalInitReporter.getOrElse(
|
||||||
reporter
|
sys.error(s"Sbt forgot to initialize `scalacGlobalInitReporter`."))
|
||||||
|
} else reporter
|
||||||
}
|
}
|
||||||
|
|
||||||
def throwParserErrorsIfAny(reporter: StoreReporter, fileName: String): Unit = {
|
def throwParserErrorsIfAny(reporter: StoreReporter, fileName: String): Unit = {
|
||||||
|
|
@ -99,14 +98,15 @@ private[sbt] object SbtParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
private[sbt] final val globalReporter = new UniqueParserReporter
|
private[sbt] final val globalReporter = new UniqueParserReporter
|
||||||
|
private[sbt] var scalacGlobalInitReporter: Option[ConsoleReporter] = None
|
||||||
|
|
||||||
private[sbt] final lazy val defaultGlobalForParser = {
|
private[sbt] final lazy val defaultGlobalForParser = {
|
||||||
import scala.reflect.internal.util.NoPosition
|
|
||||||
val options = "-cp" :: s"$defaultClasspath" :: "-Yrangepos" :: Nil
|
val options = "-cp" :: s"$defaultClasspath" :: "-Yrangepos" :: Nil
|
||||||
val reportError = (msg: String) => globalReporter.error(NoPosition, msg)
|
val reportError = (msg: String) => System.err.println(msg)
|
||||||
val command = new CompilerCommand(options, reportError)
|
val command = new CompilerCommand(options, reportError)
|
||||||
val settings = command.settings
|
val settings = command.settings
|
||||||
settings.outputDirs.setSingleOutput(new VirtualDirectory("(memory)", None))
|
settings.outputDirs.setSingleOutput(new VirtualDirectory("(memory)", None))
|
||||||
|
scalacGlobalInitReporter = Some(new ConsoleReporter(settings))
|
||||||
|
|
||||||
// Mix Positions, otherwise global ignores -Yrangepos
|
// Mix Positions, otherwise global ignores -Yrangepos
|
||||||
val global = new Global(settings, globalReporter) with Positions
|
val global = new Global(settings, globalReporter) with Positions
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ object Scripted {
|
||||||
else dropped.take(pageSize)
|
else dropped.take(pageSize)
|
||||||
}
|
}
|
||||||
def nameP(group: String) = {
|
def nameP(group: String) = {
|
||||||
token("*".id | id.examples(pairMap(group)))
|
token("*".id | id.examples(pairMap.getOrElse(group, Set.empty[String])))
|
||||||
}
|
}
|
||||||
val PagedIds: Parser[Seq[String]] =
|
val PagedIds: Parser[Seq[String]] =
|
||||||
for {
|
for {
|
||||||
|
|
@ -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 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
commands += Command.command("noop") { s => s }
|
commands += Command.command("noop") { s => s }
|
||||||
|
|
||||||
TaskKey[Unit]("check") := {
|
TaskKey[Unit]("check") := {
|
||||||
assert(commands.value.toString() == "List(SimpleCommand(noop))",
|
assert(commands.value.toString().contains("SimpleCommand(noop)"),
|
||||||
s"""commands should display "List(SimpleCommand(noop))" but is ${commands.value}""")
|
s"""commands should contain "SimpleCommand(noop)" in ${commands.value}""")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import sbt.ExposeYourself._
|
import sbt.ExposeYourself._
|
||||||
|
|
||||||
taskCancelHandler := { (state: State) =>
|
taskCancelStrategy := { (state: State) =>
|
||||||
new TaskEvaluationCancelHandler {
|
new TaskCancellationStrategy {
|
||||||
type State = Unit
|
type State = Unit
|
||||||
override def onTaskEngineStart(canceller: TaskCancel): Unit = canceller.cancel()
|
override def onTaskEngineStart(canceller: RunningTaskEngine): Unit = canceller.cancelAndShutdown()
|
||||||
override def finish(result: Unit): Unit = ()
|
override def onTaskEngineFinish(state: State): Unit = ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
package sbt // this API is private[sbt], so only exposed for trusted clients and folks who like breaking.
|
package sbt // this API is private[sbt], so only exposed for trusted clients and folks who like breaking.
|
||||||
|
|
||||||
object ExposeYourself {
|
object ExposeYourself {
|
||||||
val taskCancelHandler = sbt.Keys.taskCancelHandler
|
val taskCancelStrategy = sbt.Keys.taskCancelStrategy
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,26 @@
|
||||||
import sbt.internal.inc.Analysis
|
import sbt.internal.inc.Analysis
|
||||||
|
import complete.DefaultParsers._
|
||||||
|
|
||||||
// checks number of compilation iterations performed since last `clean` run
|
// Reset compiler iterations, necessary because tests run in batch mode
|
||||||
InputKey[Unit]("check-number-of-compiler-iterations") := {
|
val recordPreviousIterations = taskKey[Unit]("Record previous iterations.")
|
||||||
val args = Def.spaceDelimited().parsed
|
recordPreviousIterations := {
|
||||||
val a = (compile in Compile).value.asInstanceOf[Analysis]
|
CompileState.previousIterations = {
|
||||||
assert(args.size == 1)
|
val previousAnalysis = (previousCompile in Compile).value.analysis
|
||||||
val expectedIterationsNumber = args(0).toInt
|
if (previousAnalysis.isEmpty) {
|
||||||
val allCompilationsSize = a.compilations.allCompilations.size
|
streams.value.log.info("No previous analysis detected")
|
||||||
assert(allCompilationsSize == expectedIterationsNumber,
|
0
|
||||||
"allCompilationsSize == %d (expected %d)".format(allCompilationsSize, expectedIterationsNumber))
|
} else {
|
||||||
|
previousAnalysis.get match {
|
||||||
|
case a: Analysis => a.compilations.allCompilations.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val checkIterations = inputKey[Unit]("Verifies the accumulated number of iterations of incremental compilation.")
|
||||||
|
|
||||||
|
checkIterations := {
|
||||||
|
val expected: Int = (Space ~> NatBasic).parsed
|
||||||
|
val actual: Int = ((compile in Compile).value match { case a: Analysis => a.compilations.allCompilations.size }) - CompileState.previousIterations
|
||||||
|
assert(expected == actual, s"Expected $expected compilations, got $actual")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
// This is necessary because tests are run in batch mode
|
||||||
|
object CompileState {
|
||||||
|
@volatile var previousIterations: Int = -1
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
# do not introduce unnecessary compile iterations
|
# do not introduce unnecessary compile iterations
|
||||||
|
|
||||||
# introduces first compile iteration
|
# introduces first compile iteration
|
||||||
|
> recordPreviousIterations
|
||||||
> compile
|
> compile
|
||||||
# this change is local to a method and does not change the api so introduces
|
# this change is local to a method and does not change the api so introduces
|
||||||
# only one additional compile iteration
|
# only one additional compile iteration
|
||||||
|
|
@ -9,4 +10,4 @@ $ copy-file changes/Foo1.scala src/main/scala/Foo.scala
|
||||||
# second iteration
|
# second iteration
|
||||||
> compile
|
> compile
|
||||||
# check if there are only two compile iterations being performed
|
# check if there are only two compile iterations being performed
|
||||||
> checkNumberOfCompilerIterations 2
|
> checkIterations 2
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,28 @@
|
||||||
import sbt.internal.inc.Analysis
|
import sbt.internal.inc.Analysis
|
||||||
|
import complete.DefaultParsers._
|
||||||
|
|
||||||
logLevel := Level.Debug
|
logLevel := Level.Debug
|
||||||
|
|
||||||
// dumps analysis into target/analysis-dump.txt file
|
// Reset compiler iterations, necessary because tests run in batch mode
|
||||||
InputKey[Unit]("check-number-of-compiler-iterations") := {
|
val recordPreviousIterations = taskKey[Unit]("Record previous iterations.")
|
||||||
val args = Def.spaceDelimited().parsed
|
recordPreviousIterations := {
|
||||||
val a = (compile in Compile).value.asInstanceOf[Analysis]
|
CompileState.previousIterations = {
|
||||||
assert(args.size == 1)
|
val previousAnalysis = (previousCompile in Compile).value.analysis
|
||||||
val expectedIterationsNumber = args(0).toInt
|
if (previousAnalysis.isEmpty) {
|
||||||
assert(
|
streams.value.log.info("No previous analysis detected")
|
||||||
a.compilations.allCompilations.size == expectedIterationsNumber,
|
0
|
||||||
"a.compilations.allCompilations.size = %d (expected %d)".format(
|
} else {
|
||||||
a.compilations.allCompilations.size, expectedIterationsNumber))
|
previousAnalysis.get match {
|
||||||
|
case a: Analysis => a.compilations.allCompilations.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val checkIterations = inputKey[Unit]("Verifies the accumulated number of iterations of incremental compilation.")
|
||||||
|
|
||||||
|
checkIterations := {
|
||||||
|
val expected: Int = (Space ~> NatBasic).parsed
|
||||||
|
val actual: Int = ((compile in Compile).value match { case a: Analysis => a.compilations.allCompilations.size }) - CompileState.previousIterations
|
||||||
|
assert(expected == actual, s"Expected $expected compilations, got $actual")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
// This is necessary because tests are run in batch mode
|
||||||
|
object CompileState {
|
||||||
|
@volatile var previousIterations: Int = -1
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
# do not introduce unnecessary compile iterations
|
# do not introduce unnecessary compile iterations
|
||||||
|
|
||||||
# introduces first compile iteration
|
# introduces first compile iteration
|
||||||
|
> recordPreviousIterations
|
||||||
> compile
|
> compile
|
||||||
# this change is local to a method and does not change the api so introduces
|
# this change is local to a method and does not change the api so introduces
|
||||||
# only one additional compile iteration
|
# only one additional compile iteration
|
||||||
|
|
@ -9,4 +10,4 @@ $ copy-file changes/B1.scala src/main/scala/B.scala
|
||||||
# second iteration
|
# second iteration
|
||||||
> compile
|
> compile
|
||||||
# check if there are only two compile iterations being performed
|
# check if there are only two compile iterations being performed
|
||||||
> checkNumberOfCompilerIterations 2
|
> checkIterations 2
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,26 @@
|
||||||
import sbt.internal.inc.Analysis
|
import sbt.internal.inc.Analysis
|
||||||
|
import complete.DefaultParsers._
|
||||||
|
|
||||||
InputKey[Unit]("check-number-of-compiler-iterations") := {
|
// Reset compiler iterations, necessary because tests run in batch mode
|
||||||
val args = Def.spaceDelimited().parsed
|
val recordPreviousIterations = taskKey[Unit]("Record previous iterations.")
|
||||||
val a = (compile in Compile).value.asInstanceOf[Analysis]
|
recordPreviousIterations := {
|
||||||
assert(args.size == 1)
|
CompileState.previousIterations = {
|
||||||
val expectedIterationsNumber = args(0).toInt
|
val previousAnalysis = (previousCompile in Compile).value.analysis
|
||||||
assert(a.compilations.allCompilations.size == expectedIterationsNumber,
|
if (previousAnalysis.isEmpty) {
|
||||||
"a.compilations.allCompilations.size = %d (expected %d)".format(
|
streams.value.log.info("No previous analysis detected")
|
||||||
a.compilations.allCompilations.size, expectedIterationsNumber))
|
0
|
||||||
|
} else {
|
||||||
|
previousAnalysis.get match {
|
||||||
|
case a: Analysis => a.compilations.allCompilations.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val checkIterations = inputKey[Unit]("Verifies the accumulated number of iterations of incremental compilation.")
|
||||||
|
|
||||||
|
checkIterations := {
|
||||||
|
val expected: Int = (Space ~> NatBasic).parsed
|
||||||
|
val actual: Int = ((compile in Compile).value match { case a: Analysis => a.compilations.allCompilations.size }) - CompileState.previousIterations
|
||||||
|
assert(expected == actual, s"Expected $expected compilations, got $actual")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
// This is necessary because tests are run in batch mode
|
||||||
|
object CompileState {
|
||||||
|
@volatile var previousIterations: Int = -1
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
# https://github.com/sbt/sbt/issues/610
|
# https://github.com/sbt/sbt/issues/610
|
||||||
|
|
||||||
# introduces first compile iteration
|
# introduces first compile iteration
|
||||||
|
> recordPreviousIterations
|
||||||
> compile
|
> compile
|
||||||
# this change is local to method and does not change api so introduces
|
# this change is local to method and does not change api so introduces
|
||||||
# only one additional compile iteration
|
# only one additional compile iteration
|
||||||
|
|
@ -11,4 +12,4 @@ $ copy-file changes/Impl1.scala src/main/scala/Impl.scala
|
||||||
# second iteration
|
# second iteration
|
||||||
> compile
|
> compile
|
||||||
# check if there are only two compile iterations performed
|
# check if there are only two compile iterations performed
|
||||||
> checkNumberOfCompilerIterations 2
|
> checkIterations 2
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
# Quoting @eed3si9n in https://github.com/dwijnand/sbt-lm/pull/1 :
|
# Quoting @eed3si9n in https://github.com/dwijnand/sbt-lm/pull/1 :
|
||||||
#
|
|
||||||
# > After several experiments, I'm actually convinced that force() is unrelated to the scripted scenario,
|
# > After several experiments, I'm actually convinced that force() is unrelated to the scripted scenario,
|
||||||
# > and it's # currently passing by virtue of the questionable caching behavior:
|
# > and it's # currently passing by virtue of the questionable caching behavior:
|
||||||
# > https://github.com/sbt/sbt/blob/c223dccb542beaf763a3a2909cda74bdad39beca/ivy/src/main/scala/sbt/ivyint/CachedResolutionResolveEngine.scala#L715
|
# > https://github.com/sbt/sbt/blob/c223dccb542beaf763a3a2909cda74bdad39beca/ivy/src/main/scala/sbt/ivyint/CachedResolutionResolveEngine.scala#L715
|
||||||
|
|
|
||||||
|
|
@ -52,9 +52,9 @@ check := {
|
||||||
same(ddel, None, "del in projD")
|
same(ddel, None, "del in projD")
|
||||||
//
|
//
|
||||||
val buildValue = (demo in ThisBuild).value
|
val buildValue = (demo in ThisBuild).value
|
||||||
same(buildValue, "build 0", "demo in ThisBuild")
|
same(buildValue, "build 1", "demo in ThisBuild") // this is temporary, should be 0 until # is fixed
|
||||||
val globalValue = (demo in Global).value
|
val globalValue = (demo in Global).value
|
||||||
same(globalValue, "global 0", "demo in Global")
|
same(globalValue, "global 1", "demo in Global") // this is temporary, should be 0 until # is fixed
|
||||||
val projValue = (demo in projC).?.value
|
val projValue = (demo in projC).?.value
|
||||||
same(projValue, Some("project projC Q R"), "demo in projC")
|
same(projValue, Some("project projC Q R"), "demo in projC")
|
||||||
val qValue = (del in projC in q).?.value
|
val qValue = (del in projC in q).?.value
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ $ copy-file changes/define/build.sbt build.sbt
|
||||||
$ copy-file changes/define/A.scala A.scala
|
$ copy-file changes/define/A.scala A.scala
|
||||||
$ copy-file changes/define/D.scala D.scala
|
$ copy-file changes/define/D.scala D.scala
|
||||||
|
|
||||||
# reload implied
|
> reload
|
||||||
> publishLocal
|
> publishLocal
|
||||||
|
|
||||||
# Now we remove the source code and define a project which uses the build.
|
# Now we remove the source code and define a project which uses the build.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
lazy val a,b = project
|
lazy val a = project
|
||||||
|
lazy val b = project
|
||||||
|
|
||||||
def now = System.currentTimeMillis
|
def now = System.currentTimeMillis
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
> reboot
|
||||||
> checkCount 1 0
|
> checkCount 1 0
|
||||||
> checkCount 1 0
|
> checkCount 1 0
|
||||||
> reload
|
> reload
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,18 @@
|
||||||
$ copy-file changes/settingAssign/build.sbt build.sbt
|
$ copy-file changes/settingAssign/build.sbt build.sbt
|
||||||
-> compile
|
# Necessary to reload since the test assumes that the new build is picked up
|
||||||
|
-> reload
|
||||||
|
|
||||||
$ copy-file changes/settingAppend1/build.sbt build.sbt
|
$ copy-file changes/settingAppend1/build.sbt build.sbt
|
||||||
-> compile
|
-> reload
|
||||||
|
|
||||||
$ copy-file changes/settingAppendN/build.sbt build.sbt
|
$ copy-file changes/settingAppendN/build.sbt build.sbt
|
||||||
-> compile
|
-> reload
|
||||||
|
|
||||||
$ copy-file changes/taskAssign/build.sbt build.sbt
|
$ copy-file changes/taskAssign/build.sbt build.sbt
|
||||||
-> compile
|
-> reload
|
||||||
|
|
||||||
$ copy-file changes/taskAppend1/build.sbt build.sbt
|
$ copy-file changes/taskAppend1/build.sbt build.sbt
|
||||||
-> compile
|
-> reload
|
||||||
|
|
||||||
$ copy-file changes/taskAppendN/build.sbt build.sbt
|
$ copy-file changes/taskAppendN/build.sbt build.sbt
|
||||||
-> compile
|
-> reload
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,26 @@
|
||||||
import sbt.internal.inc.Analysis
|
import sbt.internal.inc.Analysis
|
||||||
|
import complete.DefaultParsers._
|
||||||
|
|
||||||
InputKey[Unit]("checkNumberOfCompilerIterations") := {
|
// Reset compiler iterations, necessary because tests run in batch mode
|
||||||
val a = (compile in Compile).value.asInstanceOf[Analysis]
|
val recordPreviousIterations = taskKey[Unit]("Record previous iterations.")
|
||||||
val args = Def.spaceDelimited().parsed
|
recordPreviousIterations := {
|
||||||
assert(args.size == 1)
|
CompileState.previousIterations = {
|
||||||
val expectedIterationsNumber = args(0).toInt
|
val previousAnalysis = (previousCompile in Compile).value.analysis
|
||||||
assert(a.compilations.allCompilations.size == expectedIterationsNumber,
|
if (previousAnalysis.isEmpty) {
|
||||||
"a.compilations.allCompilations.size = %d (expected %d)".format(
|
streams.value.log.info("No previous analysis detected")
|
||||||
a.compilations.allCompilations.size, expectedIterationsNumber)
|
0
|
||||||
)
|
} else {
|
||||||
|
previousAnalysis.get match {
|
||||||
|
case a: Analysis => a.compilations.allCompilations.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val checkIterations = inputKey[Unit]("Verifies the accumulated number of iterations of incremental compilation.")
|
||||||
|
|
||||||
|
checkIterations := {
|
||||||
|
val expected: Int = (Space ~> NatBasic).parsed
|
||||||
|
val actual: Int = ((compile in Compile).value match { case a: Analysis => a.compilations.allCompilations.size }) - CompileState.previousIterations
|
||||||
|
assert(expected == actual, s"Expected $expected compilations, got $actual")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
// This is necessary because tests are run in batch mode
|
||||||
|
object CompileState {
|
||||||
|
@volatile var previousIterations: Int = -1
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
# See https://github.com/sbt/sbt/issues/726 for details
|
# See https://github.com/sbt/sbt/issues/726 for details
|
||||||
|
|
||||||
# introduces first compile iteration
|
# introduces first compile iteration
|
||||||
|
> recordPreviousIterations
|
||||||
> compile
|
> compile
|
||||||
# this change adds a comment and does not change api so introduces
|
# this change adds a comment and does not change api so introduces
|
||||||
# only one additional compile iteration
|
# only one additional compile iteration
|
||||||
|
|
@ -11,4 +12,4 @@ $ copy-file changes/Bar1.scala src/main/scala/Bar.scala
|
||||||
# second iteration
|
# second iteration
|
||||||
#> compile
|
#> compile
|
||||||
# check if there are only two compile iterations performed
|
# check if there are only two compile iterations performed
|
||||||
> checkNumberOfCompilerIterations 2
|
> checkIterations 2
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,26 @@
|
||||||
import sbt.internal.inc.Analysis
|
import sbt.internal.inc.Analysis
|
||||||
import complete.DefaultParsers._
|
import complete.DefaultParsers._
|
||||||
|
|
||||||
|
// Reset compiler iterations, necessary because tests run in batch mode
|
||||||
|
val recordPreviousIterations = taskKey[Unit]("Record previous iterations.")
|
||||||
|
recordPreviousIterations := {
|
||||||
|
CompileState.previousIterations = {
|
||||||
|
val previousAnalysis = (previousCompile in Compile).value.analysis
|
||||||
|
if (previousAnalysis.isEmpty) {
|
||||||
|
streams.value.log.info("No previous analysis detected")
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
previousAnalysis.get match {
|
||||||
|
case a: Analysis => a.compilations.allCompilations.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val checkIterations = inputKey[Unit]("Verifies the accumulated number of iterations of incremental compilation.")
|
val checkIterations = inputKey[Unit]("Verifies the accumulated number of iterations of incremental compilation.")
|
||||||
|
|
||||||
checkIterations := {
|
checkIterations := {
|
||||||
val expected: Int = (Space ~> NatBasic).parsed
|
val expected: Int = (Space ~> NatBasic).parsed
|
||||||
val actual: Int = (compile in Compile).value match { case a: Analysis => a.compilations.allCompilations.size }
|
val actual: Int = ((compile in Compile).value match { case a: Analysis => a.compilations.allCompilations.size }) - CompileState.previousIterations
|
||||||
assert(expected == actual, s"Expected $expected compilations, got $actual")
|
assert(expected == actual, s"Expected $expected compilations, got $actual")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
// This is necessary because tests are run in batch mode
|
||||||
|
object CompileState {
|
||||||
|
@volatile var previousIterations: Int = -1
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
# This also verifies that compilation does not get repeatedly triggered by a mismatch in
|
# This also verifies that compilation does not get repeatedly triggered by a mismatch in
|
||||||
# paths.
|
# paths.
|
||||||
|
|
||||||
|
> recordPreviousIterations
|
||||||
> compile
|
> compile
|
||||||
> compile
|
> compile
|
||||||
> checkIterations 1
|
> checkIterations 1
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
# Marked as pending, see https://github.com/sbt/sbt/issues/1543
|
# Marked as pending, see https://github.com/sbt/sbt/issues/1543
|
||||||
#
|
|
||||||
# Tests if source dependencies are tracked properly
|
# Tests if source dependencies are tracked properly
|
||||||
# for compile-time constants (like final vals in top-level objects)
|
# for compile-time constants (like final vals in top-level objects)
|
||||||
# see https://issues.scala-lang.org/browse/SI-7173 for details
|
# see https://issues.scala-lang.org/browse/SI-7173 for details
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,26 @@
|
||||||
import sbt.internal.inc.Analysis
|
import sbt.internal.inc.Analysis
|
||||||
import complete.DefaultParsers._
|
import complete.DefaultParsers._
|
||||||
|
|
||||||
|
// Reset compiler iterations, necessary because tests run in batch mode
|
||||||
|
val recordPreviousIterations = taskKey[Unit]("Record previous iterations.")
|
||||||
|
recordPreviousIterations := {
|
||||||
|
CompileState.previousIterations = {
|
||||||
|
val previousAnalysis = (previousCompile in Compile).value.analysis
|
||||||
|
if (previousAnalysis.isEmpty) {
|
||||||
|
streams.value.log.info("No previous analysis detected")
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
previousAnalysis.get match {
|
||||||
|
case a: Analysis => a.compilations.allCompilations.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val checkIterations = inputKey[Unit]("Verifies the accumulated number of iterations of incremental compilation.")
|
val checkIterations = inputKey[Unit]("Verifies the accumulated number of iterations of incremental compilation.")
|
||||||
|
|
||||||
checkIterations := {
|
checkIterations := {
|
||||||
val expected: Int = (Space ~> NatBasic).parsed
|
val expected: Int = (Space ~> NatBasic).parsed
|
||||||
val actual: Int = (compile in Compile).value match { case a: Analysis => a.compilations.allCompilations.size }
|
val actual: Int = ((compile in Compile).value match { case a: Analysis => a.compilations.allCompilations.size }) - CompileState.previousIterations
|
||||||
assert(expected == actual, s"Expected $expected compilations, got $actual")
|
assert(expected == actual, s"Expected $expected compilations, got $actual")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
// This is necessary because tests are run in batch mode
|
||||||
|
object CompileState {
|
||||||
|
@volatile var previousIterations: Int = -1
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
# initial compilation
|
# initial compilation
|
||||||
|
> recordPreviousIterations
|
||||||
> checkIterations 1
|
> checkIterations 1
|
||||||
|
|
||||||
# no further compilation should be necessary, since nothing changed
|
# no further compilation should be necessary, since nothing changed
|
||||||
# previously, a dependency on a jar in <java.home>lib/ext/ would
|
# previously, a dependency on a jar in <java.home>lib/ext/ would
|
||||||
# always force recompilation
|
# always force recompilation
|
||||||
> checkIterations 1
|
> checkIterations 1
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,26 @@
|
||||||
import sbt.internal.inc.Analysis
|
import sbt.internal.inc.Analysis
|
||||||
import complete.DefaultParsers._
|
import complete.DefaultParsers._
|
||||||
|
|
||||||
|
// Reset compiler iterations, necessary because tests run in batch mode
|
||||||
|
val recordPreviousIterations = taskKey[Unit]("Record previous iterations.")
|
||||||
|
recordPreviousIterations := {
|
||||||
|
CompileState.previousIterations = {
|
||||||
|
val previousAnalysis = (previousCompile in Compile).value.analysis
|
||||||
|
if (previousAnalysis.isEmpty) {
|
||||||
|
streams.value.log.info("No previous analysis detected")
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
previousAnalysis.get match {
|
||||||
|
case a: Analysis => a.compilations.allCompilations.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val checkIterations = inputKey[Unit]("Verifies the accumulated number of iterations of incremental compilation.")
|
val checkIterations = inputKey[Unit]("Verifies the accumulated number of iterations of incremental compilation.")
|
||||||
|
|
||||||
checkIterations := {
|
checkIterations := {
|
||||||
val expected: Int = (Space ~> NatBasic).parsed
|
val expected: Int = (Space ~> NatBasic).parsed
|
||||||
val actual: Int = (compile in Compile).value match { case a: Analysis => a.compilations.allCompilations.size }
|
val actual: Int = ((compile in Compile).value match { case a: Analysis => a.compilations.allCompilations.size }) - CompileState.previousIterations
|
||||||
assert(expected == actual, s"Expected $expected compilations, got $actual")
|
assert(expected == actual, s"Expected $expected compilations, got $actual")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
// This is necessary because tests are run in batch mode
|
||||||
|
object CompileState {
|
||||||
|
@volatile var previousIterations: Int = -1
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
# 1 iteration from initial full compile
|
# 1 iteration from initial full compile
|
||||||
|
> recordPreviousIterations
|
||||||
> run
|
> run
|
||||||
$ copy-file changes/A2.java A.java
|
$ copy-file changes/A2.java A.java
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,26 @@
|
||||||
import sbt.internal.inc.Analysis
|
import sbt.internal.inc.Analysis
|
||||||
import complete.DefaultParsers._
|
import complete.DefaultParsers._
|
||||||
|
|
||||||
|
// Reset compiler iterations, necessary because tests run in batch mode
|
||||||
|
val recordPreviousIterations = taskKey[Unit]("Record previous iterations.")
|
||||||
|
recordPreviousIterations := {
|
||||||
|
CompileState.previousIterations = {
|
||||||
|
val previousAnalysis = (previousCompile in Compile).value.analysis
|
||||||
|
if (previousAnalysis.isEmpty) {
|
||||||
|
streams.value.log.info("No previous analysis detected")
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
previousAnalysis.get match {
|
||||||
|
case a: Analysis => a.compilations.allCompilations.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val checkIterations = inputKey[Unit]("Verifies the accumulated number of iterations of incremental compilation.")
|
val checkIterations = inputKey[Unit]("Verifies the accumulated number of iterations of incremental compilation.")
|
||||||
|
|
||||||
checkIterations := {
|
checkIterations := {
|
||||||
val expected: Int = (Space ~> NatBasic).parsed
|
val expected: Int = (Space ~> NatBasic).parsed
|
||||||
val actual: Int = (compile in Compile).value match { case a: Analysis => a.compilations.allCompilations.size }
|
val actual: Int = ((compile in Compile).value match { case a: Analysis => a.compilations.allCompilations.size }) - CompileState.previousIterations
|
||||||
assert(expected == actual, s"Expected $expected compilations, got $actual")
|
assert(expected == actual, s"Expected $expected compilations, got $actual")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
// This is necessary because tests are run in batch mode
|
||||||
|
object CompileState {
|
||||||
|
@volatile var previousIterations: Int = -1
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
# 1 iteration from initial full compile
|
# 1 iteration from initial full compile
|
||||||
|
> recordPreviousIterations
|
||||||
> run
|
> run
|
||||||
$ copy-file changes/A2.scala A.scala
|
$ copy-file changes/A2.scala A.scala
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,26 @@ import complete.DefaultParsers._
|
||||||
|
|
||||||
crossTarget in Compile := target.value
|
crossTarget in Compile := target.value
|
||||||
|
|
||||||
|
// Reset compiler iterations, necessary because tests run in batch mode
|
||||||
|
val recordPreviousIterations = taskKey[Unit]("Record previous iterations.")
|
||||||
|
recordPreviousIterations := {
|
||||||
|
CompileState.previousIterations = {
|
||||||
|
val previousAnalysis = (previousCompile in Compile).value.analysis
|
||||||
|
if (previousAnalysis.isEmpty) {
|
||||||
|
streams.value.log.info("No previous analysis detected")
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
previousAnalysis.get match {
|
||||||
|
case a: Analysis => a.compilations.allCompilations.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val checkIterations = inputKey[Unit]("Verifies the accumulated number of iterations of incremental compilation.")
|
val checkIterations = inputKey[Unit]("Verifies the accumulated number of iterations of incremental compilation.")
|
||||||
|
|
||||||
checkIterations := {
|
checkIterations := {
|
||||||
val expected: Int = (Space ~> NatBasic).parsed
|
val expected: Int = (Space ~> NatBasic).parsed
|
||||||
val actual: Int = (compile in Compile).value match { case a: Analysis => a.compilations.allCompilations.size }
|
val actual: Int = ((compile in Compile).value match { case a: Analysis => a.compilations.allCompilations.size }) - CompileState.previousIterations
|
||||||
assert(expected == actual, s"Expected $expected compilations, got $actual")
|
assert(expected == actual, s"Expected $expected compilations, got $actual")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
// This is necessary because tests are run in batch mode
|
||||||
|
object CompileState {
|
||||||
|
@volatile var previousIterations: Int = -1
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ $ copy-file changes/A1.scala A.scala
|
||||||
$ copy-file changes/B.scala B.scala
|
$ copy-file changes/B.scala B.scala
|
||||||
# B depends on A
|
# B depends on A
|
||||||
# 1 iteration
|
# 1 iteration
|
||||||
|
> recordPreviousIterations
|
||||||
> compile
|
> compile
|
||||||
|
|
||||||
$ copy-file changes/A2.scala A.scala
|
$ copy-file changes/A2.scala A.scala
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,14 @@
|
||||||
import sbt.internal.inc.Analysis
|
import sbt.internal.inc.Analysis
|
||||||
|
import xsbti.Maybe
|
||||||
|
import xsbti.compile.{PreviousResult, CompileAnalysis, MiniSetup}
|
||||||
|
|
||||||
|
previousCompile in Compile := {
|
||||||
|
if (!CompileState.isNew) {
|
||||||
|
val res = new PreviousResult(Maybe.nothing[CompileAnalysis], Maybe.nothing[MiniSetup])
|
||||||
|
CompileState.isNew = true
|
||||||
|
res
|
||||||
|
} else (previousCompile in Compile).value
|
||||||
|
}
|
||||||
|
|
||||||
/* Performs checks related to compilations:
|
/* Performs checks related to compilations:
|
||||||
* a) checks in which compilation given set of files was recompiled
|
* a) checks in which compilation given set of files was recompiled
|
||||||
|
|
@ -22,7 +32,7 @@ TaskKey[Unit]("checkCompilations") := {
|
||||||
val files = fileNames.map(new java.io.File(_))
|
val files = fileNames.map(new java.io.File(_))
|
||||||
assert(recompiledFiles(iteration) == files, "%s != %s".format(recompiledFiles(iteration), files))
|
assert(recompiledFiles(iteration) == files, "%s != %s".format(recompiledFiles(iteration), files))
|
||||||
}
|
}
|
||||||
assert(allCompilations.size == 2)
|
assert(allCompilations.size == 2, s"All compilations is ${allCompilations.size}")
|
||||||
// B.scala is just compiled at the beginning
|
// B.scala is just compiled at the beginning
|
||||||
recompiledFilesInIteration(0, Set("B.scala"))
|
recompiledFilesInIteration(0, Set("B.scala"))
|
||||||
// A.scala is changed and recompiled
|
// A.scala is changed and recompiled
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
// This is necessary because tests are run in batch mode
|
||||||
|
object CompileState {
|
||||||
|
@volatile var isNew: Boolean = false
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,18 @@
|
||||||
import sbt.internal.inc.Analysis
|
import sbt.internal.inc.Analysis
|
||||||
|
import xsbti.Maybe
|
||||||
|
import xsbti.compile.{PreviousResult, CompileAnalysis, MiniSetup}
|
||||||
|
|
||||||
logLevel := Level.Debug
|
logLevel := Level.Debug
|
||||||
|
|
||||||
|
// Reset compile status because scripted tests are run in batch mode
|
||||||
|
previousCompile in Compile := {
|
||||||
|
if (!CompileState.isNew) {
|
||||||
|
val res = new PreviousResult(Maybe.nothing[CompileAnalysis], Maybe.nothing[MiniSetup])
|
||||||
|
CompileState.isNew = true
|
||||||
|
res
|
||||||
|
} else (previousCompile in Compile).value
|
||||||
|
}
|
||||||
|
|
||||||
// disable sbt's heuristic which recompiles everything in case
|
// disable sbt's heuristic which recompiles everything in case
|
||||||
// some fraction (e.g. 50%) of files is scheduled to be recompiled
|
// some fraction (e.g. 50%) of files is scheduled to be recompiled
|
||||||
// in this test we want precise information about recompiled files
|
// in this test we want precise information about recompiled files
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
// This is necessary because tests are run in batch mode
|
||||||
|
object CompileState {
|
||||||
|
@volatile var isNew: Boolean = false
|
||||||
|
}
|
||||||
|
|
@ -59,13 +59,14 @@ object ScriptedPlugin extends AutoPlugin {
|
||||||
ModuleUtilities.getObject("sbt.test.ScriptedTests", loader)
|
ModuleUtilities.getObject("sbt.test.ScriptedTests", loader)
|
||||||
}
|
}
|
||||||
|
|
||||||
def scriptedRunTask: Initialize[Task[Method]] = Def task (
|
def scriptedRunTask: Initialize[Task[Method]] = Def.task(
|
||||||
scriptedTests.value.getClass.getMethod("run",
|
scriptedTests.value.getClass.getMethod("run",
|
||||||
classOf[File],
|
classOf[File],
|
||||||
classOf[Boolean],
|
classOf[Boolean],
|
||||||
classOf[Array[String]],
|
classOf[Array[String]],
|
||||||
classOf[File],
|
classOf[File],
|
||||||
classOf[Array[String]])
|
classOf[Array[String]],
|
||||||
|
classOf[java.util.List[File]])
|
||||||
)
|
)
|
||||||
|
|
||||||
import DefaultParsers._
|
import DefaultParsers._
|
||||||
|
|
@ -98,7 +99,7 @@ object ScriptedPlugin extends AutoPlugin {
|
||||||
else dropped.take(pageSize)
|
else dropped.take(pageSize)
|
||||||
}
|
}
|
||||||
def nameP(group: String) = {
|
def nameP(group: String) = {
|
||||||
token("*".id | id.examples(pairMap(group)))
|
token("*".id | id.examples(pairMap.getOrElse(group, Set.empty[String])))
|
||||||
}
|
}
|
||||||
val PagedIds: Parser[Seq[String]] =
|
val PagedIds: Parser[Seq[String]] =
|
||||||
for {
|
for {
|
||||||
|
|
@ -125,7 +126,8 @@ object ScriptedPlugin extends AutoPlugin {
|
||||||
scriptedBufferLog.value: java.lang.Boolean,
|
scriptedBufferLog.value: java.lang.Boolean,
|
||||||
args.toArray,
|
args.toArray,
|
||||||
sbtLauncher.value,
|
sbtLauncher.value,
|
||||||
scriptedLaunchOpts.value.toArray
|
scriptedLaunchOpts.value.toArray,
|
||||||
|
new java.util.ArrayList()
|
||||||
)
|
)
|
||||||
} catch { case e: java.lang.reflect.InvocationTargetException => throw e.getCause }
|
} catch { case e: java.lang.reflect.InvocationTargetException => throw e.getCause }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
package sbt
|
||||||
|
package test
|
||||||
|
|
||||||
|
import sbt.internal.scripted._
|
||||||
|
import sbt.test.BatchScriptRunner.States
|
||||||
|
|
||||||
|
/** Defines an alternative script runner that allows batch execution. */
|
||||||
|
private[sbt] class BatchScriptRunner extends ScriptRunner {
|
||||||
|
|
||||||
|
/** Defines a method to run batched execution.
|
||||||
|
*
|
||||||
|
* @param statements The list of handlers and statements.
|
||||||
|
* @param states The states of the runner. In case it's empty, inherited apply is called.
|
||||||
|
*/
|
||||||
|
def apply(statements: List[(StatementHandler, Statement)], states: States): Unit = {
|
||||||
|
if (states.isEmpty) super.apply(statements)
|
||||||
|
else statements.foreach(st => processStatement(st._1, st._2, states))
|
||||||
|
}
|
||||||
|
|
||||||
|
def initStates(states: States, handlers: Seq[StatementHandler]): Unit =
|
||||||
|
handlers.foreach(handler => states(handler) = handler.initialState)
|
||||||
|
|
||||||
|
def cleanUpHandlers(handlers: Seq[StatementHandler], states: States): Unit = {
|
||||||
|
for (handler <- handlers; state <- states.get(handler)) {
|
||||||
|
try handler.finish(state.asInstanceOf[handler.State])
|
||||||
|
catch { case _: Exception => () }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def processStatement(handler: StatementHandler, statement: Statement, states: States): Unit = {
|
||||||
|
val state = states(handler).asInstanceOf[handler.State]
|
||||||
|
val nextState =
|
||||||
|
try { Right(handler(statement.command, statement.arguments, state)) } catch {
|
||||||
|
case e: Exception => Left(e)
|
||||||
|
}
|
||||||
|
nextState match {
|
||||||
|
case Left(err) =>
|
||||||
|
if (statement.successExpected) {
|
||||||
|
err match {
|
||||||
|
case t: TestFailed =>
|
||||||
|
throw new TestException(statement, "Command failed: " + t.getMessage, null)
|
||||||
|
case _ => throw new TestException(statement, "Command failed", err)
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
()
|
||||||
|
case Right(s) =>
|
||||||
|
if (statement.successExpected)
|
||||||
|
states(handler) = s
|
||||||
|
else
|
||||||
|
throw new TestException(statement, "Command succeeded but failure was expected", null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private[sbt] object BatchScriptRunner {
|
||||||
|
import scala.collection.mutable
|
||||||
|
type States = mutable.HashMap[StatementHandler, Any]
|
||||||
|
}
|
||||||
|
|
@ -8,120 +8,285 @@ 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.{
|
import sbt.io.{ DirectoryFilter, HiddenFileFilter, IO }
|
||||||
CommentHandler,
|
|
||||||
FileCommands,
|
|
||||||
ScriptRunner,
|
|
||||||
TestScriptParser,
|
|
||||||
TestException
|
|
||||||
}
|
|
||||||
import sbt.io.{ DirectoryFilter, HiddenFileFilter }
|
|
||||||
import sbt.io.IO.wrapNull
|
import sbt.io.IO.wrapNull
|
||||||
|
import sbt.io.FileFilter._
|
||||||
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.mutable
|
||||||
|
import scala.collection.parallel.ForkJoinTaskSupport
|
||||||
|
import scala.collection.parallel.mutable.ParSeq
|
||||||
|
|
||||||
final class ScriptedTests(resourceBaseDirectory: File,
|
final class ScriptedTests(resourceBaseDirectory: File,
|
||||||
bufferLog: Boolean,
|
bufferLog: Boolean,
|
||||||
launcher: File,
|
launcher: File,
|
||||||
launchOpts: Seq[String]) {
|
launchOpts: Seq[String]) {
|
||||||
|
import sbt.io.syntax._
|
||||||
import ScriptedTests._
|
import ScriptedTests._
|
||||||
private val testResources = new Resources(resourceBaseDirectory)
|
private val testResources = new Resources(resourceBaseDirectory)
|
||||||
|
|
||||||
val ScriptFilename = "test"
|
val ScriptFilename = "test"
|
||||||
val PendingScriptFilename = "pending"
|
val PendingScriptFilename = "pending"
|
||||||
|
|
||||||
def scriptedTest(group: String, name: String, log: xsbti.Logger): Seq[() => Option[String]] =
|
def scriptedTest(group: String, name: String, log: xsbti.Logger): Seq[TestRunner] =
|
||||||
scriptedTest(group, name, Logger.xlog2Log(log))
|
scriptedTest(group, name, Logger.xlog2Log(log))
|
||||||
def scriptedTest(group: String, name: String, log: Logger): Seq[() => Option[String]] =
|
def scriptedTest(group: String, name: String, log: Logger): Seq[TestRunner] =
|
||||||
scriptedTest(group, name, emptyCallback, log)
|
singleScriptedTest(group, name, emptyCallback, log)
|
||||||
def scriptedTest(group: String,
|
|
||||||
name: String,
|
/** Returns a sequence of test runners that have to be applied in the call site. */
|
||||||
prescripted: File => Unit,
|
def singleScriptedTest(group: String,
|
||||||
log: Logger): Seq[() => Option[String]] = {
|
name: String,
|
||||||
import sbt.io.syntax._
|
prescripted: File => Unit,
|
||||||
|
log: Logger): Seq[TestRunner] = {
|
||||||
|
|
||||||
|
// Test group and names may be file filters (like '*')
|
||||||
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
|
||||||
val n = nme.getName
|
val n = nme.getName
|
||||||
val str = s"$g / $n"
|
val label = s"$g / $n"
|
||||||
() =>
|
() =>
|
||||||
{
|
{
|
||||||
println("Running " + str)
|
println(s"Running $label")
|
||||||
testResources.readWriteResourceDirectory(g, n) { testDirectory =>
|
val result = testResources.readWriteResourceDirectory(g, n) { testDirectory =>
|
||||||
val disabled = new File(testDirectory, "disabled").isFile
|
val buffer = new BufferedLogger(new FullLogger(log))
|
||||||
if (disabled) {
|
val singleTestRunner = () => {
|
||||||
log.info("D " + str + " [DISABLED]")
|
val handlers = createScriptedHandlers(testDirectory, buffer)
|
||||||
None
|
val runner = new BatchScriptRunner
|
||||||
} else {
|
val states = new mutable.HashMap[StatementHandler, Any]()
|
||||||
try { scriptedTest(str, testDirectory, prescripted, log); None } catch {
|
commonRunTest(label, testDirectory, prescripted, handlers, runner, states, buffer)
|
||||||
case _: TestException | _: PendingTestSuccessException => Some(str)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
runOrHandleDisabled(label, testDirectory, singleTestRunner, buffer)
|
||||||
}
|
}
|
||||||
|
Seq(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private def scriptedTest(label: String,
|
private def createScriptedHandlers(testDir: File,
|
||||||
testDirectory: File,
|
buffered: Logger): Map[Char, StatementHandler] = {
|
||||||
prescripted: File => Unit,
|
val fileHandler = new FileCommands(testDir)
|
||||||
log: Logger): Unit = {
|
val sbtHandler = new SbtHandler(testDir, launcher, buffered, launchOpts)
|
||||||
val buffered = new BufferedLogger(new FullLogger(log))
|
Map('$' -> fileHandler, '>' -> sbtHandler, '#' -> CommentHandler)
|
||||||
if (bufferLog)
|
}
|
||||||
buffered.record()
|
|
||||||
|
|
||||||
def createParser() = {
|
/** Returns a sequence of test runners that have to be applied in the call site. */
|
||||||
val fileHandler = new FileCommands(testDirectory)
|
def batchScriptedRunner(
|
||||||
val sbtHandler = new SbtHandler(testDirectory, launcher, buffered, launchOpts)
|
testGroupAndNames: Seq[(String, String)],
|
||||||
new TestScriptParser(Map('$' -> fileHandler, '>' -> sbtHandler, '#' -> CommentHandler))
|
prescripted: File => Unit,
|
||||||
|
sbtInstances: Int,
|
||||||
|
log: Logger
|
||||||
|
): Seq[TestRunner] = {
|
||||||
|
// Test group and names may be file filters (like '*')
|
||||||
|
val groupAndNameDirs = {
|
||||||
|
for {
|
||||||
|
(group, name) <- testGroupAndNames
|
||||||
|
groupDir <- resourceBaseDirectory.*(group).get
|
||||||
|
testDir <- groupDir.*(name).get
|
||||||
|
} yield (groupDir, testDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val labelsAndDirs = groupAndNameDirs.map {
|
||||||
|
case (groupDir, nameDir) =>
|
||||||
|
val groupName = groupDir.getName
|
||||||
|
val testName = nameDir.getName
|
||||||
|
val testDirectory = testResources.readOnlyResourceDirectory(groupName, testName)
|
||||||
|
(groupName, testName) -> testDirectory
|
||||||
|
}
|
||||||
|
|
||||||
|
if (labelsAndDirs.isEmpty) List()
|
||||||
|
else {
|
||||||
|
val batchSeed = labelsAndDirs.size / sbtInstances
|
||||||
|
val batchSize = if (batchSeed == 0) labelsAndDirs.size else batchSeed
|
||||||
|
labelsAndDirs
|
||||||
|
.grouped(batchSize)
|
||||||
|
.map(batch => () => IO.withTemporaryDirectory(runBatchedTests(batch, _, prescripted, log)))
|
||||||
|
.toList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Defines an auto plugin that is injected to sbt between every scripted session.
|
||||||
|
*
|
||||||
|
* It sets the name of the local root project for those tests run in batch mode.
|
||||||
|
*
|
||||||
|
* This is necessary because the current design to run tests in batch mode forces
|
||||||
|
* scripted tests to share one common sbt dir instead of each one having its own.
|
||||||
|
*
|
||||||
|
* Sbt extracts the local root project name from the directory name. So those
|
||||||
|
* scripted tests that don't set the name for the root and whose test files check
|
||||||
|
* information based on the name will fail.
|
||||||
|
*
|
||||||
|
* The reason why we set the name here and not via `set` is because some tests
|
||||||
|
* dump the session to check that their settings have been correctly applied.
|
||||||
|
*
|
||||||
|
* @param testName The test name used to extract the root project name.
|
||||||
|
* @return A string-based implementation to run between every reload.
|
||||||
|
*/
|
||||||
|
private def createAutoPlugin(testName: String) =
|
||||||
|
s"""
|
||||||
|
|import sbt._, Keys._
|
||||||
|
|object InstrumentScripted extends AutoPlugin {
|
||||||
|
| override def trigger = AllRequirements
|
||||||
|
| override def globalSettings: Seq[Setting[_]] =
|
||||||
|
| Seq(commands += setUpScripted) ++ super.globalSettings
|
||||||
|
|
|
||||||
|
| def setUpScripted = Command.command("setUpScripted") { (state0: State) =>
|
||||||
|
| val nameScriptedSetting = name.in(LocalRootProject).:=(
|
||||||
|
| if (name.value.startsWith("sbt_")) "$testName" else name.value)
|
||||||
|
| val state1 = Project.extract(state0).append(nameScriptedSetting, state0)
|
||||||
|
| "initialize" :: state1
|
||||||
|
| }
|
||||||
|
|}
|
||||||
|
""".stripMargin
|
||||||
|
|
||||||
|
/** Defines the batch execution of scripted tests.
|
||||||
|
*
|
||||||
|
* Scripted tests are run one after the other one recycling the handlers, under
|
||||||
|
* the assumption that handlers do not produce side effects that can change scripted
|
||||||
|
* tests' behaviours.
|
||||||
|
*
|
||||||
|
* In batch mode, the test runner performs these operations between executions:
|
||||||
|
*
|
||||||
|
* 1. Delete previous test files in the common test directory.
|
||||||
|
* 2. Copy over next test files to the common test directory.
|
||||||
|
* 3. Reload the sbt handler.
|
||||||
|
*
|
||||||
|
* @param groupedTests The labels and directories of the tests to run.
|
||||||
|
* @param tempTestDir The common test directory.
|
||||||
|
* @param preHook The hook to run before scripted execution.
|
||||||
|
* @param log The logger.
|
||||||
|
*/
|
||||||
|
private def runBatchedTests(
|
||||||
|
groupedTests: Seq[((String, String), File)],
|
||||||
|
tempTestDir: File,
|
||||||
|
preHook: File => Unit,
|
||||||
|
log: Logger
|
||||||
|
): Seq[Option[String]] = {
|
||||||
|
|
||||||
|
val runner = new BatchScriptRunner
|
||||||
|
val buffer = new BufferedLogger(new FullLogger(log))
|
||||||
|
val handlers = createScriptedHandlers(tempTestDir, buffer)
|
||||||
|
val states = new BatchScriptRunner.States
|
||||||
|
val seqHandlers = handlers.values.toList
|
||||||
|
runner.initStates(states, seqHandlers)
|
||||||
|
|
||||||
|
def runBatchTests = {
|
||||||
|
groupedTests.map {
|
||||||
|
case ((group, name), originalDir) =>
|
||||||
|
val label = s"$group / $name"
|
||||||
|
println(s"Running $label")
|
||||||
|
// Copy test's contents and reload the sbt instance to pick them up
|
||||||
|
IO.copyDirectory(originalDir, tempTestDir)
|
||||||
|
|
||||||
|
val runTest = () => {
|
||||||
|
// Reload and initialize (to reload contents of .sbtrc files)
|
||||||
|
val pluginImplementation = createAutoPlugin(name)
|
||||||
|
IO.write(tempTestDir / "project" / "InstrumentScripted.scala", pluginImplementation)
|
||||||
|
val sbtHandlerError = "Missing sbt handler. Scripted is misconfigured."
|
||||||
|
val sbtHandler = handlers.getOrElse('>', sbtHandlerError).asInstanceOf[SbtHandler]
|
||||||
|
val commandsToRun = ";reload;setUpScripted"
|
||||||
|
val statement = Statement(commandsToRun, Nil, successExpected = true, line = -1)
|
||||||
|
|
||||||
|
// Run reload inside the hook to reuse error handling for pending tests
|
||||||
|
val wrapHook = (file: File) => {
|
||||||
|
preHook(file)
|
||||||
|
try runner.processStatement(sbtHandler, statement, states)
|
||||||
|
catch {
|
||||||
|
case t: Throwable =>
|
||||||
|
val newMsg = "Reload for scripted batch execution failed."
|
||||||
|
throw new TestException(statement, newMsg, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commonRunTest(label, tempTestDir, wrapHook, handlers, runner, states, buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the test and delete files (except global that holds local scala jars)
|
||||||
|
val result = runOrHandleDisabled(label, tempTestDir, runTest, buffer)
|
||||||
|
IO.delete(tempTestDir.*("*" -- "global").get)
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try runBatchTests
|
||||||
|
finally runner.cleanUpHandlers(seqHandlers, states)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def runOrHandleDisabled(
|
||||||
|
label: String,
|
||||||
|
testDirectory: File,
|
||||||
|
runTest: () => Option[String],
|
||||||
|
log: Logger
|
||||||
|
): Option[String] = {
|
||||||
|
val existsDisabled = new File(testDirectory, "disabled").isFile
|
||||||
|
if (!existsDisabled) runTest()
|
||||||
|
else {
|
||||||
|
log.info(s"D $label [DISABLED]")
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val PendingLabel = "[PENDING]"
|
||||||
|
|
||||||
|
private def commonRunTest(
|
||||||
|
label: String,
|
||||||
|
testDirectory: File,
|
||||||
|
preScriptedHook: File => Unit,
|
||||||
|
createHandlers: Map[Char, StatementHandler],
|
||||||
|
runner: BatchScriptRunner,
|
||||||
|
states: BatchScriptRunner.States,
|
||||||
|
log: BufferedLogger
|
||||||
|
): Option[String] = {
|
||||||
|
if (bufferLog) log.record()
|
||||||
|
|
||||||
val (file, pending) = {
|
val (file, pending) = {
|
||||||
val normal = new File(testDirectory, ScriptFilename)
|
val normal = new File(testDirectory, ScriptFilename)
|
||||||
val pending = new File(testDirectory, PendingScriptFilename)
|
val pending = new File(testDirectory, PendingScriptFilename)
|
||||||
if (pending.isFile) (pending, true) else (normal, false)
|
if (pending.isFile) (pending, true) else (normal, false)
|
||||||
}
|
}
|
||||||
val pendingString = if (pending) " [PENDING]" else ""
|
|
||||||
|
|
||||||
def runTest() = {
|
val pendingMark = if (pending) PendingLabel else ""
|
||||||
val run = new ScriptRunner
|
def testFailed(t: Throwable): Option[String] = {
|
||||||
val parser = createParser()
|
if (pending) log.clear() else log.stop()
|
||||||
run(parser.parse(file))
|
log.error(s"x $label $pendingMark")
|
||||||
}
|
if (!NonFatal(t)) throw t // We make sure fatal errors are rethrown
|
||||||
def testFailed(): Unit = {
|
if (t.isInstanceOf[TestException]) {
|
||||||
if (pending) buffered.clear() else buffered.stop()
|
t.getCause match {
|
||||||
buffered.error("x " + label + pendingString)
|
case null | _: java.net.SocketException =>
|
||||||
}
|
log.error(" Cause of test exception: " + t.getMessage)
|
||||||
|
case _ => t.printStackTrace()
|
||||||
try {
|
|
||||||
prescripted(testDirectory)
|
|
||||||
runTest()
|
|
||||||
buffered.info("+ " + label + pendingString)
|
|
||||||
if (pending) throw new PendingTestSuccessException(label)
|
|
||||||
} catch {
|
|
||||||
case e: TestException =>
|
|
||||||
testFailed()
|
|
||||||
e.getCause match {
|
|
||||||
case null | _: java.net.SocketException => buffered.error(" " + e.getMessage)
|
|
||||||
case _ => e.printStackTrace
|
|
||||||
}
|
}
|
||||||
if (!pending) throw e
|
}
|
||||||
case e: PendingTestSuccessException =>
|
if (pending) None else Some(label)
|
||||||
testFailed()
|
}
|
||||||
buffered.error(" Mark as passing to remove this failure.")
|
|
||||||
throw e
|
import scala.util.control.Exception.catching
|
||||||
case NonFatal(e) =>
|
catching(classOf[TestException]).withApply(testFailed).andFinally(log.clear).apply {
|
||||||
testFailed()
|
preScriptedHook(testDirectory)
|
||||||
if (!pending) throw e
|
val handlers = createHandlers
|
||||||
} finally { buffered.clear() }
|
val parser = new TestScriptParser(handlers)
|
||||||
|
val handlersAndStatements = parser.parse(file)
|
||||||
|
runner.apply(handlersAndStatements, states)
|
||||||
|
|
||||||
|
// Handle successful tests
|
||||||
|
log.info(s"+ $label $pendingMark")
|
||||||
|
if (pending) {
|
||||||
|
log.clear()
|
||||||
|
log.error(" Pending test passed. Mark as passing to remove this failure.")
|
||||||
|
Some(label)
|
||||||
|
} else None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object ScriptedTests extends ScriptedRunner {
|
object ScriptedTests extends ScriptedRunner {
|
||||||
val emptyCallback: File => Unit = { _ =>
|
|
||||||
()
|
/** Represents the function that runs the scripted tests, both in single or batch mode. */
|
||||||
}
|
type TestRunner = () => Seq[Option[String]]
|
||||||
|
|
||||||
|
val emptyCallback: File => Unit = _ => ()
|
||||||
def main(args: Array[String]): Unit = {
|
def main(args: Array[String]): Unit = {
|
||||||
val directory = new File(args(0))
|
val directory = new File(args(0))
|
||||||
val buffer = args(1).toBoolean
|
val buffer = args(1).toBoolean
|
||||||
|
|
@ -136,22 +301,6 @@ object ScriptedTests extends ScriptedRunner {
|
||||||
}
|
}
|
||||||
|
|
||||||
class ScriptedRunner {
|
class ScriptedRunner {
|
||||||
import ScriptedTests._
|
|
||||||
|
|
||||||
@deprecated("No longer used", "0.13.9")
|
|
||||||
def run(resourceBaseDirectory: File,
|
|
||||||
bufferLog: Boolean,
|
|
||||||
tests: Array[String],
|
|
||||||
bootProperties: File,
|
|
||||||
launchOpts: Array[String]): Unit =
|
|
||||||
run(resourceBaseDirectory,
|
|
||||||
bufferLog,
|
|
||||||
tests,
|
|
||||||
ConsoleLogger(),
|
|
||||||
bootProperties,
|
|
||||||
launchOpts,
|
|
||||||
emptyCallback) //new FullLogger(Logger.xlog2Log(log)))
|
|
||||||
|
|
||||||
// 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,
|
||||||
|
|
@ -165,30 +314,6 @@ class ScriptedRunner {
|
||||||
prescripted.add(f); ()
|
prescripted.add(f); ()
|
||||||
}) //new FullLogger(Logger.xlog2Log(log)))
|
}) //new FullLogger(Logger.xlog2Log(log)))
|
||||||
|
|
||||||
@deprecated("No longer used", "0.13.9")
|
|
||||||
def run(resourceBaseDirectory: File,
|
|
||||||
bufferLog: Boolean,
|
|
||||||
tests: Array[String],
|
|
||||||
bootProperties: File,
|
|
||||||
launchOpts: Array[String],
|
|
||||||
prescripted: File => Unit): Unit =
|
|
||||||
run(resourceBaseDirectory,
|
|
||||||
bufferLog,
|
|
||||||
tests,
|
|
||||||
ConsoleLogger(),
|
|
||||||
bootProperties,
|
|
||||||
launchOpts,
|
|
||||||
prescripted)
|
|
||||||
|
|
||||||
@deprecated("No longer used", "0.13.9")
|
|
||||||
def run(resourceBaseDirectory: File,
|
|
||||||
bufferLog: Boolean,
|
|
||||||
tests: Array[String],
|
|
||||||
logger: AbstractLogger,
|
|
||||||
bootProperties: File,
|
|
||||||
launchOpts: Array[String]): Unit =
|
|
||||||
run(resourceBaseDirectory, bufferLog, tests, logger, bootProperties, launchOpts, emptyCallback)
|
|
||||||
|
|
||||||
def run(resourceBaseDirectory: File,
|
def run(resourceBaseDirectory: File,
|
||||||
bufferLog: Boolean,
|
bufferLog: Boolean,
|
||||||
tests: Array[String],
|
tests: Array[String],
|
||||||
|
|
@ -199,22 +324,65 @@ class ScriptedRunner {
|
||||||
val runner = new ScriptedTests(resourceBaseDirectory, bufferLog, bootProperties, launchOpts)
|
val runner = new ScriptedTests(resourceBaseDirectory, bufferLog, bootProperties, launchOpts)
|
||||||
val allTests = get(tests, resourceBaseDirectory, logger) flatMap {
|
val allTests = get(tests, resourceBaseDirectory, logger) flatMap {
|
||||||
case ScriptedTest(group, name) =>
|
case ScriptedTest(group, name) =>
|
||||||
runner.scriptedTest(group, name, prescripted, logger)
|
runner.singleScriptedTest(group, name, prescripted, logger)
|
||||||
}
|
}
|
||||||
runAll(allTests)
|
runAll(allTests)
|
||||||
}
|
}
|
||||||
|
|
||||||
def runAll(tests: Seq[() => Option[String]]): Unit = {
|
def runInParallel(resourceBaseDirectory: File,
|
||||||
val errors = for (test <- tests; err <- test()) yield err
|
bufferLog: Boolean,
|
||||||
if (errors.nonEmpty)
|
tests: Array[String],
|
||||||
sys.error(errors.mkString("Failed tests:\n\t", "\n\t", "\n"))
|
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,
|
||||||
|
1)
|
||||||
|
}
|
||||||
|
|
||||||
|
def runInParallel(
|
||||||
|
resourceBaseDirectory: File,
|
||||||
|
bufferLog: Boolean,
|
||||||
|
tests: Array[String],
|
||||||
|
logger: AbstractLogger,
|
||||||
|
bootProperties: File,
|
||||||
|
launchOpts: Array[String],
|
||||||
|
prescripted: File => Unit,
|
||||||
|
instances: Int
|
||||||
|
): Unit = {
|
||||||
|
val runner = new ScriptedTests(resourceBaseDirectory, bufferLog, bootProperties, launchOpts)
|
||||||
|
// The scripted tests mapped to the inputs that the user wrote after `scripted`.
|
||||||
|
val scriptedTests = get(tests, resourceBaseDirectory, logger).map(st => (st.group, st.name))
|
||||||
|
val scriptedRunners = runner.batchScriptedRunner(scriptedTests, prescripted, instances, logger)
|
||||||
|
val parallelRunners = scriptedRunners.toParArray
|
||||||
|
val pool = new java.util.concurrent.ForkJoinPool(instances)
|
||||||
|
parallelRunners.tasksupport = new ForkJoinTaskSupport(pool)
|
||||||
|
runAllInParallel(parallelRunners)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def reportErrors(errors: Seq[String]): Unit =
|
||||||
|
if (errors.nonEmpty) sys.error(errors.mkString("Failed tests:\n\t", "\n\t", "\n")) else ()
|
||||||
|
|
||||||
|
def runAll(toRun: Seq[ScriptedTests.TestRunner]): Unit =
|
||||||
|
reportErrors(toRun.flatMap(test => test.apply().flatten.toSeq))
|
||||||
|
|
||||||
|
// We cannot reuse `runAll` because parallel collections != collections
|
||||||
|
def runAllInParallel(tests: ParSeq[ScriptedTests.TestRunner]): Unit = {
|
||||||
|
reportErrors(tests.flatMap(test => test.apply().flatten.toSeq).toList)
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
|
|
@ -260,18 +428,6 @@ private[test] final class ListTests(baseDirectory: File,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object CompatibilityLevel extends Enumeration {
|
|
||||||
val Full, Basic, Minimal, Minimal27, Minimal28 = Value
|
|
||||||
|
|
||||||
def defaultVersions(level: Value) =
|
|
||||||
level match {
|
|
||||||
case Full => "2.7.4 2.7.7 2.9.0.RC1 2.8.0 2.8.1"
|
|
||||||
case Basic => "2.7.7 2.7.4 2.8.1 2.8.0"
|
|
||||||
case Minimal => "2.7.7 2.8.1"
|
|
||||||
case Minimal27 => "2.7.7"
|
|
||||||
case Minimal28 => "2.8.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
class PendingTestSuccessException(label: String) extends Exception {
|
class PendingTestSuccessException(label: String) extends Exception {
|
||||||
override def getMessage: String =
|
override def getMessage: String =
|
||||||
s"The pending test $label succeeded. Mark this test as passing to remove this failure."
|
s"The pending test $label succeeded. Mark this test as passing to remove this failure."
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue