When serially executing tests*, order tests by testOnly filter order

* parallelExecution in test := false

cc @bantonsson
This commit is contained in:
Mark Harrah 2013-02-13 10:27:36 -05:00
parent b152a836c5
commit ae74fff88c
9 changed files with 52 additions and 18 deletions

View File

@ -30,6 +30,8 @@ object Tests
final case class Exclude(tests: Iterable[String]) extends TestOption
final case class Listeners(listeners: Iterable[TestReportListener]) extends TestOption
final case class Filter(filterTest: String => Boolean) extends TestOption
/** Test execution will be ordered by the position of the matching filter. */
final case class Filters(filterTest: Seq[String => Boolean]) extends TestOption
// args for all frameworks
def Argument(args: String*): Argument = Argument(None, args.toList)
@ -45,6 +47,7 @@ object Tests
{
import collection.mutable.{HashSet, ListBuffer, Map, Set}
val testFilters = new ListBuffer[String => Boolean]
var orderedFilters = Seq[String => Boolean]()
val excludeTestsSet = new HashSet[String]
val setup, cleanup = new ListBuffer[ClassLoader => Unit]
val testListeners = new ListBuffer[TestReportListener]
@ -63,6 +66,7 @@ object Tests
option match
{
case Filter(include) => testFilters += include
case Filters(includes) => if(!orderedFilters.isEmpty) error("Cannot define multiple ordered test filters.") else orderedFilters = includes
case Exclude(exclude) => excludeTestsSet ++= exclude
case Listeners(listeners) => testListeners ++= listeners
case Setup(setupFunction) => setup += setupFunction
@ -87,7 +91,8 @@ object Tests
log.warn("Arguments defined for test frameworks that are not present:\n\t" + undefinedFrameworks.mkString("\n\t"))
def includeTest(test: TestDefinition) = !excludeTestsSet.contains(test.name) && testFilters.forall(filter => filter(test.name))
val tests = discovered.filter(includeTest).toSet.toSeq
val filtered0 = discovered.filter(includeTest).distinct
val tests = if(orderedFilters.isEmpty) filtered0 else orderedFilters.flatMap(f => filtered0.filter(d => f(d.name))).distinct
val arguments = testArgsByFramework.map { case (k,v) => (k, v.toList) } toMap;
testTask(frameworks.values.toSeq, testLoader, tests, setup.readOnly, cleanup.readOnly, log, testListeners.readOnly, arguments, config)
}
@ -117,7 +122,7 @@ object Tests
type TestRunnable = (String, () => TestResult.Value)
def makeParallel(runnables: Iterable[TestRunnable], setupTasks: Task[Unit], tags: Seq[(Tag,Int)]) =
runnables map { case (name, test) => task { (name, test()) } dependsOn setupTasks named name tagw(tags : _*) }
def makeSerial(runnables: Iterable[TestRunnable], setupTasks: Task[Unit], tags: Seq[(Tag,Int)]) =
def makeSerial(runnables: Seq[TestRunnable], setupTasks: Task[Unit], tags: Seq[(Tag,Int)]) =
task { runnables map { case (name, test) => (name, test()) } } dependsOn(setupTasks)
def processResults(results: Iterable[(String, TestResult.Value)]): (TestResult.Value, Map[String, TestResult.Value]) =

View File

@ -388,7 +388,7 @@ object Defaults extends BuildCommon
new Tests.Execution(opts, par, ts)
}
def testQuickFilter: Initialize[Task[Seq[String] => String => Boolean]] =
def testQuickFilter: Initialize[Task[Seq[String] => Seq[String => Boolean]]] =
(fullClasspath in test, streams in test) map {
(cp, s) =>
val ans = cp.flatMap(_.metadata get Keys.analysis)
@ -407,12 +407,13 @@ object Defaults extends BuildCommon
apis.internal(f).compilation.startTime
}.max)
}
args => test => selectedFilter(args)(test) && {
succeeded.get(test) match {
case None => true
case Some(ts) => stamp(test) > ts
}
def noSuccessYet(test: String) = succeeded.get(test) match {
case None => true
case Some(ts) => stamp(test) > ts
}
args => for(filter <- selectedFilter(args)) yield
(test: String) => filter(test) && noSuccessYet(test)
}
def succeededFile(dir: File) = dir / "succeeded_tests"
@ -429,7 +430,7 @@ object Defaults extends BuildCommon
val config = testExecution.value
implicit val display = Project.showContextKey(state.value)
val modifiedOpts = Tests.Filter(filter(selected)) +: Tests.Argument(frameworkOptions : _*) +: config.options
val modifiedOpts = Tests.Filters(filter(selected)) +: Tests.Argument(frameworkOptions : _*) +: config.options
val newConfig = config.copy(options = modifiedOpts)
val groupsTask = allTestGroupsTask(s, loadedTestFrameworks.value, testLoader.value, testGrouping.value, newConfig, fullClasspath.value, javaHome.value)
val processed =
@ -452,10 +453,13 @@ object Defaults extends BuildCommon
Tests.foldTasks(groupTasks, config.parallel)
}
def selectedFilter(args: Seq[String]): String => Boolean =
def selectedFilter(args: Seq[String]): Seq[String => Boolean] =
{
val filters = args map GlobFilter.apply
s => filters.isEmpty || filters.exists { _ accept s }
if(filters.isEmpty)
Seq(const(true))
else
filters.map { f => (s: String) => f accept s }
}
def detectTests: Initialize[Task[Seq[TestDefinition]]] = (loadedTestFrameworks, compile, streams) map { (frameworkMap, analysis, s) =>
Tests.discover(frameworkMap.values.toSeq, analysis, s.log)._1

View File

@ -200,7 +200,7 @@ object Keys
val testFrameworks = SettingKey[Seq[TestFramework]]("test-frameworks", "Registered, although not necessarily present, test frameworks.", CTask)
val testListeners = TaskKey[Seq[TestReportListener]]("test-listeners", "Defines test listeners.", DTask)
val testExecution = TaskKey[Tests.Execution]("test-execution", "Settings controlling test execution", DTask)
val testFilter = TaskKey[Seq[String] => String => Boolean]("test-filter", "Filter controlling whether the test is executed", DTask)
val testFilter = TaskKey[Seq[String] => Seq[String => Boolean]]("test-filter", "Filter controlling whether the test is executed", DTask)
val testGrouping = TaskKey[Seq[Tests.Group]]("test-grouping", "Collects discovered tests into groups. Whether to fork and the options for forking are configurable on a per-group basis.", BMinusTask)
val isModule = AttributeKey[Boolean]("is-module", "True if the target is a module.", DSetting)

View File

@ -0,0 +1,3 @@
parallelExecution in test := false
libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.10.0" % "test"

View File

@ -0,0 +1,5 @@
import org.scalacheck._
object A extends Properties("A") {
property("Ran second") = Prop.secure(Counter.i == B.value)
}

View File

@ -0,0 +1,9 @@
import org.scalacheck._
object B extends Properties("B") {
val value = 3
property("Succeed") = Prop.secure {
Counter.i = value
true
}
}

View File

@ -0,0 +1,3 @@
object Counter {
var i = 0
}

View File

@ -0,0 +1,2 @@
-> test-only A B
> test-only B A

View File

@ -132,17 +132,20 @@ object TestFramework
log: Logger,
listeners: Seq[TestReportListener],
testArgsByFramework: Map[Framework, Seq[String]]):
(() => Unit, Iterable[(String, () => TestResult.Value)], TestResult.Value => () => Unit) =
(() => Unit, Seq[(String, () => TestResult.Value)], TestResult.Value => () => Unit) =
{
val arguments = testArgsByFramework withDefaultValue Nil
val mappedTests = testMap(frameworks, tests, arguments)
if(mappedTests.isEmpty)
(() => (), Nil, _ => () => () )
else
createTestTasks(testLoader, mappedTests, log, listeners)
createTestTasks(testLoader, mappedTests, tests, log, listeners)
}
private def testMap(frameworks: Seq[Framework], tests: Seq[TestDefinition], args: Map[Framework, Seq[String]]):
private[this] def order(mapped: Map[String, () => TestResult.Value], inputs: Seq[TestDefinition]): Seq[(String, () => TestResult.Value)] =
for( d <- inputs; act <- mapped.get(d.name) ) yield (d.name, act)
private[this] def testMap(frameworks: Seq[Framework], tests: Seq[TestDefinition], args: Map[Framework, Seq[String]]):
Map[Framework, (Set[TestDefinition], Seq[String])] =
{
import scala.collection.mutable.{HashMap, HashSet, Set}
@ -171,7 +174,7 @@ object TestFramework
uniqueDefs.toSet
}
private def createTestTasks(loader: ClassLoader, tests: Map[Framework, (Set[TestDefinition], Seq[String])], log: Logger, listeners: Seq[TestReportListener]) =
private def createTestTasks(loader: ClassLoader, tests: Map[Framework, (Set[TestDefinition], Seq[String])], ordered: Seq[TestDefinition], log: Logger, listeners: Seq[TestReportListener]) =
{
val testsListeners = listeners collect { case tl: TestsListener => tl }
def foreachListenerSafe(f: TestsListener => Unit): () => Unit = () => safeForeach(testsListeners, log)(f)
@ -180,7 +183,7 @@ object TestFramework
val startTask = foreachListenerSafe(_.doInit)
val testTasks =
tests.view flatMap { case (framework, (testDefinitions, testArgs)) =>
tests flatMap { case (framework, (testDefinitions, testArgs)) =>
val runner = new TestRunner(framework, loader, listeners, log)
for(testDefinition <- testDefinitions) yield
@ -191,7 +194,7 @@ object TestFramework
}
val endTask = (result: TestResult.Value) => foreachListenerSafe(_.doComplete(result))
(startTask, testTasks.toList, endTask)
(startTask, order(testTasks, ordered), endTask)
}
private[this] def withContextLoader[T](loader: ClassLoader)(eval: => T): T =
{