From f862e649114927af344dd55e9a4ec347da3c9d6e Mon Sep 17 00:00:00 2001 From: cheeseng Date: Thu, 4 Apr 2013 11:30:59 +0800 Subject: [PATCH] Added support of custom summary message returned from Runner.done method. --- .../src/main/scala/sbt/ForkTests.scala | 8 +- main/actions/src/main/scala/sbt/Tests.scala | 77 ++++++++++++------- main/src/main/scala/sbt/Defaults.scala | 13 ++-- 3 files changed, 60 insertions(+), 38 deletions(-) diff --git a/main/actions/src/main/scala/sbt/ForkTests.scala b/main/actions/src/main/scala/sbt/ForkTests.scala index 54e3a0258..d4421e713 100755 --- a/main/actions/src/main/scala/sbt/ForkTests.scala +++ b/main/actions/src/main/scala/sbt/ForkTests.scala @@ -31,7 +31,7 @@ private[sbt] object ForkTests { val server = new ServerSocket(0) object Acceptor extends Runnable { val resultsAcc = mutable.Map.empty[String, SuiteResult] - lazy val result = (overall(resultsAcc.values.map(_.result)), resultsAcc.toMap) + lazy val result = TestOutput(overall(resultsAcc.values.map(_.result)), resultsAcc.toMap, Iterable.empty) def run: Unit = { val socket = try { @@ -76,20 +76,20 @@ private[sbt] object ForkTests { val ec = Fork.java(fork, options) val result = if (ec != 0) - (TestResult.Error, Map("Running java with options " + options.mkString(" ") + " failed with exit code " + ec -> SuiteResult.Error)) + TestOutput(TestResult.Error, Map("Running java with options " + options.mkString(" ") + " failed with exit code " + ec -> SuiteResult.Error), Iterable.empty) else { // Need to wait acceptor thread to finish its business acceptorThread.join() Acceptor.result } - testListeners.foreach(_.doComplete(result._1)) + testListeners.foreach(_.doComplete(result.overall)) result } finally { server.close() } } else - (TestResult.Passed, Map.empty[String, SuiteResult]) + TestOutput(TestResult.Passed, Map.empty[String, SuiteResult], Iterable.empty) } tagw (config.tags: _*) } } diff --git a/main/actions/src/main/scala/sbt/Tests.scala b/main/actions/src/main/scala/sbt/Tests.scala index 86801fdd5..987e39126 100644 --- a/main/actions/src/main/scala/sbt/Tests.scala +++ b/main/actions/src/main/scala/sbt/Tests.scala @@ -19,7 +19,8 @@ sealed trait TestOption object Tests { // (overall result, individual results) - type Output = (TestResult.Value, Map[String,SuiteResult]) + final case class Output(overall: TestResult.Value, events: Map[String,SuiteResult], summaries: Iterable[Summary]) + final case class Summary(name: String, summaryText: String) final case class Setup(setup: ClassLoader => Unit) extends TestOption def Setup(setup: () => Unit) = new Setup(_ => setup()) @@ -115,7 +116,7 @@ object Tests makeSerial(runnables, setupTasks, config.tags) val taggedMainTasks = mainTasks.tagw(config.tags : _*) taggedMainTasks map processResults flatMap { results => - val cleanupTasks = fj(partApp(userCleanup) :+ frameworkCleanup(results._1)) + val cleanupTasks = fj(partApp(userCleanup) :+ frameworkCleanup(results.overall)) cleanupTasks map { _ => results } } } @@ -125,12 +126,12 @@ object Tests 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, SuiteResult)]): (TestResult.Value, Map[String, SuiteResult]) = - (overall(results.map(_._2.result)), results.toMap) - def foldTasks(results: Seq[Task[Output]], parallel: Boolean): Task[Output] = + def processResults(results: Iterable[(String, SuiteResult)]): Output = + Output(overall(results.map(_._2.result)), results.toMap, Iterable.empty) + def foldTasks(results: Seq[Task[Output]], parallel: Boolean): Task[Output] = if (parallel) reduced(results.toIndexedSeq, { - case ((v1, m1), (v2, m2)) => (if (v1.id < v2.id) v2 else v1, m1 ++ m2) + case (Output(v1, m1, _), Output(v2, m2, _)) => Output(if (v1.id < v2.id) v2 else v1, m1 ++ m2, Iterable.empty) }) else { def sequence(tasks: List[Task[Output]], acc: List[Output]): Task[List[Output]] = tasks match { @@ -138,8 +139,8 @@ object Tests case hd::tl => hd flatMap { out => sequence(tl, out::acc) } } sequence(results.toList, List()) map { ress => - val (rs, ms) = ress.unzip - (overall(rs), ms reduce (_ ++ _)) + val (rs, ms) = ress.unzip { e => (e.overall, e.events) } + Output(overall(rs), ms reduce (_ ++ _), Iterable.empty) } } def overall(results: Iterable[TestResult.Value]): TestResult.Value = @@ -169,27 +170,45 @@ object Tests (tests, mains.toSet) } - def showResults(log: Logger, results: (TestResult.Value, Map[String, SuiteResult]), noTestsMessage: =>String): Unit = + def showResults(log: Logger, results: Output, noTestsMessage: =>String): Unit = { - val (skipped, errors, passed, failures) = - results._2.foldLeft((0, 0, 0, 0)) { case (acc, entry) => - val suiteResult = entry._2 - (acc._1 + suiteResult.skippedCount, acc._2 + suiteResult.errorCount, acc._3 + suiteResult.passedCount, acc._4 + suiteResult.failureCount) - } - val totalCount = failures + errors + skipped + passed - val postfix = "Total " + totalCount + ", Failed " + failures + ", Errors " + errors + ", Passed " + passed + ", Skipped " + skipped - results._1 match { - case TestResult.Error => log.error("Error: " + postfix) - case TestResult.Passed => log.info("Passed: " + postfix) - case TestResult.Failed => log.error("Failed: " + postfix) + val multipleFrameworks = results.summaries.size > 1 + def printSummary(name: String, message: String) + { + if (multipleFrameworks) + log.info(name) + if (message.size > 0) + log.info(message) + else + log.info("Summary for " + name + " not available.") } - if (results._2.isEmpty) + for (Summary(name, messages) <- results.summaries) + printSummary(name, messages) + val noSummary = results.summaries.headOption.forall(_.summaryText.size == 0) + val printStandard = multipleFrameworks || noSummary + // Print the standard one-liner statistic if no framework summary is defined, or when > 1 framework is in used. + if (printStandard) + { + val (skippedCount, errorsCount, passedCount, failuresCount) = + results.events.foldLeft((0, 0, 0, 0)) { case (acc, (name, testEvent)) => + (acc._1 + testEvent.skippedCount, acc._2 + testEvent.errorCount, acc._3 + testEvent.passedCount, acc._4 + testEvent.failureCount) + } + val totalCount = failuresCount + errorsCount + skippedCount + passedCount + val postfix = "Total " + totalCount + ", Failed " + failuresCount + ", Errors " + errorsCount + ", Passed " + passedCount + ", Skipped " + skippedCount + results.overall match { + case TestResult.Error => log.error("Error: " + postfix) + case TestResult.Passed => log.info("Passed: " + postfix) + case TestResult.Failed => log.error("Failed: " + postfix) + } + } + // Let's always print out Failed tests for now + if (results.events.isEmpty) log.info(noTestsMessage) else { import TestResult.{Error, Failed, Passed} - def select(Tpe: TestResult.Value) = results._2 collect { case (name, Tpe) => name } + def select(Tpe: TestResult.Value) = results.events collect { case (name, Tpe) => name } val failures = select(Failed) val errors = select(Error) @@ -197,17 +216,19 @@ object Tests def show(label: String, level: Level.Value, tests: Iterable[String]): Unit = if(!tests.isEmpty) - { - log.log(level, label) - log.log(level, tests.mkString("\t", "\n\t", "")) - } + { + log.log(level, label) + log.log(level, tests.mkString("\t", "\n\t", "")) + } show("Passed tests:", Level.Debug, passed ) show("Failed tests:", Level.Error, failures) show("Error during tests:", Level.Error, errors) + } - if(!failures.isEmpty || !errors.isEmpty) - throw new TestsFailedException + results.overall match { + case TestResult.Error | TestResult.Failed => throw new TestsFailedException + case TestResult.Passed => } } diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index e4d779518..2287f8a34 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -446,9 +446,9 @@ object Defaults extends BuildCommon implicit val display = Project.showContextKey(state.value) 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 output = allTestGroupsTask(s, loadedTestFrameworks.value, testLoader.value, testGrouping.value, newConfig, fullClasspath.value, javaHome.value) val processed = - for(out <- groupsTask) yield + for(out <- output) yield Tests.showResults(s.log, out, noTestsMessage(resolvedScoped.value)) Def.value(processed) } @@ -480,10 +480,11 @@ object Defaults extends BuildCommon } val output = Tests.foldTasks(groupTasks, config.parallel) output map { out => - runners foreach { case (tf, r) => - r.done() - } - out + val summaries = + runners map { case (tf, r) => + Tests.Summary(frameworks(tf).name, r.done()) + } + out.copy(summaries = summaries) } }