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

org.specs2.matcher.MatchResult.scala Maven / Gradle / Ivy

There is a newer version: 3.7
Show newest version
package org.specs2
package matcher

import scalaz.{ Functor, Scalaz }, Scalaz._
import execute._
import MatchResultLogicalCombinators._
import ResultLogicalCombinators._

/**
 * Result of a Match.
 * 
 * A MatchResult contains several information about a match on an expectable:
 * 
 * - the expectable value, to allow the chaining of matches
 * - a pair of messages ok message / ko message to allow the easy creation of the negation
 *   of a match
 * 
 * A MatchResult can be transformed to a simple Result object to be the body of an Example.
 * 
 * There are different kinds of MatchResults, some of them being only created to support
 * English-like combination of Matchers:
 * 
 * `1 must be equalTo(1) and not be equalTo(2)`
 * 
 * In an Expectation like the one above, there is a left to right evaluation:
 * 
 *  1. be is a NeutralMatcher, returning a NeutralMatch doing nothing yet, just storing
 *     the expectable
 *  
 *  2. equalTo(1) is a real Matcher which is applied to the NeutralMatch MatchResult
 *     thanks to an implicit definition in the BeHaveAnyMatchers trait. This yields a 
 *     MatchSuccess result
 *  
 *  3. not creates a NotMatcher and can be and-ed with the previous MatchSuccess to
 *     yield a AndMatch(MatchSuccess, NotMatch), with NotMatch being the result of
 *     applying the NotMatcher to the expectable. This AndMatch is evaluated to create a 
 *     AndNotMatch(MatchSuccess, MatchSkip)
 *     
 *     Basically this is like forming an evaluation
 *     structure which will be resolved when the next 'real' matcher will arrive
 *     
 *  4. the AndNotMatch get nows it be method called with the equalTo Matcher.
 *     This results in equalTo being applied to the AndNotMatch, effectively doing:
 *     MatchSuccess and MatchSkip.apply(equalTo(2).not), which is
 *     MatchSuccess and expectable.applyMatcher(equalTo(2).not) which is MatchSuccess
 * 
 * @see org.specs2.matcher.BeHaveMatchersSpec for examples
 */
trait MatchResult[+T] extends ResultLike {
  /** the value being matched */
  val expectable: Expectable[T]

  /** 
   * apply a Matcher to the expectable contained in that MatchResult. Depending on the exact type of the MatchResult,
   * that logic may vary.
   *
   * Note: this method is marked as "private" to give a clue to the user when semi-column inference fails to apply.
   *
   * For example in that case:
   *
   *    "string" must not beNull
   *    1 must_== 1
   *
   * There will be a compilation error as if the apply method was used between the first and second line:
   *    ("string" must not beNull).apply(1)
   *
   * For a more detailed explanation, see: http://bit.ly/12STc95
   *
   */
  private[specs2]
  def apply(m: Matcher[T]): MatchResult[T]

  /** alias for the apply method, to be used outside specs2 */
  def applyMatcher(m: Matcher[T]): MatchResult[T] = apply(m)

  /** @return the negation of this result */
  def negate: MatchResult[T]

  /** apply the matcher */
  def be(m: Matcher[T]) = {
    if (m == null) apply(new BeNull)
    else apply(m)
  }
  def be[S >: T <: AnyRef](s: S) = {
    apply(new BeTheSameAs(s))
  }
  /** apply the matcher */
  def have(m: Matcher[T]) = apply(m)
  def toResult: Result = evaluate.toResult

  def isSuccess = toResult.isSuccess
  def message = toResult.message
  def orThrow = this
  def orSkip = this

  /** @return the MatchResult with no messages */
  def mute: MatchResult[T] = this
  /** update the failure message of this match result */
  def updateMessage(f: String => String) = this
  /** set a new failure message on this match result */
  def setMessage(message: String) = updateMessage((s: String) => message)

  /** the value being matched */
  protected[specs2] def evaluate[S >: T]: MatchResult[S] = this
}

/**
 * The signature of this class constructor is unusual, with a useless implicit parameter.
 *
 * This is actually here to avoid overloading conflicts with the apply method in the companion object
 */
case class MatchSuccess[T] private[specs2](ok: () => String, ko: () => String, expectable: Expectable[T])(implicit p: Int = 0) extends MatchResult[T] {
  def okMessage = ok()
  def koMessage = ko()
  override def toResult = Success(okMessage)

  def negate: MatchResult[T] = MatchFailure(koMessage, okMessage, expectable)
  def apply(matcher: Matcher[T]): MatchResult[T] = expectable.applyMatcher(matcher)
  override def mute = MatchSuccess("", "", expectable)
  override def updateMessage(f: String => String) = copy(ok = () => f(okMessage), ko = () => f(koMessage))
}
object MatchSuccess {
  def apply[T](ok: =>String, ko: =>String, expectable: Expectable[T]) =
    new MatchSuccess(() => ok, () => ko, expectable)
}
case class MatchFailure[T] private[specs2](ok: () => String, ko: () => String, expectable: Expectable[T],
                                           trace: List[StackTraceElement] = Nil, details: Details = NoDetails) extends MatchResult[T] {
  def okMessage = ok()
  def koMessage = ko()

  /** an exception having the same stacktrace */
  lazy val exception = new Exception(koMessage)

  override def toResult = Failure(koMessage, okMessage, trace, details)

  def negate: MatchResult[T] = MatchSuccess(koMessage, okMessage, expectable)
  def apply(matcher: Matcher[T]): MatchResult[T] = expectable.applyMatcher(matcher)

  override def mute                               = copy(ok = () => "", ko = () => "")
  override def updateMessage(f: String => String) = copy(ok = () => f(okMessage), ko = () => f(koMessage))
  override def orThrow: MatchFailure[T]           = throw new FailureException(toResult)
  override def orSkip: MatchFailure[T]            = throw new SkipException(toResult)
}
object MatchFailure {
  def create[T](ok: =>String, ko: =>String, expectable: Expectable[T], trace: List[StackTraceElement], details: Details): MatchFailure[T] =
    new MatchFailure(() => ok, () => ko, expectable, trace, details)

  def create[T](ok: =>String, ko: =>String, expectable: Expectable[T], details: Details): MatchFailure[T] =
    create(ok, ko, expectable, Nil, details)

  def apply[T](ok: =>String, ko: =>String, expectable: Expectable[T]): MatchFailure[T] =
    create(ok, ko, expectable, Nil, NoDetails)
}

case class MatchSkip[T] private[specs2](override val message: String, expectable: Expectable[T]) extends MatchResult[T] {
  def negate: MatchResult[T] = this
  def apply(matcher: Matcher[T]): MatchResult[T] = expectable.applyMatcher(matcher)
  override def toResult = Skipped(message)
  override def mute = MatchSkip("", expectable)
  override def updateMessage(f: String => String) = MatchSkip(f(message), expectable)
  override def orThrow: MatchSkip[T] = throw new SkipException(toResult)
}
case class MatchPending[T] private[specs2](override val message: String, expectable: Expectable[T]) extends MatchResult[T] {
  def negate: MatchResult[T] = this
  def apply(matcher: Matcher[T]): MatchResult[T] = expectable.applyMatcher(matcher)
  override def toResult = Pending(message)
  override def mute = MatchPending("", expectable)
  override def updateMessage(f: String => String) = MatchPending(message, expectable)
  override def orThrow: MatchPending[T] = throw new PendingException(toResult)
}
case class NotMatch[T] private[specs2](m: MatchResult[T]) extends MatchResult[T] {
  val expectable = m.expectable
  override def evaluate[S >: T] = m
  def negate: MatchResult[T] = NeutralMatch(m)
  def apply(matcher: Matcher[T]): MatchResult[T] = m(matcher.not)
}
case class NeutralMatch[T] private[specs2](m: MatchResult[T]) extends MatchResult[T] {
  val expectable = m.expectable
  override def evaluate[S >: T] = m
  def negate: MatchResult[T] = NotMatch(m)
  def apply(matcher: Matcher[T]): MatchResult[T] = m(matcher)
}
class AndMatch[T] private[specs2](first: MatchResult[T], second: =>MatchResult[T]) extends MatchResult[T] {
  val expectable = m1.expectable
  lazy val m1 = first
  lazy val m2 = second
  override def evaluate[S >: T] = {
    m1 match {
      case f: MatchFailure[_] => m1
      case _ =>
        (m1, m2) match {
          case (_, NeutralMatch(_))                              => new AndMatch(m1, MatchSkip("", expectable))
          case (NeutralMatch(_), _)                              => new AndMatch(m2, MatchSkip("", expectable))
          case (NotMatch(_), NotMatch(_))                        => new AndNotMatch(m1.evaluate, m2.evaluate)
          case (_, NotMatch(_))                                  => new AndNotMatch(m1, MatchSkip("", expectable))
          case (NotMatch(_), _)                                  => new AndMatch(m1.evaluate, m2).evaluate
          case (s: MatchSuccess[_], f: MatchFailure[_])          => m2
          case (MatchSuccess(_, _, _), _)                        => m1
          case (_, MatchSuccess(_, _, _))                        => m2
          case (_, _)                                            => m1
        }
    }
  }
  def negate: MatchResult[T] = new OrMatch(m1.not, m2.not).evaluate
  def apply(matcher: Matcher[T]): MatchResult[T] = m1 and m2(matcher)
  override def toResult = m1.toResult and m2.toResult
}
class AndNotMatch[T] private[specs2](first: MatchResult[T], second: =>MatchResult[T]) extends MatchResult[T] {
  val expectable = m1.expectable
  lazy val m1 = first
  lazy val m2 = second

  override def evaluate[S >: T] = m1 and m2.not
  def negate: MatchResult[T] = new OrMatch(m1.not, m2).evaluate
  def apply(matcher: Matcher[T]): MatchResult[T] = m1 and m2(matcher.not)
}
class OrMatch[T] private[specs2](first: MatchResult[T], second: =>MatchResult[T]) extends MatchResult[T] {
  val expectable = m1.expectable
  lazy val m1 = first
  lazy val m2 = second
  override def evaluate[S >: T] = {
    m1 match {
      // see MatchResultLogicalCombinatorsSpec for a case where OrMatches are nested together. see #233
      case om: OrMatch[_] if om.m1.isSuccess => new OrMatch(om.m1, MatchSkip("", expectable))
      case MatchSuccess(_, _, _)             => new OrMatch(m1, MatchSkip("", expectable))
      case _ => {
        (m1, m2) match {
          case (_, NeutralMatch(_))                                        => new OrMatch(m1, MatchSkip("", expectable))
          case (NeutralMatch(_), _)                                        => new OrMatch(m2, MatchSkip("", expectable))
          case (NotMatch(_), NotMatch(_))                                  => new OrNotMatch(m1.evaluate, m2)
          case (_, NotMatch(_))                                            => new OrNotMatch(m1, m2)
          case (NotMatch(_), _)                                            => new OrMatch(m1.evaluate, m2).evaluate
          case (_, MatchSuccess(_, _, _))                                  => m2
          case (MatchFailure(ok,ko,e,t,d), MatchFailure(ok2,ko2,e2,t2,d2)) => MatchFailure.create(ok()+"; "+ok2(), ko()+"; "+ko2(), e, t, d)
          case (_, _) => m1
        }
      }
    }
  }
  def negate: MatchResult[T] = new AndMatch(m1.not, m2.not).evaluate
  def apply(matcher: Matcher[T]): MatchResult[T] = m1 or m2(matcher)
  override def toResult = m1.toResult or m2.toResult
}
class OrNotMatch[T] private[specs2](first: MatchResult[T], second: =>MatchResult[T]) extends MatchResult[T] {
  lazy val m1 = first
  lazy val m2 = second
  val expectable = m1.expectable
  override def evaluate[S >: T] = m1 or m2.not
  def negate: MatchResult[T] = new AndMatch(m1.not, m2).evaluate
  def apply(matcher: Matcher[T]): MatchResult[T] = m1 or evaluate(matcher.not)
}

/**
 * Utility functions for MatchResult.
 *
 * A MatchResult is a Functor where the fmap function acts on the embedded Expectable value (which itself is a Functor)
 */
object MatchResult {
  import Expectable._

  implicit val MatchResultFunctor: Functor[MatchResult] = new Functor[MatchResult] {
    def map[A, B](m: MatchResult[A])(f: A => B) = m match {
      case success: MatchSuccess[_] => success.map(f)
      case failure: MatchFailure[_] => failure.map(f)
      case skip: MatchSkip[_]       => skip.map(f)
      case pending: MatchPending[_] => pending.map(f)
      case neg: NotMatch[_]         => neg.map(f)
      case neutral: NeutralMatch[_] => neutral.map(f)
      case and: AndMatch[_]         => and.map(f)
      case andnot: AndNotMatch[_]   => andnot.map(f)
      case or: OrMatch[_]           => or.map(f)
      case ornot: OrNotMatch[_]     => ornot.map(f)
    }
  }
  implicit val MatchSuccessFunctor: Functor[MatchSuccess] = new Functor[MatchSuccess] {
    def map[A, B](m: MatchSuccess[A])(f: A => B) =
      MatchSuccess(m.okMessage, m.koMessage, m.expectable.map(f))
  }
  implicit val MatchFailureFunctor: Functor[MatchFailure] = new Functor[MatchFailure] {
    def map[A, B](m: MatchFailure[A])(f: A => B) = MatchFailure(m.okMessage, m.koMessage, m.expectable.map(f))
  }
  implicit val MatchSkipFunctor: Functor[MatchSkip] = new Functor[MatchSkip] {
    def map[A, B](m: MatchSkip[A])(f: A => B) = new MatchSkip(m.message, m.expectable.map(f))
  }
  implicit val MatchPendingFunctor: Functor[MatchPending] = new Functor[MatchPending] {
    def map[A, B](m: MatchPending[A])(f: A => B) = new MatchPending(m.message, m.expectable.map(f))
  }
  implicit val NotMatchFunctor: Functor[NotMatch] = new Functor[NotMatch] {
    def map[A, B](n: NotMatch[A])(f: A => B) = new NotMatch(n.m.map(f))
  }
  implicit val NeutralMatchFunctor: Functor[NeutralMatch] = new Functor[NeutralMatch] {
    def map[A, B](n: NeutralMatch[A])(f: A => B) = new NeutralMatch(n.m.map(f))
  }
  implicit val AndMatchFunctor: Functor[AndMatch] = new Functor[AndMatch] {
    def map[A, B](m: AndMatch[A])(f: A => B) = new AndMatch(m.m1.map(f), m.m2.map(f))
  }
  implicit val AndNotMatchFunctor: Functor[AndNotMatch] = new Functor[AndNotMatch] {
    def map[A, B](m: AndNotMatch[A])(f: A => B) = new AndNotMatch(m.m1.map(f), m.m2.map(f))
  }
  implicit val OrMatchFunctor: Functor[OrMatch] = new Functor[OrMatch] {
    def map[A, B](m: OrMatch[A])(f: A => B) = new OrMatch(m.m1.map(f), m.m2.map(f))
  }
  implicit val OrNotMatchFunctor: Functor[OrNotMatch] = new Functor[OrNotMatch] {
    def map[A, B](m: OrNotMatch[A])(f: A => B) = new OrNotMatch(m.m1.map(f), m.m2.map(f))
  }

  implicit def matchResultAsResult[M[_] <: MatchResult[_], T]: AsResult[M[T]] = new AsResult[M[T]] {
    def asResult(t: =>M[T]): Result = AsResult(t.toResult)
  }

  /** implicit typeclass instance to create examples from a sequence of MatchResults */
  implicit def matchResultSeqAsResult[T]: AsResult[Seq[MatchResult[T]]] = new AsResult[Seq[MatchResult[T]]] {
    def asResult(t: =>Seq[MatchResult[T]]): Result = t.foldLeft(StandardResults.success: Result)(_ and _.toResult)
  }

  /** sequence a list of MatchResults into a MatchResult of a list */
  def sequence[T](seq: Seq[MatchResult[T]]): MatchResult[Seq[T]] =
    Matcher.result(AsResult(seq), MustExpectations.createExpectable(seq.map(_.expectable.value)))
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy