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

org.specs.matcher.Matchers.scala Maven / Gradle / Ivy

The newest version!
/**
 * Copyright (c) 2007-2009 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.matcher

import org.specs._
import org.specs.specification._
import org.specs.collection.ExtendedIterable._
import org.specs.util.ExtendedThrowable._
import org.specs.matcher.MatcherUtils._
import org.specs.execute._
import scala.xml.Node
/**
 * 

The Matchers object allows to import the functions from the Matchers trait. */ object Matchers extends Matchers /** *

The Matchers trait provides all existing Matchers to the * Specification trait

* They can be used with be/have + matcher syntax */ trait Matchers extends AnyMatchers with LogicalMatchers with StringMatchers with IterableMatchers with MapMatchers with NumericMatchers with PatternMatchers with XmlMatchers with FileMatchers with MatcherResult /** *

The BaseMatchers trait provides all existing Matchers to the * Specification trait

* They can't be used with be/have + matcher syntax */ trait BaseMatchers extends AnyBaseMatchers with LogicalMatchers with StringBaseMatchers with IterableBaseMatchers with MapBaseMatchers with NumericBaseMatchers with PatternBaseMatchers with XmlBaseMatchers with FileBaseMatchers with MatcherResult /** *

The AbstractMatcher class is the base class for Matchers. * * This class should be subclassed to provide an appropriate apply method that * will check a value a

. * * Children of that class can use the optional description string to provide enhanced failure messages. * * Implementation notes:
    *
  • the parameter to the apply method should be a by-name parameter. This allow some parameters not to be evaluated * when not necessary. For example in a must (m1(b) and m2(c)) m2(c) will not be evaluated if m1(b) is false
  • *
  • However in the implementation of the apply function, it must be taken care of not evaluating the parameter twice. Assigning it to * a val is the solution to this issue.
  • *
  • 2 messages are included in the result of the apply function, to allow the easy creation of the negation of matchers * with the not method.
  • *
  • The description attribute and the 'd' method can be used to construct precise messages describing the * value being matched against
  • *
* */ abstract class AbstractMatcher[-T] { /** * this function must be implemented by subclasses. * @return a triple with a boolean indicating the success or failure of the matcher and 2 messages, the ok message, * and the ko message */ def apply(a: => T): (Boolean, String, String) /** * Optional description of the expectable object that will be passed to the matcher. * It will be set on the matcher when creating it through the following construction: * a aka "this value" must beTrue.
* The 'aka' ("also known as") method sets the description in the ExpectableFactory. */ protected var desc: Option[String] = None /** @return the precise description of the example */ def description = desc /** set the description of the matcher */ def setDescription(d: Option[String]): this.type = { desc = d; this } /** @return the description of the matched value, quoted. */ protected def d(value: Any) = description match { case None => q(value) case Some(desc: String) => desc + " " + q(value) } /** @return the description of the matched value, unquoted. */ protected def dUnquoted(value: Any) = description match { case None => unq(value) case Some(desc) => desc + " " + unq(value) } } /** *

This class is the base class for checking if a given value, of type T, is matching an expected value

*

The matcher parameter is a function which takes a value and return a Boolean saying if the match is ok and * 2 messages: 1 if the match is ok, and another one if the match is ko. Those messages should usually specify which was the * expected value and which was the actual one

*

It is also possible to use the boolean logical operator on matchers: and, or, not, xor to combine matchers together. * This is the essential reason why the ok message is included in the matcher function. For instance, when the * not operator is used, the ok message is used as a ko message

* */ abstract class Matcher[-T] extends AbstractMatcher[T] with MatcherResult { outer => /** * The and operator allow to combine to matchers through a logical and. * m1 and m2 can successfully match a value a only if m1 succeeds * and m2 succeeds also */ def and[U <: T](m: => Matcher[U]): Matcher[U] = { new Matcher[U](){ def apply(a: => U) = { val value = a outer.setDescription(this.description) val r1 = outer(value) if (!r1.success) (false, r1.okMessage, r1.koMessage) else { val andMatcher = m andMatcher.setDescription(this.description) val r2 = andMatcher(value) (r2.success, r1.okMessage + " and " + r2.okMessage, r1.okMessage + " but " + r2.koMessage) } }}} /** * The or operator allow to combine to matchers through a logical or. * m1 or m2 can successfully match a value a if m1 succeeds * or m2 succeeds. m2 is not evaluated if m1 succeeds */ def or[U <: T](m: => Matcher[U]) : Matcher[U] = { new Matcher[U]() { def apply(a: =>U) = { val value = a outer.setDescription(this.description) val r1 = outer(value) if (r1.success) (true, r1.okMessage, r1.koMessage) else { val orMatcher = m orMatcher.setDescription(this.description) val r2 = orMatcher(value) if (r2.success) (true, r2.okMessage + " but " + r1.koMessage, r1.koMessage + " and " + r2.koMessage) else (false, r1.okMessage + " and " + r2.okMessage, r1.koMessage + " and " + r2.koMessage) } }}} /** * The xor operator allow to combine to matchers through a logical xor. * m1 xor m2 can successfully match a value a if m1 succeeds * and m2 fails, or if m1 fails and m2 succeeds */ def xor[U <: T](m: => Matcher[U]) : Matcher[U] = (this and m.not) or (this.not and m) /** * The not operator allow to combine to matchers through a logical not. * m1.not returns a matcher failing if m1 succeeds, and succeeding if m1 fails */ def not = { new Matcher[T]() { def apply(a: => T) = { outer.setDescription(this.description) val result = outer(a) (!result.success, result.koMessage, result.okMessage) }} } /** * The when operator returns a matcher which will be ok only if a condition is true */ def when(condition : => Boolean) = { new Matcher[T]() { outer.setDescription(this.description) def apply(a: => T) = { val result = outer(a) (if (condition) result.success else true, result.okMessage, result.koMessage) }} } /** * The unless operator returns a matcher which will be ok only if a condition is false */ def unless(condition : => Boolean) = when(!condition) /** * The lazily operator returns a matcher which will match a function returning the expected value */ def lazily = { new Matcher[() => T]() { outer.setDescription(this.description) def apply(a: => (() => T)) = outer(a.apply) } } /** * The ^^ operator returns a matcher which will apply a function before doing the match */ def composeWithFunction[A](f: A => T) = this ^^ f def ^^[A](f: A => T) = { new Matcher[A]() { outer.setDescription(this.description) def apply(a: => A) = outer(f(a)) } } /** * The orSkipExample operator throws a SkippedException if the matcher fails */ def orSkipExample = { val outer = this; new Matcher[T]() { outer.setDescription(this.description) def apply(a: => T) = { val result = outer(a) if (!result.success) { val skippedException = new SkippedException("skipped because " + result.koMessage) skippedException.throwWithStackTraceOf(new SkippedException("").removeTracesWhileNameMatches("(Expectable.scala|Matchers.scala)")) } (result.success, result.okMessage, result.koMessage) }} } /** * Alias for orSkipExample */ def orSkip = orSkipExample } /** * Result of Matcher.apply. Provides a method named 'success' to get the result * of a match, as well as 'okMessage' and 'koMessage' to get the status messages. */ trait MatcherResult { /** * This case class and the associated implicit definition are used to add more meaningful names to * the tuple representing the result of a match when implementing Matcher logical operators.
* Usage: matcher.apply(value).okMessage for instance */ case class MatcherResult(success: Boolean, okMessage: String, koMessage: String) implicit def toMatcherResult(t: (Boolean, String, String)): MatcherResult = MatcherResult(t._1, t._2, t._3) implicit def toTuple(m: MatcherResult): (Boolean, String, String) = (m.success, m.okMessage, m.koMessage) } abstract class OkWordMatcher[T] extends Matcher[T] class NotMatcher[T] extends Matcher[T] { def apply(v: =>T) = (false, "", "") } class BeVerbMatcher[T] extends OkWordMatcher[T] { def apply(v: =>T) = (true, "", "") def ==/(node: Iterable[Node]) = new EqualIgnoringSpaceMatcher(node) def ==/(s: String) = new BeEqualToIgnoringCase(s) def !=/(s: String) = new BeEqualToIgnoringCase(s).not def <[T](n: T)(implicit d: T => Double, e: T => Ordered[T]) = new BeLessThan(n) def <=[T](n: T)(implicit d: T => Double, e: T => Ordered[T]) = new BeLessThanOrEqualTo(n) def >[T](n: T)(implicit d: T => Double, e: T => Ordered[T]) = new BeLessThanOrEqualTo(n).not def >=[T](n: T)(implicit d: T => Double, e: T => Ordered[T]) = new BeLessThan(n).not def ~[T](n: T, delta: T)(implicit d: T => Monoid[T], e: T => Ordered[T]) = new BeCloseTo(n, delta) } class HaveVerbMatcher[T] extends OkWordMatcher[T] { def apply(v: =>T) = (true, "", "") def \\(node: Node) = XmlBaseMatchers.\\(node) def \\(label: String) = XmlBaseMatchers.\\(label) def \\(node: Node, attributes: List[String]) = XmlBaseMatchers.\\(node, attributes) def \\(label: String, attributes: List[String]) = XmlBaseMatchers.\\(label, attributes) def \\(node: Node, attributeValues: Map[String, String]) = XmlBaseMatchers.\\(node, attributeValues) def \\(label: String, attributeValues: Map[String, String]) = XmlBaseMatchers.\\(label, attributeValues) def \\(node: Node, attributeValues: Pair[String, String]*) = XmlBaseMatchers.\\(node, attributeValues:_*) def \\(label: String, attributeValues: Pair[String, String]*) = XmlBaseMatchers.\\(label, attributeValues:_*) def \(node: Node) = XmlBaseMatchers.\(node) def \(label: String) = XmlBaseMatchers.\(label) def \(node: Node, attributes: List[String]) = XmlBaseMatchers.\(node, attributes) def \(label: String, attributes: List[String]) = XmlBaseMatchers.\(label, attributes) def \(node: Node, attributeValues: Map[String, String]) = XmlBaseMatchers.\(node, attributeValues) def \(label: String, attributeValues: Map[String, String]) = XmlBaseMatchers.\(label, attributeValues) def \(node: Node, attributeValues: Pair[String, String]*) = XmlBaseMatchers.\(node, attributeValues:_*) def \(label: String, attributeValues: Pair[String, String]*) = XmlBaseMatchers.\(label, attributeValues:_*) } class ArticleMatcher[T] extends OkWordMatcher[T] { def apply(v: =>T) = (true, "", "") }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy