org.specs2.matcher.MatchersImplicits.scala Maven / Gradle / Ivy
package org.specs2
package matcher
import execute._
import org.specs2.internal.scalaz._, Scalaz._
import Foldable._
import Generator._
import text.Quote._
import text.Plural._
import MatchResultMessages._
import Result.ResultFailureMonoid
import scala.collection.{GenTraversable, GenTraversableOnce}
import ResultLogicalCombinators._
/**
* This trait provides implicit definitions from MatchResults and Booleans to Results.
*
* It also allows to:
*
* - create matchers from functions
* - create matchers for seqs and sets from single matchers
*/
trait MatchersImplicits extends Expectations with MatchResultCombinators with MatchResultImplicits { outer =>
/**
* Add functionalities to functions returning matchers so that they can be combined before taking a value and
* returning actual matchers
*/
implicit def matcherFunction[S, T](f: S => Matcher[T]) = new MatcherFunction(f)
implicit def matcherFunction2[T](f: T => Matcher[T]) = new MatcherFunction2(f)
class MatcherFunction[S, T](f: S => Matcher[T]) {
/**
* @return a function which will return a matcher checking a sequence of objects
*/
def toSeq = new Function1[Seq[S], Matcher[Seq[T]]] {
def apply(s: Seq[S]) = new SeqMatcher(s, f)
}
/**
* @return a function which will return a matcher checking a set of objects
*/
def toSet = new Function1[Set[S], Matcher[Set[T]]] {
def apply(s: Set[S]) = new SetMatcher(s, f)
}
}
class MatcherFunction2[T](f: T => Matcher[T]) {
/**
* @return a function which will return the composition of a matcher and a function
*/
def ^^^[A](g: A => T) = (a: A) =>
new Matcher[A] {
def apply[B <: A](b: Expectable[B]) = {
val r = f(g(a)).apply(b.map(g))
result(r, b)
}
}
/**
* check that the function is valid for all value, stopping after the first failure
*/
def forall(values: Seq[T]): MatchResult[Seq[T]] = verifyFunction((t: T) => f(t).apply(Expectable(t))).forall(values)
/**
* check that the function is valid for each value, showing all the failures
*/
def foreach(values: Seq[T]): MatchResult[Seq[T]] = verifyFunction((t: T) => f(t).apply(Expectable(t))).foreach(values)
/**
* check that the function is valid at least once
*/
def atLeastOnce(values: Seq[T]): MatchResult[Seq[T]] = verifyFunction((t: T) => f(t).apply(Expectable(t))).atLeastOnce(values)
}
/**
* this implicit provides an inverted syntax to adapt matchers to make the adaptation more readable in some cases:
* - def haveExtension(extension: =>String) = ((_:File).getPath) ^^ endWith(extension)
*/
implicit def adapterFunction[T, S](f: T => S) = new AdapterFunction(f)
case class AdapterFunction[T, S](f: T => S) {
def ^^(m: Matcher[S]): Matcher[T] = m ^^ f
}
/**
* The SeqMatcher
class is a matcher matching a sequence of objects with a matcher returned by a function.
* Usage:List(1, 2, 3) must ((beEqualTo(_:Int)).toSeq)(List(1, 2, 3))
*/
class SeqMatcher[S, T](s: Seq[S], f: S => Matcher[T]) extends Matcher[Seq[T]] {
def apply[U <: Seq[T]](t: Expectable[U]) = {
val bothSequences = t.value.toList zip s.toList
val results = bothSequences.map { case (t1, s1) => f(s1).apply(createExpectable(t1)) }
if (s.size != t.value.size)
result(false,
"the seqs contain the same number of elements",
t.description + " contains " + t.value.size + " elements while " + q(s) + " contains " + s.size + " elements",
t)
else
result(FoldrGenerator[List].reduce(MatchResultMessageReducer[T], results), t)
}
}
/**
* The SetMatcher
class is a matcher matching a set of objects with a matcher returned by a function.
* Usage:List(1, 2, 3) must ((beEqualTo(_:Int)).toSet)(List(2, 1, 3))
*/
class SetMatcher[S, T](s: Set[S], f: S => Matcher[T]) extends Matcher[Set[T]] {
def apply[U <: Set[T]](t: Expectable[U]) = {
val setToTest = t
if (s.size != setToTest.value.size)
result(false,
"the sets contain the same number of elements",
setToTest.description + " contains " + setToTest.value.size + " elements while " + q(s) + " contains " + s.size + " elements",
setToTest)
else {
val results = setToTest.value.map { (element: T) =>
s.find { (otherElement:S) => f(otherElement).apply(createExpectable(element)).isSuccess } match {
case None => result(false, "all matches", "no match for element " + q(element), setToTest)
case Some(x) => result(true, q(element) + " matches with " + x, "no match for element " + q(element), setToTest)
}
}
result(FoldrGenerator[Set].reduce(MatchResultMessageReducer[U], results), setToTest)
}
}
}
/** verify the function f for all the values, stopping after the first failure */
def forall[T, U](values: GenTraversableOnce[T])(f: T => MatchResult[U]) = verifyFunction(f).forall(values.seq.toSeq)
/** verify the function f for all the values, stopping after the first failure, where the PartialFunction is defined */
def forallWhen[T, U](values: GenTraversable[T])(f: PartialFunction[T, MatchResult[U]]) = forall(values.filter(f.isDefinedAt))(f)
/** verify the function f for all the values, and collect all failures */
def foreach[T, U](values: GenTraversableOnce[T])(f: T => MatchResult[U]) = verifyFunction(f).foreach(values.seq.toSeq)
/** verify the function f for all the values, and collect all failures, where the PartialFunction is defined */
def foreachWhen[T, U](values: GenTraversable[T])(f: PartialFunction[T, MatchResult[U]]) = foreach(values.filter(f.isDefinedAt))(f)
/** verify the function f for at least one value */
def atLeastOnce[T, U](values: GenTraversableOnce[T])(f: T => MatchResult[U]) = verifyFunction(f).atLeastOnce(values.seq.toSeq)
/** verify the function f for at least one value, where the PartialFunction is defined */
def atLeastOnceWhen[T, U](values: GenTraversable[T])(f: PartialFunction[T, MatchResult[U]]) = atLeastOnce(values.filter(f.isDefinedAt))(f)
/**
* This method transform a function to a Matcher
*/
implicit def functionToMatcher[T](f: (T => Boolean, String)): Matcher[T] =
functionAndMessagesToMatcher[T]((f._1, (t:T) => "not ("+q(t)+" "+f._2+")", (t:T) => q(t)+" "+f._2))
/**
* This method transform a function to a Matcher
*/
implicit def functionToMatcher2[T](f: (T => Boolean, String, String)): Matcher[T] =
functionAndMessagesToMatcher[T]((f._1, (t:T) => q(t)+" "+f._2, (t:T) => q(t)+" "+f._3))
/**
* This method transform a function to a Matcher
*/
implicit def functionAndKoMessageToMatcher[T](f: (T => Boolean, T => String)): Matcher[T] =
functionAndMessagesToMatcher[T]((f._1, (t:T) => "not ("+f._2(t)+")", f._2))
/**
* This method transform a function, with function descriptors to a Matcher
*/
implicit def functionAndMessagesToMatcher[T](f: (T => Boolean, T => String, T => String)): Matcher[T] = new Matcher[T] {
def apply[S <: T](s: Expectable[S]) = {
val functionResult = f._1(s.value)
result(functionResult, f._2(s.value), f._3(s.value), s)
}
}
/**
* This method transform a function returning a pair (Boolean, String for ko message) to a Matcher
*/
implicit def pairFunctionToMatcher[T](f: T =>(Boolean, String)): Matcher[T] = new Matcher[T] {
def apply[S <: T](s: Expectable[S]) = {
val functionResult = f(s.value)
result(functionResult._1, "not ("+functionResult._2+")", functionResult._2, s)
}
}
/**
* This method transform a function returning a triplet (Boolean, String for ok message, String for ko message) to a Matcher
*/
implicit def tripletFunctionToMatcher[T](f: T =>(Boolean, String, String)): Matcher[T] = new Matcher[T] {
def apply[S <: T](s: Expectable[S]) = {
val functionResult = f(s.value)
result(functionResult._1, functionResult._2, functionResult._3, s)
}
}
/**
* This method transform a function returning a MatchResult to a Matcher
*/
implicit def matchResultFunctionToMatcher[T](f: T => MatchResult[_]): Matcher[T] = new Matcher[T] {
def apply[S <: T](s: Expectable[S]) = {
val functionResult = ResultExecution.execute(f(s.value).toResult)
result(functionResult.isSuccess, functionResult.message, functionResult.message, s)
}
}
implicit def verifyFunction[U, T](t: U => MatchResult[T]) = new MatchResultFunctionVerification(t)
class MatchResultFunctionVerification[U, T](function: U => MatchResult[T]) {
def forall[S <: Traversable[U]](seq: S) = {
val expectable = createExpectable(seq)
val result =
if (seq.isEmpty)
Matcher.result(true, "ok", "ko", expectable)
else {
val (r, lastValueTried) = seq.drop(1).foldLeft(executeFunctionAndReturnValue(seq.head)) { (res, cur) =>
if (res._1.isSuccess) executeFunctionAndReturnValue(cur)
else res
}
lazy val failingElementIndex = if (r.isSuccess) -1 else seq.toSeq.indexOf(lastValueTried)
lazy val failingElementMessage =
if (failingElementIndex >= 0)
"In the sequence "+q(seq.mkString(", "))+", the "+(failingElementIndex+1).th+" element is failing: "+r.message
else
r.message
makeSeqResult(r, "All elements of "+q(seq.mkString(", "))+" are matching ok", failingElementMessage, expectable)
}
checkFailure(result)
}
def foreach[S <: Traversable[U]](seq: S) = {
val expectable = createExpectable(seq)
val result =
if (seq.isEmpty)
Matcher.result(true, "ok", "ko", expectable)
else {
val r = seq.toSeq.foldMap(executeFunction(_))
makeSeqResult(r, "All elements of "+q(seq.mkString(", "))+" are successful", r.message, expectable)
}
checkFailure(result)
}
def atLeastOnce[S <: Traversable[U]](seq: S) = {
val expectable = createExpectable(seq)
val result =
if (seq.isEmpty)
Matcher.result(false, "ok", "ko", expectable)
else {
val (r, lastTriedValue) = seq.drop(1).foldLeft(executeFunctionAndReturnValue(seq.head)) { (res, cur) =>
if (res._1.isSuccess) res
else executeFunctionAndReturnValue(cur)
}
makeSeqResult(r, "In the sequence "+q(seq.mkString(", "))+
", the "+(seq.toSeq.indexOf(lastTriedValue)+1).th+" element is matching: "+r.message,
"No element of "+q(seq.mkString(", "))+" is matching ok",
expectable)
}
checkFailure(result)
}
private def executeFunctionAndReturnValue(value: U): (Result, U) = (executeFunction(value), value)
private def executeFunction(value: U): Result = ResultExecution.execute(function(value).toResult)
private def makeSeqResult[T](r: Result, okMessage: String, koMessage: String, expectable: Expectable[T]): MatchResult[T] =
if (r.isSkipped) MatchSkip(r.message, expectable)
else if (r.isPending) MatchPending(r.message, expectable)
else Matcher.result(r.isSuccess, okMessage, koMessage, expectable)
}
}
private[specs2]
object MatchersImplicits extends MatchersImplicits
/**
* Implicit conversions for MatchResults
*/
private[specs2]
trait MatchResultImplicits { outer =>
/**
* implicit definition to transform a Seq of MatchResults to a Result
*/
implicit def seqToResult[T](r: Seq[MatchResult[T]]): Result = r.foldLeft(StandardResults.success: Result)(_ and _.toResult)
/**
* implicit definition to transform any MatchResult to a Result
*/
implicit def asResult[T, M[_] <: MatchResult[_]](r: M[T]): Result = r.toResult
/**
* implicit definition to accept any MatchResult as a Boolean value.
* It is true if the MatchResult is not an Error or a Failure
*/
implicit def fromMatchResult(r: =>MatchResult[_]): Boolean = r.isSuccess || r.toResult.isSkipped || r.toResult.isPending
/** implicit typeclass instance to create examples from MatchResults */
implicit def matchResultAsResult[T, M[_] <: MatchResult[_]]: AsResult[M[T]] = new AsResult[M[T]] {
def asResult(t: =>M[T]): Result = outer.asResult(t)
}
/** 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)
}
}