From 3109912d00807151aa536fc0e17976e77efc3d74 Mon Sep 17 00:00:00 2001 From: cheeseng Date: Wed, 3 Apr 2013 21:00:59 +0800 Subject: [PATCH] Added support of nested test tasks when tests are executed InProcess and sequentially. --- main/actions/src/main/scala/sbt/Tests.scala | 25 ++++++++-- .../tests/nested-inproc-seq/build.sbt | 7 +++ .../main/scala/custom/CustomReporter.scala | 30 ++++++++++++ .../src/test/scala/com/test/NestedSpecs.scala | 17 +++++++ sbt/src/sbt-test/tests/nested-inproc-seq/test | 48 +++++++++++++++++++ .../src/main/scala/sbt/TestFramework.scala | 28 ++++++----- 6 files changed, 140 insertions(+), 15 deletions(-) create mode 100644 sbt/src/sbt-test/tests/nested-inproc-seq/build.sbt create mode 100644 sbt/src/sbt-test/tests/nested-inproc-seq/src/main/scala/custom/CustomReporter.scala create mode 100644 sbt/src/sbt-test/tests/nested-inproc-seq/src/test/scala/com/test/NestedSpecs.scala create mode 100644 sbt/src/sbt-test/tests/nested-inproc-seq/test diff --git a/main/actions/src/main/scala/sbt/Tests.scala b/main/actions/src/main/scala/sbt/Tests.scala index 6c8542e85..9da8786dc 100644 --- a/main/actions/src/main/scala/sbt/Tests.scala +++ b/main/actions/src/main/scala/sbt/Tests.scala @@ -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) diff --git a/sbt/src/sbt-test/tests/nested-inproc-seq/build.sbt b/sbt/src/sbt-test/tests/nested-inproc-seq/build.sbt new file mode 100644 index 000000000..dda0ba5dc --- /dev/null +++ b/sbt/src/sbt-test/tests/nested-inproc-seq/build.sbt @@ -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 \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/nested-inproc-seq/src/main/scala/custom/CustomReporter.scala b/sbt/src/sbt-test/tests/nested-inproc-seq/src/main/scala/custom/CustomReporter.scala new file mode 100644 index 000000000..f21595450 --- /dev/null +++ b/sbt/src/sbt-test/tests/nested-inproc-seq/src/main/scala/custom/CustomReporter.scala @@ -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 _ => + } + } +} \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/nested-inproc-seq/src/test/scala/com/test/NestedSpecs.scala b/sbt/src/sbt-test/tests/nested-inproc-seq/src/test/scala/com/test/NestedSpecs.scala new file mode 100644 index 000000000..b9d5a838f --- /dev/null +++ b/sbt/src/sbt-test/tests/nested-inproc-seq/src/test/scala/com/test/NestedSpecs.scala @@ -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 ` {} +} \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/nested-inproc-seq/test b/sbt/src/sbt-test/tests/nested-inproc-seq/test new file mode 100644 index 000000000..6481f9187 --- /dev/null +++ b/sbt/src/sbt-test/tests/nested-inproc-seq/test @@ -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 \ No newline at end of file diff --git a/testing/src/main/scala/sbt/TestFramework.scala b/testing/src/main/scala/sbt/TestFramework.scala index 59bec069a..ec3c4ef6a 100644 --- a/testing/src/main/scala/sbt/TestFramework.scala +++ b/testing/src/main/scala/sbt/TestFramework.scala @@ -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] }