
org.specs.runner.Reporter.scala Maven / Gradle / Ivy
The newest version!
/**
* Copyright (c) 2007-2011 Eric Torreborre
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software. Neither the name of specs nor the names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written permission.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
* TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
package org.specs.runner
import scala.collection.mutable.Queue
import org.specs.log.ConsoleLog
import org.specs.specification._
import org.specs.util.Configuration._
import org.specs.util.Configuration
import org.specs.util.Property
import org.specs._
/**
* The SpecsHolder trait is used by any class providing access to a sequence of specifications
*/
trait SpecsHolder {
val specs: Seq[Specification]
}
/**
* An object using the reporter trait should be able to report the result of the execution of several specifications.
* Those specifications are usually provided by the object when using the reportSpecs method, but
* the report functionality can also be accessed by passing other specifications directly to the report(specs) method.
*
* Any object using the Reporter trait will also inherit a main method controlling:
* - the reading of command line arguments
* - the display of stacktraces
* - the acception or rejection of some tags
*
*
* The accepted arguments are:
* - -ns or --nostacktrace to avoid displaying stacktraces
* - -acc, --accept followed by a comma separated list of tag names for the tags to accept
* - -rej, --reject followed by a comma separated list of tag names for the tags to reject
*
*
* When subclassing the Reporter trait, the subclasses should usually override the report(specs) method
* to provide concrete reporting behavior.
*
* Subclasses must not forget to insert a super.report call at the beginning of their processing
* to allow the chaining of several reporters as traits:
* object runner extends Runner(spec) with Html with Xml for example
*/
trait Reporter extends SpecsFilter with ConsoleLog {
private val userConfiguration: Property[Configuration] = Property(config)
/** this variable controls if stacktraces should be printed. */
private[specs] val stacktrace = Property(userConfiguration().stacktrace)
/** this variable controls if ok examples should be printed. */
private[specs] val failedAndErrorsOnly = Property(userConfiguration().failedAndErrorsOnly)
/** this variable controls if the statistics should be printed. */
private[specs] val statistics = Property(userConfiguration().statistics)
/** this variable controls if the final statistics should be printed. */
private[specs] val finalStatisticsOnly = Property(userConfiguration().finalStatisticsOnly)
/** this variable controls if the ANSI color sequences should be used to colorize output */
private[specs] val colorize = Property(userConfiguration().colorize)
/** this variable controls if the examples must not be executed and only high-level descriptions must be displayed */
private[specs] val planOnly = Property(false)
/** set a new configuration object. */
def setConfiguration(className: Option[String]): this.type = {
className.map((name: String) => Configuration.config = Configuration.getConfiguration(name))
setOptionsFromConfig()
this
}
/** allow subclasses to remove the stacktrace display. */
def setNoStacktrace(): this.type = { stacktrace(false); this }
/** allow subclasses to remove the ok and skipped examples. */
def setFailedAndErrorsOnly(): this.type = { failedAndErrorsOnly(true); this }
/** allow subclasses to remove the statistics. */
def setNoStatistics(): this.type = { statistics(false); this }
/** allow subclasses to print the final statistics.only */
def setFinalStatisticsOnly(): this.type = { finalStatisticsOnly(true); this }
/** allow subclasses to add colorization to the output. */
def setColorize(): this.type = { colorize(true); this }
/** allow subclasses to display high-level descriptions only. */
def setPlanOnly(): this.type = { planOnly(true); this }
/** reset all options. */
def resetOptions(): this.type = {
args = Array()
setOptionsFromConfig()
}
def setOptionsFromConfig(): this.type = {
userConfiguration(config)
stacktrace(userConfiguration().stacktrace)
failedAndErrorsOnly(userConfiguration().failedAndErrorsOnly)
colorize(userConfiguration().colorize)
statistics(userConfiguration().statistics)
finalStatisticsOnly(userConfiguration().finalStatisticsOnly)
this
}
def runConfiguration = {
val outer = this
new Configuration {
override def stacktrace = outer.stacktrace()
override def failedAndErrorsOnly = outer.failedAndErrorsOnly()
override def statistics = outer.statistics()
override def finalStatisticsOnly = outer.finalStatisticsOnly()
override def colorize = outer.colorize()
}
}
/**
* optional arguments to be used in the main method and which can be set from the code directly.
*/
private var specArgs: Array[String] = Array()
def args = specArgs
def args_=(a: Array[String]) = {
specArgs = a
overrideConfigurationWithUserArgs()
}
/**
* Main method for the Reporter trait.
*
* It first agregates all arguments: passed to the class and passed from the command line.
* Then it calls the reportSpecs method and exit the System with the appropriate error code,
* depending on the specification success or not.
*/
def main(arguments: Array[String]) = {
if (arguments != null)
args = args ++ arguments
if (argsContain("-h", "--help")) {
displayHelp
} else {
reportSpecs
if (filteredSpecs.exists(_.isFailing)) exit(1) else exit(0)
}
}
/** override this method for a different handling of exiting. */
private[specs] def exit(code: Int) = System.exit(code)
/** display all help options. */
protected def displayHelp = {
displayUsage
displayOptions
displayOptionsDescription
}
/** display the usage. */
protected def displayUsage = {
println("usage java package.mySpecification")
}
/** display the options summary. */
protected def displayOptions = {
println("""
[-h|--help]
[-config|--configuration]
[-ns|--nostacktrace]
[-nostats|--nostatistics]
[-finalstats|--finalstatistics]
[-xonly | -failedonly]
[[-acc | --accept] tag1,tag2,...] [[-rej | --reject] tag1,tag2,...]
[-sus | --system]
[-ex | --example]
[-plan | --planOnly]
[-c | --color]""".stripMargin)
}
/** display the options description. */
protected def displayOptionsDescription = {
println("""
-h, --help print this message and doesn't execute the specification
-config, --configuration class name of an object extending the org.specs.util.Configuration trait
-ns, --nostacktrace remove the stacktraces from the reporting
-nostats, --nostatistics remove the statistics from the reporting
-finalstats, --finalstatistics print the final statistics only
-xonly, --failedonly report only failures and errors
-acc, --accept tags accept only the specified tags (comma-separated names)
-rej, --reject tags reject the specified tags (comma-separated names)
-sus, --system only the systems under specifications matching this regular expression will be executed
-ex, --example only the examples matching this regular expression will be executed
-plan, --planOnly only display the sus and first level descriptions without executing the examples
-c, --color report with color""".stripMargin)
}
/** regexp for filtering systems. */
override def susFilterPattern = argValue(args, List("-sus", "--system")).getOrElse(".*")
/** regexp for filtering examples. */
override def exampleFilterPattern = argValue(args, List("-ex", "--example")).getOrElse(".*")
/** report the list of specifications held by the object mixing this trait. */
def reportSpecs: this.type = {
try { report(this.filteredSpecs) }
catch {
case e: SpecsFilterPatternException => println(e.getMessage); this
}
}
/**
* report specifications.
*
* This method should usually be overriden by subclasses to provide concrete reporting behavior.
* Subclasses must not forget to insert a super.report call at the beginning of their processing
* to allow the chaining of several reporters as traits.
*/
def report(specs: Seq[Specification]): this.type = {
overrideConfigurationWithUserArgs()
setTags(specs)
debug("Reporter - reporting " + specs.map(_.description).mkString(", "))
this
}
/**
* set the specification configuration from the user arguments
*/
private def overrideConfigurationWithUserArgs() = {
if (argsContain("-config", "--configuration")) setConfiguration(argValue(args, List("-config", "--configuration")))
if (argsContain("-ns", "--nostacktrace")) setNoStacktrace()
if (argsContain("-nostats", "--nostatistics")) setNoStatistics()
if (argsContain("-finalstats", "-finalstatistics")) setFinalStatisticsOnly()
if (argsContain("-xonly", "--failedonly")) setFailedAndErrorsOnly()
if (argsContain("-plan")) setPlanOnly()
if (argsContain("-c", "--color")) setColorize()
}
/** @return true if the args contain one of the options, regardless of the case. */
private def argsContain(options: String*) = args.map(_.toLowerCase).exists(options.contains(_))
/**
* set the tags passed by the user on the specification.
* @param specifications list of specifications
* @param arguments user-defined arguments containing either -acc, --accept, -rej, --reject
*/
private[specs] def setTags(specifications: Seq[Specification]) = {
def printWarning = warning("accept/reject tags omitted in: " + specArgs.mkString(", "))
def acceptSpecTags(s: Specification, i: Int) = s.acceptTag(specArgs(i + 1).split(","):_*)
def rejectSpecTags(s: Specification, i: Int) = s.rejectTag(specArgs(i + 1).split(","):_*)
def setAcceptedTags(specifications: Seq[Specification], argumentNames: List[String], f: (Specification, Int) => Specification) = {
specArgs.map(_.toLowerCase).indexWhere(arg => argumentNames.contains(arg)) match {
case -1 => ()
case i if (i < specArgs.length - 1) => filteredSpecs.foreach(f(_, i))
case _ => if (!specArgs.isEmpty) printWarning
}
}
setAcceptedTags(specifications, List("-acc", "--accept"), acceptSpecTags(_, _))
setAcceptedTags(specifications, List("-rej", "--reject"), rejectSpecTags(_, _))
}
/**
* @return the argument value in a list of arguments for a given flag in the argumentNames list.
* for example: argValue(Array("-ex", ".*ex.*"), List("-ex", "--example")) = Some(".*ex.*")
*/
protected def argValue(arguments: Array[String], argumentNames: List[String]): Option[String] = {
arguments.map(_.toLowerCase).indexWhere(arg => argumentNames.contains(arg)) match {
case -1 => None
case i if (i < arguments.length - 1) => Some(arguments(i + 1))
case _ => {
if (!arguments.isEmpty) warning("missing values for flags: " + argumentNames.mkString(", ") + " in " + arguments.mkString(", "))
None
}
}
}
def ::(r: Reporter) = List(r, this)
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy