All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.specs2.execute.Result.scala Maven / Gradle / Ivy

package org.specs2
package execute
import control.Throwablex
import control.Throwablex._
import text.NotNullStrings._
import main.Arguments
import org.specs2.internal.scalaz.Scalaz._
import internal.scalaz.{Foldable, Monoid}
import text.Message.concat

/**
 * The result of an execution, either:
 *
 *  - a success: the execution is ok
 *  - a failure: an expectation is not met
 *  - an error: an exception occurred
 *  - a pending execution: the user has decided that execution must not be performed
 *  - a skipped execution: based on dynamic conditions (a database not available for instance)
 *    the execution is not performed 
 * 
 * A Result has:
 *  - a message describing the outcome
 *  - a message describing the expectation
 *  - possibly a number of expectations
 *    when it is the outcome of several checks (this is used for the reporting of ScalaCheck properties).
 * 
 */
sealed abstract class Result(val message: String = "", val expected: String = "", val expectationsNb: Int = 1) {
  /**
   * @return the colored textual status of the result
   */
  def coloredStatus(implicit args: Arguments = Arguments()): String = {
    if (args.plan)
      args.pendingColor("*")
    else {
      this match {
        case Success(_,_)          => args.successColor("+")
        case Failure(_, _, _, _)   => args.failureColor("x")
        case Error(_, _)           => args.errorColor  ("!")
        case Pending(_)            => args.pendingColor("*")
        case Skipped(_, _)         => args.skippedColor("o")
        case DecoratedResult(_, r) => r.coloredStatus(args)
      }
    }
  }

  private lazy val nocolor = Arguments("nocolor")
  /**Skipped
   * @return the uncolored textual status of the result
   */
  def status: String = coloredStatus(nocolor)

  /** @return the textual status of the result */
  def statusName(implicit args: Arguments = Arguments()): String =
    if (args.plan)
      "info"
    else {
      this match {
        case Success(_,_)          => "success"
        case Failure(_, _, _, _)   => "failure"
        case Error(_, _)           => "error"
        case Pending(_)            => "pending"
        case Skipped(_, _)         => "skipped"
        case DecoratedResult(_, r) => r.statusName(args)
      }
    }

  /** update the message of a result, keeping the subclass type */
  def updateMessage(msg: String): Result =
	  this match {
	    case Success(m, e)         => Success(msg,e)
	    case Failure(m, e, st, d)  => Failure(msg, e, st, d)
	    case Error(m, st)          => Error(msg, st)
	    case Skipped(m, e)         => Skipped(msg, e)
	    case Pending(m)            => Pending(msg)
      case DecoratedResult(t, r) => DecoratedResult(t, r.updateMessage(msg))
	  }

  /** change this result's message */
  def mapMessage(f: String => String): Result = updateMessage(f(message))

  /** update the expected of a result, keeping the subclass type */
  def updateExpected(exp: String): Result =
    this match {
      case Success(m, e)         => Success(m,exp, expectationsNb)
      case Failure(m, e, st, d)  => Failure(m, exp, st, d).setExpectationsNb(expectationsNb)
      case Skipped(m, e)         => Skipped(m, exp)
      case DecoratedResult(t, r) => DecoratedResult(t, r.updateExpected(exp))
      case other                 => this
    }

  /** change this result's expected */
  def mapExpected(f: String => String): Result = updateExpected(f(expected))

  /**
   * increment the number of expectations
   */
  def addExpectationsNb(n: Int): Result = setExpectationsNb(expectationsNb + n)
  /**
   * set the number of expectations
   */
  def setExpectationsNb(n: Int): Result

  /**
   * @return true if the result is a Success instance
   */
  def isSuccess: Boolean = false
  /**
   * @return true if the result is an Error instance
   */
  def isError: Boolean = false
  /**
   * @return true if the result is a Skipped instance
   */
  def isSkipped: Boolean = false
  /**
   * @return true if the result is a Pending instance
   */
  def isPending: Boolean = false
  /**
   * @return true if the result is a Skipped or Pending
   */
  def isSuspended: Boolean = isSkipped || isPending
  /**
   * @return true if the result is a Failure instance
   */
  def isFailure: Boolean = false
  /**
   * @return the result with no message
   */
  def mute: Result

}
object Result {

  /**
   * @return the accumulation of all results, without success messages
   */
  def issues(results: Seq[Result], separator: String = "; ") =
    results.foldMap(identity)(implicitly[Foldable[Seq]], ResultFailuresMonoid(separator)).addExpectationsNb(-1)

  implicit val ResultMonoid: Monoid[Result] = new Monoid[Result] {
    val zero = Success()
    def append(m1: Result, m2: =>Result) = {
      (m1, m2) match {
        case (Success(msg1, e1),           Success(msg2, e2))          => Success(msg1+"; "+msg2, concat(e1, e2))
        case (Success(msg1, e1),           Skipped(msg2, e2))          => Success(msg1+"; "+msg2, e1)
        case (Skipped(msg1,  _),           Success(msg2, e2))          => Success(msg1+"; "+msg2, e2)
        case (Pending(msg1),               Success(msg2, e2))          => Success(msg1+"; "+msg2, e2)
        case (Success(msg1,e1),            Pending(msg2))              => Success(msg1+"; "+msg2, e1)

        case (Success(msg1, e1),           Failure(msg2, e2, st1, d2)) => m2.updateMessage(msg1+"; "+msg2)
        case (Failure(msg1, e1, st1, d1),  Failure(msg2, e2, st2, d2)) => Failure(msg1+"; "+msg2,
                                                                                  concat(e1, e2), st1, NoDetails())

        case (Success(msg1, e1),           Error(msg2, st1))           => m2.updateMessage(msg1+"; "+msg2)
        case (Error(msg1, st1),            Error(msg2, st2))           => Error(msg1+"; "+msg2, st1)
        case (Error(msg1, st1),            Failure(msg2, e2, st2, d2)) => Error(msg1+"; "+msg2, st1)

        case (Skipped(msg1, e1),           Skipped(msg2, e2))          => Skipped(msg1+"; "+msg2, e1+"; "+e2)
        case (Skipped(msg1, e1),           Pending(msg2))              => Pending(msg1+"; "+msg2)
        case (Pending(msg1),               Skipped(msg2, e2))          => Pending(msg1+"; "+msg2)
        case (Pending(msg1),               Pending(msg2))              => Pending(msg1+"; "+msg2)

        case (DecoratedResult(t, r),       other)                      => DecoratedResult(t, append(r, other))
        case (other,                       DecoratedResult(t, r))      => DecoratedResult(t, append(other, r))

        case (Failure(msg1, e1, st, d),    _)                          => m1
        case (Error(msg1, st),          _)                             => m1
        case (_,                           Failure(msg1, e1, st, d))   => m2
        case (_,                           Error(msg1, st))            => m2
      }
    }.setExpectationsNb(m1.expectationsNb + m2.expectationsNb)
  }
  /**
   * This monoids "absorbs" success messages if the result of the |+| is not a success
   */
  implicit val ResultFailureMonoid: Monoid[Result] = ResultFailuresMonoid("; ")
  implicit def ResultFailuresMonoid(separator: String): Monoid[Result] = new Monoid[Result] {
      val zero = Success()
    def append(m1: Result, m2: =>Result) = {
      (m1, m2) match {
        case (Success(msg1, e1),           Success(msg2, e2))          => Success("", concat(e1, e2))
        case (Success(msg1, e1),           other)                      => other
        case (other,                       Success(msg2, e2))          => other
        case (Failure(msg1, e1, st1, d1),  Failure(msg2, e2, st2, d2)) => Failure(concat(msg1, msg2, separator), e1+separator+e2, st1, NoDetails())
        case (Error(msg1, st1),            Error(msg2, st2))           => Error(concat(msg1, msg2, separator), st1)
        case (Error(msg1, st1),            Failure(msg2, e2, st2, d2)) => Error(concat(msg1, msg2, separator), st1)
        case (Skipped(msg1, e1),           Skipped(msg2, e2))          => Skipped(concat(msg1, msg2, separator), e1+separator+e2)
        case (Skipped(msg1, e1),           Pending(msg2))              => Pending(concat(msg1, msg2, separator))
        case (Pending(msg1),               Skipped(msg2, e2))          => Pending(concat(msg1, msg2, separator))
        case (Pending(msg1),               Pending(msg2))              => Pending(concat(msg1, msg2, separator))
        case (DecoratedResult(t, r),       other)                      => DecoratedResult(t, append(r, other))
        case (other,                       DecoratedResult(t, r))      => DecoratedResult(t, append(other, r))
        case (Failure(msg1, e1, st, d),    _)                          => m1
        case (Error(msg1, st),             _)                          => m1
        case (_,                           Failure(msg1, e1, st, d))   => m2
        case (_,                           Error(msg1, st))            => m2
      }
    }.setExpectationsNb(m1.expectationsNb + m2.expectationsNb)
  }

}

trait Results {
  /**
   * implicit definition to accept any boolean value as a Result
   * This avoids writing b must beTrue
   */
  implicit def toResult(b: Boolean): Result =
    if (b) execute.Success("true") else execute.Failure("false")

}

object Results extends Results

/**
 * Typeclass trait for anything that can be transformed to a Result
 */
trait AsResult[T] {
  def asResult(t: =>T): Result
}

object AsResult {
  /** implicit typeclass instance to create examples from Results */
  implicit def resultAsResult[R <: Result]: AsResult[R] = new AsResult[R] {
    def asResult(t: =>R): Result = t
  }
  /** implicit typeclass instance to create examples from Booleans */
  implicit def booleanAsResult: AsResult[Boolean] = new AsResult[Boolean] {
    def asResult(t: =>Boolean): Result = Results.toResult(t)
  }

  /** nicer syntax to use the AsResult syntax: AsResult(r) */
  def apply[R : AsResult](r: =>R): Result = implicitly[AsResult[R]].asResult(r)

  /** typeclass instance for types which are convertible to Result */
  implicit def asResult[R <% Result]: AsResult[R] = new AsResult[R] {
    def asResult(r: =>R): Result = r
  }
}

/**
 * This class represents the success of an execution
 */
case class Success(m: String = "", exp: String = "")  extends Result(m, exp) {
  override def isSuccess = true

  def setExpectationsNb(n: Int): Result = Success(m, expected, n)

  def mute = Success()

}
/**
 * Companion object to the Success class providing 
 * a method to set the expectations number
 */
object Success {
  def apply(m: String, exp: String, expNb: Int) = new Success(m, exp) {
    override val expectationsNb = expNb
  }
  def apply(m: String, expNb: Int) = new Success(m) {
	  override val expectationsNb = expNb
  }
}
/** 
 * This class represents the failure of an execution.
 * It has a message and a stacktrace
 */
case class Failure(m: String = "", e: String = "", stackTrace: List[StackTraceElement] = new Exception().getStackTrace.toList, details: Details = NoDetails())
  extends Result(m, e) with ResultStackTrace { outer =>
  /** @return an exception created from the message and the stackTraceElements */
  def exception = Throwablex.exception(m, stackTrace)

  def setExpectationsNb(n: Int): Result = new Failure(m, e, stackTrace, details) {
    override val expectationsNb = n
  }
  def mute = copy(m = "",  e = "")

  override def toString = m
  override def equals(o: Any) = {
    o match {
      case Failure(m2, e2, _, _) => m == m2 && e == e2
      case _ => false
    }
  }
  override def hashCode = m.hashCode + e.hashCode
  override def isFailure: Boolean = true

  def skip: Skipped = Skipped(m, e)
}

/**
 * Trait to model detailled information for failures so that smart differences can be computed
 */
sealed trait Details
case class FailureDetails(expected: String, actual: String) extends Details
case class NoDetails() extends Details
/** 
 * This class represents an exception occurring during an execution.
 */
case class Error(m: String, e: Exception) extends Result(m) with ResultStackTrace { outer =>
  /** @return an exception created from the message and the stackTraceElements */
  def exception = e
  def stackTrace = e.getFullStackTrace.toList

  override def equals(o: Any) = {
    o match {
      case Error(m2, e2) => m == m2 && e.getMessage == e2.getMessage
      case _ => false
    }
  }

  def setExpectationsNb(n: Int): Result = new Error(m, e) {
    override val expectationsNb = n
  }
  def mute = copy(m = "")

  override def hashCode = m.hashCode
  override def isError: Boolean = true
}
/** 
 * This object allows to create an Error from an exception
 */
case object Error {
  def apply(e: Exception) = new Error(e.getMessage.notNull, e)
  def apply(t: Throwable) = new Error(t.getMessage.notNull, new ThrowableException(t))
  case class ThrowableException(t: Throwable) extends Exception(t.getClass.getSimpleName+": "+t.getMessage, t)
  def apply(m: String = "") = new Error(m, new Exception(m))
}
/** 
 * Pending result
 * @see Result for description
 */
case class Pending(m: String = "")  extends Result(m) { outer =>

  def mute = Pending()
  def setExpectationsNb(n: Int): Result = new Pending(m) {
    override val expectationsNb = n
  }

  override def isPending: Boolean = true
}
/** 
 * Skipped result
 * @see Result for description 
 */
case class Skipped(m: String = "", e: String = "")  extends Result(m, e) { outer =>

  def mute = Skipped()
  def setExpectationsNb(n: Int): Result = new Skipped(m) {
    override val expectationsNb = n
  }

  override def isSkipped: Boolean = true
}

/**
 * This result allows to embed additional data with a given result for further display
 *
 * Is is used to provide a way to display properly the data tables in the HtmlExporter
 */
case class DecoratedResult[+T](decorator: T, result: Result) extends Result(result.message, result.expected) { outer =>
  def mute = DecoratedResult(decorator, result.mute)
  def setExpectationsNb(n: Int): Result = new DecoratedResult(decorator, result) {
    override val expectationsNb = n
  }
    
  override def isSuccess: Boolean       = result.isSuccess
  override def isError: Boolean         = result.isError
  override def isSkipped: Boolean       = result.isSkipped
  override def isPending: Boolean       = result.isPending
  override def isFailure: Boolean       = result.isFailure

  /** use another decorator */
  def decorate[S](otherDecorator: S) = DecoratedResult(otherDecorator, result)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy