Added support of nested test tasks when tests are executed InProcess and sequentially.

This commit is contained in:
cheeseng 2013-04-03 21:00:59 +08:00
parent df9a475158
commit 3109912d00
6 changed files with 140 additions and 15 deletions

View File

@ -12,6 +12,7 @@ package sbt
import ConcurrentRestrictions.Tag
import testing.{AnnotatedFingerprint, Fingerprint, Framework, SubclassFingerprint, Runner, Task => TestTask}
import scala.annotation.tailrec
import java.io.File
@ -113,7 +114,7 @@ object Tests
if(config.parallel)
makeParallel(runnables, setupTasks, config.tags).toSeq.join
else
makeSerial(runnables, setupTasks, config.tags)
makeSerial(loader, runnables, setupTasks, config.tags)
val taggedMainTasks = mainTasks.tagw(config.tags : _*)
taggedMainTasks map processResults flatMap { results =>
val cleanupTasks = fj(partApp(userCleanup) :+ frameworkCleanup(results.overall))
@ -122,10 +123,26 @@ object Tests
}
type TestRunnable = (String, TestFunction)
def makeParallel(runnables: Iterable[TestRunnable], setupTasks: Task[Unit], tags: Seq[(Tag,Int)]) =
runnables map { case (name, test) => task { (name, test.apply()) } tagw(tags : _*) tag(test.tags map (ConcurrentRestrictions.Tag(_)) : _*) dependsOn setupTasks named name }
runnables map { case (name, test) => task { (name, test.apply()._1) } tagw(tags : _*) tag(test.tags map (ConcurrentRestrictions.Tag(_)) : _*) dependsOn setupTasks named name }
def makeSerial(runnables: Seq[TestRunnable], setupTasks: Task[Unit], tags: Seq[(Tag,Int)]) =
task { runnables map { case (name, test) => (name, test.apply()) } } dependsOn(setupTasks)
def makeSerial(loader: ClassLoader, runnables: Seq[TestRunnable], setupTasks: Task[Unit], tags: Seq[(Tag,Int)]): Task[List[(String, SuiteResult)]] =
{
@tailrec
def processRunnable(runnableList: List[TestRunnable], acc: List[(String, SuiteResult)]): List[(String, SuiteResult)] =
runnableList match {
case hd :: rst =>
val testFun = hd._2
val (result, nestedTasks) = testFun.apply()
val nestedRunnables =
nestedTasks.view.zipWithIndex map { case (nt, idx) =>
(hd._1, TestFramework.createTestFunction(loader, new TestDefinition(testFun.testDefinition.name + "-" + idx, testFun.testDefinition.fingerprint), testFun.runner, nt))
}
processRunnable(nestedRunnables.toList ::: rst, (hd._1, result) :: acc)
case Nil => acc
}
task { processRunnable(runnables.toList, List.empty) } dependsOn(setupTasks)
}
def processResults(results: Iterable[(String, SuiteResult)]): Output =
Output(overall(results.map(_._2.result)), results.toMap, Iterable.empty)

View File

@ -0,0 +1,7 @@
scalaVersion := "2.10.1"
libraryDependencies += "org.scalatest" %% "scalatest" % "2.0.M6-SNAP15"
testOptions in Test += Tests.Argument("-r", "custom.CustomReporter")
parallelExecution in Test := false

View File

@ -0,0 +1,30 @@
package custom
import java.io._
import org.scalatest._
import events._
class CustomReporter extends Reporter {
private def writeFile(filePath: String, content: String) {
val file = new File(filePath)
val writer =
if (!file.exists)
new FileWriter(new File(filePath))
else
new FileWriter(new File(filePath + "-2"))
writer.write(content)
writer.flush()
writer.close()
}
def apply(event: Event) {
event match {
case SuiteStarting(_, suiteName, _, _, _, _, _, _, _, _) => writeFile("target/SuiteStarting-" + suiteName, suiteName)
case SuiteCompleted(_, suiteName, _, _, _, _, _, _, _, _, _) => writeFile("target/SuiteCompleted-" + suiteName, suiteName)
case TestStarting(_, _, _, _, testName, _, _, _, _, _, _, _) => writeFile("target/TestStarting-" + testName, testName)
case TestSucceeded(_, _, _, _, testName, _, _, _, _, _, _, _, _, _) => writeFile("target/TestSucceeded-" + testName, testName)
case _ =>
}
}
}

View File

@ -0,0 +1,17 @@
package com.test
import org.scalatest._
class NestedSpecs extends Suites (
new TestSpec
)
@DoNotDiscover
class TestSpec extends Spec {
def `TestSpec-test-1 ` {}
def `TestSpec-test-2 ` {}
def `TestSpec-test-3 ` {}
}

View File

@ -0,0 +1,48 @@
#This test that the framework will execute ScalaTest nested suites as sequential nested task (InProcess) properly.
#A CustomReporter is used to report expected ScalaTest's events by writing out to files in target/,
#it is then used to check for their existence, and if the expected event is fired > 1 (which is unexpected),
#a xxxx-2 file will be written, thus here we also check for 'absent' of such file.
> clean
> test
$ exists target/SuiteStarting-NestedSpecs
$ absent target/SuiteStarting-NestedSpecs-2
$ exists target/SuiteCompleted-NestedSpecs
$ absent target/SuiteCompleted-NestedSpecs-2
$ exists target/SuiteStarting-TestSpec
$ absent target/SuiteStarting-TestSpec-2
$ exists target/SuiteCompleted-TestSpec
$ absent target/SuiteCompleted-TestSpec-2
$ exists target/TestStarting-TestSpec-test-1
$ absent target/TestStarting-TestSpec-test-1-2
$ exists target/TestSucceeded-TestSpec-test-1
$ absent target/TestSucceeded-TestSpec-test-1-2
$ exists target/TestStarting-TestSpec-test-2
$ absent target/TestStarting-TestSpec-test-2-2
$ exists target/TestSucceeded-TestSpec-test-2
$ absent target/TestSucceeded-TestSpec-test-2-2
$ exists target/TestStarting-TestSpec-test-3
$ absent target/TestStarting-TestSpec-test-3-2
$ exists target/TestSucceeded-TestSpec-test-3
$ absent target/TestSucceeded-TestSpec-test-3-2

View File

@ -68,7 +68,7 @@ final class TestRunner(delegate: Runner, listeners: Seq[TestReportListener], log
final def task(testDefinition: TestDefinition): TestTask =
delegate.task(testDefinition.name, testDefinition.fingerprint, false, Array(new SuiteSelector)) // TODO: To pass in correct explicitlySpecified and selectors
final def run(testDefinition: TestDefinition, testTask: TestTask): SuiteResult =
final def run(testDefinition: TestDefinition, testTask: TestTask): (SuiteResult, Seq[TestTask]) =
{
log.debug("Running " + testDefinition)
val name = testDefinition.name
@ -78,25 +78,26 @@ final class TestRunner(delegate: Runner, listeners: Seq[TestReportListener], log
val results = new scala.collection.mutable.ListBuffer[Event]
val handler = new EventHandler { def handle(e:Event){ results += e } }
val loggers = listeners.flatMap(_.contentLogger(testDefinition))
try testTask.execute(handler, loggers.map(_.log).toArray)
finally loggers.foreach( _.flush() )
val nestedTasks =
try testTask.execute(handler, loggers.map(_.log).toArray)
finally loggers.foreach( _.flush() )
val event = TestEvent(results)
safeListenersCall(_.testEvent( event ))
SuiteResult(results)
(SuiteResult(results), nestedTasks.toSeq)
}
safeListenersCall(_.startGroup(name))
try
{
val suiteResult = runTest()
val (suiteResult, nestedTasks) = runTest()
safeListenersCall(_.endGroup(name, suiteResult.result))
suiteResult
(suiteResult, nestedTasks)
}
catch
{
case e: Throwable =>
safeListenersCall(_.endGroup(name, e))
SuiteResult.Error
(SuiteResult.Error, Seq.empty[TestTask])
}
}
@ -204,8 +205,8 @@ object TestFramework
val runner = runners(framework)
for(testDefinition <- testDefinitions) yield
{
val task = withContextLoader(loader) { runner.task(testDefinition) }
val testFunction = new TestFunction(() => withContextLoader(loader) { runner.run(testDefinition, task) }) { def tags = task.tags }
val testTask = withContextLoader(loader) { runner.task(testDefinition) }
val testFunction = createTestFunction(loader, testDefinition, runner, testTask)
(testDefinition.name, testFunction)
}
}
@ -228,8 +229,13 @@ object TestFramework
val main = ClasspathUtilities.makeLoader(classpath, dual, scalaInstance, tempDir)
ClasspathUtilities.filterByClasspath(interfaceJar +: classpath, main)
}
def createTestFunction(loader: ClassLoader, testDefinition: TestDefinition, runner:TestRunner, testTask: TestTask): TestFunction =
new TestFunction(testDefinition, runner, (r: TestRunner) => withContextLoader(loader) { r.run(testDefinition, testTask) }) { def tags = testTask.tags }
}
abstract class TestFunction(val apply: () => SuiteResult) {
def tags: Array[String]
abstract class TestFunction(val testDefinition: TestDefinition, val runner: TestRunner, fun: (TestRunner) => (SuiteResult, Seq[TestTask])) {
def apply(): (SuiteResult, Seq[TestTask]) = fun(runner)
def tags: Seq[String]
}