package org.specs.runner
import scala.collection.mutable.Queue
import org.specs.log.ConsoleLog
import org.specs.util.Timer
import org.specs.util.SimpleTimer
import org.specs.io._
import java.util.Calendar
import org.specs.ExtendedThrowable._
import org.specs.specification._
/**
* Generic trait for reporting the result of a list of specifications
*/
trait Reporter {
/** reports a list of specifications */
def report(specs: Iterable[Specification]): Unit
}
/**
* This trait reports the result of a specification on a simple <code>Output</code>
* which must support <code>print</code>-like methods
*/
trait OutputReporter extends Reporter with Output {
/** this variable controls if stacktraces should be printed */
private var stacktrace = true
def setNoStacktrace = stacktrace = false
/** the timer is used to display execution times */
val timer: org.specs.util.Timer
/** reports a list of specifications */
def report(specs: Iterable[Specification]): Unit = specs foreach (reportSpec(_, ""))
/**
* reports a list of specifications with a given space separator to display before the results.<br>
* This method may be called recursively by the <code>reportSpec</code> method if a specification
* has subSpecifications, hence the <code>padding</code> will be incremented
*/
def report(specs: Iterable[Specification], padding: String): Unit = specs foreach (reportSpec(_, padding))
/** reports a specification with no padding */
def reportSpec(spec: Specification): Unit = reportSpec(spec, "")
/**
* reports a specification with a given space separator to display before the results.<br>
* This method may be called recursively by the <code>reportSpec</code> method if a specification
* has subSpecifications, hence the <code>padding</code> will be incremented
*/
def reportSpec(spec: Specification, padding: String): Unit = {
timer.start
println(padding + "Specification \"" + spec.name + "\"")
report(spec.subSpecifications, padding + " ")
reportSuts(spec.suts, padding + " ")
println(padding + "Total for specification \"" + spec.name + "\":")
printStats(stats(spec), padding)
}
/** utility implicit definition to be able to add tuples */
implicit def toAddableTuple(t1: Tuple5[Int, Int, Int, Int, Int]) = new AddableTuple(t1)
class AddableTuple(t1: Tuple5[Int, Int, Int, Int, Int]) { def +(t2: Tuple5[Int, Int, Int, Int, Int]) = (t1._1 + t2._1, t1._2 + t2._2, t1._3 + t2._3, t1._4 + t2._4, t1._5 + t2._5) }
/**
* @return the number of examples, assertions, failures and errors for a specification
* by collecting those numbers on sub-specifications and suts
*/
def stats(spec: Specification): (Int, Int, Int, Int, Int) = {
spec.suts.foldLeft((0, 0, 0, 0, 0))(_ + stats(_)) +
spec.subSpecifications.foldLeft((0, 0, 0, 0, 0))(_ + stats(_))
}
/**
* @return the number of examples, assertions, failures and errors for a sut
* by collecting those numbers on examples
*/
def stats(sut: Sut): (Int, Int, Int, Int, Int) = {
sut.examples.foldLeft((0, 0, 0, 0, 0))(_ + stats(_))
}
/**
* @return the number of examples, assertions, failures and errors for an example
* by collecting those numbers on this example and on sub-examples
*/
def stats(example: Example): (Int, Int, Int, Int, Int) = {
(if (example.subExamples.isEmpty) 1 else 0, example.assertionsNb, example.failures.size, example.errors.size, example.skipped.size) +
example.subExamples.foldLeft((0, 0, 0, 0, 0))(_ + stats(_))
}
/**
* reports the sut results. If there are more than one, then report stats for each
* else just print the specification of the sut, the parent specification will display the total
* for that sut
*/
def reportSuts(suts: Iterable[Sut], padding: String) = {
if (suts.toList.size > 1)
suts foreach {reportSut(_, padding)}
else
suts foreach {printSut(_, padding)}
}
/**
* reports one sut results: print the sut specifications, then the statistics
*/
def reportSut(sut: Sut, padding: String) = { timer.start; printSut(sut, padding); printStats(sut, padding) }
/**
* prints one sut specification
*/
def printSut(sut: Sut, padding: String) = {
println(padding + sut.description + " " + sut.verb + sut.skippedSut.map(" (skipped: " + _ + ")").getOrElse(""))
sut.literalDescription foreach {s => println(padding + s)}
reportExamples(sut.examples, padding)
println("")
}
/**
* prints the statistics for a sut
*/
def printStats(sut: Sut, padding: String): Unit = {
println(padding + "Total for SUT \"" + sut.description + "\":")
printStats(stats(sut), padding)
}
/**
* prints the statistics for a specification
*/
def printStats(stat: (Int, Int, Int, Int, Int), padding: String) = {
val (examplesNb, assertionsNb, failuresNb, errorsNb, skippedNb) = stat
def plural[T](nb: int) = if (nb > 1) "s" else ""
println(padding + "Finished in " + timer.stop)
println(padding +
examplesNb + " example" + plural(examplesNb) +
(if (skippedNb > 0) " (" + skippedNb + " skipped)" else "") + ", " +
assertionsNb + " assertion" + plural(assertionsNb) + ", " +
failuresNb + " failure" + plural(failuresNb) + ", " +
errorsNb + " error" + plural(errorsNb)
)
println("")
}
/**
* reports a list of examples and indent subexamples if there are some
*/
def reportExamples(examples: Iterable[Example], padding: String): Unit = {
for (example <- examples) {
reportExample(example, padding)
reportExamples(example.subExamples, padding + " ")
}
}
/**
* reports one example: + if it succeeds, x if it fails, its description, its failures or errors
*/
def reportExample(example: Example, padding: String) = {
def status(example: Example) = {
if (example.errors.size + example.failures.size > 0)
"x "
else if (example.skipped.size > 0)
"o "
else
"+ "
}
println(padding + status(example) + example.description)
if (stacktrace && example.errors.size > 0) example.errors foreach { printStackTrace(_) }
// if the failure, skip or the error message has linefeeds they must be padded too
def parens(f: Throwable) = " (" + f.location + ")"
example.skipped.toList ::: example.failures.toList ::: example.errors.toList foreach { f: Throwable =>
if (f.getMessage != null)
println(padding + " " + f.getMessage.replaceAll("\n", "\n" + padding + " ") + parens(f))
else
println(padding + " exception message is null" + parens(f))
}
}
}
/**
* Implementation of the <code>OutputReporter</code> with a <code>ConsoleOutput</code>
* and a <code>SimpleTimer</code>
*/
trait ConsoleReporter extends OutputReporter with ConsoleOutput {
/** this timer uses java Calendar to compute hours, minutes, seconds and milliseconds */
val timer = new SimpleTimer
}