aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/scala/com/iinteractive/test/harness/SummaryReporter.scala
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/scala/com/iinteractive/test/harness/SummaryReporter.scala')
-rw-r--r--src/main/scala/com/iinteractive/test/harness/SummaryReporter.scala192
1 files changed, 192 insertions, 0 deletions
diff --git a/src/main/scala/com/iinteractive/test/harness/SummaryReporter.scala b/src/main/scala/com/iinteractive/test/harness/SummaryReporter.scala
new file mode 100644
index 0000000..a5fe1e0
--- /dev/null
+++ b/src/main/scala/com/iinteractive/test/harness/SummaryReporter.scala
@@ -0,0 +1,192 @@
+package com.iinteractive.test.harness
+
+import com.iinteractive.test.tap.{TAPEvent,TAPResult,TodoDirective}
+import com.iinteractive.test.tap.{StartEvent,ResultEvent,PlanEvent,EndEvent}
+import com.iinteractive.test.Test
+
+/** Runs a series of tests. The TAP output from these tests is parsed, and
+ * output is produced which is similar in style to Perl's
+ * [[https://metacpan.org/module/Test::Harness Test::Harness]].
+ */
+class SummaryReporter extends MultiTestReporter with SummarizedTests {
+ def run (testNames: Seq[String]): Int = {
+ val results = runTests(testNames)
+ val success = results.values.forall(_.success)
+ printTestSummary(success, results)
+ if (success) 0 else 1
+ }
+
+ protected def runTests (testNames: Seq[String]): Map[String, TAPResult] = {
+ val maxLength = testNames.map(_.length).max
+
+ testNames.map { name =>
+ val callbackGenerator: () => TAPEvent => Unit = () => {
+ var width = 0
+ var tests = 0
+ var plan: Option[Int] = None
+
+ def status = {
+ tests + "/" + plan.getOrElse("?")
+ }
+
+ def printStatus (st: String) {
+ print("\r" + (" " * width) + "\r")
+ val line =
+ name + " " + ("." * (maxLength - name.length)) + ".. " + st
+ width = line.length
+ print(line)
+ Console.out.flush
+ }
+
+ (e: TAPEvent) => e match {
+ case StartEvent => {
+ printStatus("")
+ }
+ case PlanEvent(p) => {
+ plan = Some(p.plan)
+ printStatus(status)
+ }
+ case ResultEvent(r) => {
+ tests += 1
+ printStatus(status)
+ }
+ case EndEvent(result) => {
+ if (result.success) {
+ printStatus("")
+ println("ok")
+ }
+ else {
+ val results = result.results.length
+ val failed = result.results.count { t =>
+ !t.passed && !t.directive.isDefined
+ }
+
+ printStatus("")
+ println("Dubious, test returned " + result.exitCode)
+ println("Failed " + failed + "/" + results + " subtests")
+ }
+ }
+ case _ => ()
+ }
+ }
+
+ name -> runOneTest(newInstance[Test](name), callbackGenerator())
+ }.toMap
+ }
+
+ protected def printTestSummary (
+ success: Boolean,
+ results: Map[String, TAPResult]
+ ) {
+ printSuccess(success)
+ printLongSummary(results)
+ printShortSummary(results)
+ printPassFail(success, results)
+ }
+
+ private def printSuccess (success: Boolean) {
+ if (success) {
+ println("All tests successful.")
+ }
+ }
+
+ private def printShortSummary (results: Map[String, TAPResult]) {
+ val files = results.size
+ val tests = results.values.map(_.results.length).sum
+ println("Files=" + files + ", Tests=" + tests)
+ }
+
+ private def printLongSummary (results: Map[String, TAPResult]) {
+ val todoSucceeded = results.mapValues { r =>
+ r.results.filter { t =>
+ t.directive match {
+ case Some(TodoDirective(_)) => t.passed
+ case _ => false
+ }
+ }
+ }.filter(_._2.length > 0)
+
+ val testsFailed = results.mapValues { r =>
+ r.results.filter { t =>
+ t.directive match {
+ case None => !t.passed
+ case _ => false
+ }
+ }
+ }.filter(_._2.length > 0)
+
+ val testNames = (todoSucceeded ++ testsFailed).keys
+
+ if (testNames.nonEmpty) {
+ println("")
+ println("Test Summary Report")
+ println("-------------------")
+
+ val maxLength = testNames.map(_.length).max
+
+ for (name <- testNames) {
+ val result = results(name)
+
+ println(
+ name + (" " * (maxLength - name.length)) + " " +
+ "(Tests: " + result.results.length + " " +
+ "Failed: " + testsFailed.getOrElse(name, Nil).length + ")"
+ )
+
+ if (testsFailed.isDefinedAt(name)) {
+ val fails = testsFailed(name)
+ println(
+ " Failed test" + (if (fails.length > 1) "s" else "") + ": " +
+ fails.map(_.number).mkString(", ")
+ )
+ }
+
+ if (todoSucceeded.isDefinedAt(name)) {
+ val todos = todoSucceeded(name)
+ println(
+ " TODO passed: " +
+ todos.map(_.number).mkString(", ")
+ )
+ }
+
+ val exitCode = results(name).exitCode
+ if (exitCode != 0) {
+ println(" Non-zero exit status: " + exitCode)
+ }
+ }
+ }
+ }
+
+ private def printPassFail (
+ success: Boolean,
+ results: Map[String, TAPResult]
+ ) {
+ if (success) {
+ println("Result: PASS")
+ }
+ else {
+ println("Result: FAIL")
+
+ val testResults = results.values
+
+ val testsFailed = testResults.map { r =>
+ r.results.count { t =>
+ t.directive match {
+ case None => !t.passed
+ case _ => false
+ }
+ }
+ }.filter(_ > 0)
+ val failedFiles = testsFailed.size
+ val failedTests = testsFailed.sum
+
+ val allFiles = testResults.size
+ val allTests = testResults.map(_.results.length).sum
+
+ println(
+ "Failed " + failedFiles + "/" + allFiles + " test programs. " +
+ failedTests + "/" + allTests + " subtests failed."
+ )
+ }
+ }
+}