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

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

The newest version!
package org.specs2
package matcher

import control._
import data.Sized
import text.Quote._
import text.Plural._
import collection.Iterablex._
import MatchersImplicits._
import scala.collection.{GenSeq, GenTraversableOnce, GenTraversable}
import execute.{ResultExecution, Result}

/**
 * Matchers for traversables
 */
trait TraversableMatchers extends TraversableBaseMatchers with TraversableBeHaveMatchers
object TraversableMatchers extends TraversableMatchers

private[specs2]
trait TraversableBaseMatchers extends LazyParameters { outer =>
  
  trait TraversableMatcher[T] extends Matcher[GenTraversableOnce[T]]
  
  /** 
   * match if an traversable contains (t).
   * This definition looks redundant with the one below but it is necessary to 
   * avoid the implicit argThat method from Mockito to infer an improper matcher
   * @see the HtmlPrinterSpec failing with a NPE if that method is missing 
   */
  def contain[T](t: =>T): ContainMatcher[T] = contain(lazyfy(t))
  /** match if traversable contains (seq2). n is a dummy paramter to allow overloading */
  def containAllOf[T](seq: Seq[T]): ContainMatcher[T] = new ContainMatcher(seq)
  /** match if traversable contains (t1, t2) */
  def contain[T](t: LazyParameter[T]*): ContainMatcher[T] = new ContainMatcher(t.map(_.value))
  /** match if traversable contains one of (t1, t2) */
  def containAnyOf[T](seq: Seq[T]): ContainAnyOfMatcher[T] = new ContainAnyOfMatcher(seq)

  /** match if traversable contains (x matches p) */
  def containPattern[T](t: =>String): ContainLikeMatcher[T] = containLike[T](t, "pattern")
  /** match if traversable contains (x matches .*+t+.*) */
  def containMatch[T](t: =>String): ContainLikeMatcher[T] = containLike[T](".*"+t+".*", "match")

  /** does a containAll comparison in both ways */
  def containTheSameElementsAs[T](seq: Seq[T]): Matcher[Traversable[T]] = new Matcher[Traversable[T]] {

    def apply[S <: Traversable[T]](t: Expectable[S]) = {
      val missing = (seq.toSeq.diff(t.value.toSeq))
      val added   = (t.value.toSeq.diff(seq.toSeq))
      def message(diffs: Seq[_], msg: String) =
        if (diffs.isEmpty) "" else diffs.mkString("\n  "+msg+": ", ", ", "")

      result(missing.isEmpty && added.isEmpty,
             t.value + "\n  contains the same elements as\n"+ seq,
             t.value + message(missing, "is missing") + message(added, "must not contain"),
             t)
    }
  }

  /**
   * Matches if there is one element in the traversable verifying the function parameter: (traversable.exists(function(_))
   */
  def have[T](function: T => Boolean) = new Matcher[GenTraversableOnce[T]]{
    def apply[S <: GenTraversableOnce[T]](traversable: Expectable[S]) = {
      result(traversable.value.exists(function(_)),
             "at least one element verifies the property in " + traversable.description, 
             "no element verifies the property in " + traversable.description,
             traversable)
    }
  }

  /** Matches if there is at least one matching a "like" function */
  def haveOneElementLike[T, S](like: PartialFunction[T, MatchResult[S]]) = HaveOneElementLike(like)
  def oneElementLike[T, S](like: PartialFunction[T, MatchResult[S]]) = haveOneElementLike(like)

  /** Matches if all the elements are matching a "like" function */
  def haveAllElementsLike[T, S](like: PartialFunction[T, MatchResult[S]]) = HaveAllElementsLike(like)
  def allElementsLike[T, S](like: PartialFunction[T, MatchResult[S]]) = haveAllElementsLike(like)

  /**
   * Matches if there l contains the same elements as the Traversable traversable.
* This verification does not consider the order of the elements but checks the traversables recursively */ def haveTheSameElementsAs[T](l: =>Traversable[T], equality: (T, T) => Boolean = (_:T) == (_:T)) = new HaveTheSameElementsAs(l.toSeq, equality) private def containLike[T](pattern: =>String, matchType: String) = new ContainLikeMatcher[T](pattern, matchType) /** match if there is a way to size T */ def haveSize[T : Sized](n: Int) = new SizedMatcher[T](n, "size") /** alias for haveSize */ def size[T : Sized](n: Int) = haveSize[T](n) /** alias for haveSize */ def haveLength[T : Sized](n: Int) = new SizedMatcher[T](n, "length") /** alias for haveSize */ def length[T : Sized](n: Int) = haveLength[T](n) /** @return a matcher checking if the elements are ordered */ def beSorted[T : Ordering] = new OrderingMatcher[T] /** alias for beSorted */ def sorted[T : Ordering] = beSorted[T] /** any scala collection has a size */ implicit def scalaTraversableIsSized[I <: GenTraversable[_]]: Sized[I] = new Sized[I] { def size(t: I) = t.size } /** any scala array has a size */ implicit def scalaArrayIsSized[T]: Sized[Array[T]] = new Sized[Array[T]] { def size(t: Array[T]) = t.length } /** any java collection has a size */ implicit def javaCollectionIsSized[T <: java.util.Collection[_]]: Sized[T] = new Sized[T] { def size(t: T) = t.size() } /** a regular string has a size, without having to be converted to an Traversable */ implicit def stringIsSized: Sized[String] = new Sized[String] { def size(t: String) = t.size } } private[specs2] trait TraversableBeHaveMatchers extends LazyParameters { outer: TraversableMatchers => implicit def traversable[T](s: MatchResult[GenTraversableOnce[T]]) = new TraversableBeHaveMatchers(s) class TraversableBeHaveMatchers[T](s: MatchResult[GenTraversableOnce[T]]) { def contain(t: LazyParameter[T], ts: LazyParameter[T]*) = new ContainMatchResult(s, outer.contain((t +: ts):_*)) def containMatch(t: =>String) = s(outer.containMatch(t)) def containPattern(t: =>String) = s(outer.containPattern(t)) def have(f: T => Boolean) = s(outer.have(f)) def oneElementLike[U](like: PartialFunction[T, MatchResult[U]]) = s(outer.haveOneElementLike(like)) def allElementsLike[U](like: PartialFunction[T, MatchResult[U]]) = s(outer.haveAllElementsLike(like)) } implicit def sized[T : Sized](s: MatchResult[T]) = new HasSize(s) class HasSize[T : Sized](s: MatchResult[T]) { def size(n: Int) : MatchResult[T] = s(outer.haveSize[T](n)) def length(n: Int) : MatchResult[T] = size(n) } implicit def orderedSeqMatchResult[T : Ordering](result: MatchResult[Seq[T]]) = new OrderedSeqMatchResult(result) class OrderedSeqMatchResult[T : Ordering](result: MatchResult[Seq[T]]) { def sorted = result(outer.beSorted[T]) def beSorted = result(outer.beSorted[T]) } } class ContainMatchResult[T] private[specs2](val s: MatchResult[GenTraversableOnce[T]], containMatcher: ContainMatcher[T]) extends AbstractContainMatchResult[T] { outer => val matcher = containMatcher def only = new ContainOnlyMatchResult(s, containMatcher.only) def inOrder = new ContainInOrderMatchResult(s, containMatcher.inOrder) } class ContainOnlyMatchResult[T] private[specs2](val s: MatchResult[GenTraversableOnce[T]], containMatcher: ContainOnlyMatcher[T]) extends AbstractContainMatchResult[T] { outer => val matcher = containMatcher def inOrder = new ContainOnlyInOrderMatchResult(s, containMatcher.inOrder) } class ContainInOrderMatchResult[T] private[specs2](val s: MatchResult[GenTraversableOnce[T]], containMatcher: ContainInOrderMatcher[T]) extends AbstractContainMatchResult[T] { outer => val matcher = containMatcher def only = new ContainOnlyInOrderMatchResult(s, containMatcher.only) } class ContainOnlyInOrderMatchResult[T] private[specs2](val s: MatchResult[GenTraversableOnce[T]], containMatcher: Matcher[GenTraversableOnce[T]]) extends AbstractContainMatchResult[T] { outer => val matcher = containMatcher } trait AbstractContainMatchResult[T] extends MatchResult[GenTraversableOnce[T]] { val matcher: Matcher[GenTraversableOnce[T]] protected val s: MatchResult[GenTraversableOnce[T]] val expectable = s.expectable lazy val matchResult = s(matcher) override def toResult = matchResult.toResult def negate: MatchResult[GenTraversableOnce[T]] = matchResult.negate def apply(matcher: Matcher[GenTraversableOnce[T]]): MatchResult[GenTraversableOnce[T]] = matchResult(matcher) } class ContainMatcher[T](expected: Seq[T], equality: (T, T) => Boolean = (_:T) == (_:T)) extends AbstractContainMatcher(expected, equality) { type M[T] = ContainMatcher[T] def create(seq: =>Seq[T], eq: (T, T) => Boolean) = new ContainMatcher[T](seq, eq) def apply[S <: GenTraversableOnce[T]](actual: Expectable[S]) = { val missing = expected.filterNot(e => actual.value.toList.exists(a => equality(a, e))) result(missing.isEmpty, actual.description + " contains " + q(expected.mkString(", ")), actual.description + " doesn't contain " + q(missing.mkString(", ")), actual) } def inOrder = new ContainInOrderMatcher(expected, equality) def only = new ContainOnlyMatcher(expected, equality) def exactlyOnce = new ContainExactlyOnceMatcher(expected, equality) } import data._ import Traversex._ import Monoidx._ class ContainExactlyOnceMatcher[T](expected: Seq[T], equality: (T, T) => Boolean = (_:T) == (_:T)) extends AbstractContainMatcher(expected, equality) { type M[T] = ContainExactlyOnceMatcher[T] def create(seq: =>Seq[T], eq: (T, T) => Boolean) = new ContainExactlyOnceMatcher[T](seq, eq) def apply[S <: GenTraversableOnce[T]](actual: Expectable[S]) = { val actualValues = actual.value.seq.toSeq.map(e => (e, 1)).reduceMonoid(Map(_)) result(expected.forall(e => actualValues.filter { case (k, v) => equality(k, e) }.values.sum == 1), actual.description + " contains exactly once " + q(expected.mkString(", ")), actual.description + " doesn't contain exactly once " + q(expected.mkString(", ")), actual) } } class ContainAnyOfMatcher[T](expected: Seq[T], equality: (T, T) => Boolean = (_:T) == (_:T)) extends AbstractContainMatcher(expected, equality) { type M[T] = ContainAnyOfMatcher[T] def create(seq: =>Seq[T], eq: (T, T) => Boolean) = new ContainAnyOfMatcher[T](seq, eq) def apply[S <: GenTraversableOnce[T]](actual: Expectable[S]) = { result(actual.value.toList.exists((a: T) => expected.exists((e: T) => equality(a, e))), actual.description + " contains at least one of " + q(expected.mkString(", ")), actual.description + " doesn't contain any of " + q(expected.mkString(", ")), actual) } } class ContainInOrderMatcher[T](expected: Seq[T], equality: (T, T) => Boolean = (_:T) == (_:T)) extends AbstractContainMatcher(expected, equality) { type M[T] = ContainInOrderMatcher[T] def create(seq: =>Seq[T], eq: (T, T) => Boolean) = new ContainInOrderMatcher[T](seq, eq) def apply[S <: GenTraversableOnce[T]](actual: Expectable[S]) = { result(inOrder(actual.value.toList, expected, equality), actual.description + " contains in order " + q(expected.mkString(", ")), actual.description + " doesn't contain in order " + q(expected.mkString(", ")), actual) } private def inOrder[T](l1: Seq[T], l2: Seq[T], equality: (T, T) => Boolean): Boolean = { (l1.toList, l2.toList) match { case (Nil, Nil) => true case (Nil, _) => false case (_, Nil) => true case (a1 :: rest1, a2 :: rest2) => equality(a1, a2) && inOrder(rest1, rest2, equality) || inOrder(rest1, l2, equality) case other => false } } def only: Matcher[GenTraversableOnce[T]] = (this and new ContainOnlyMatcher(expected)) } class ContainOnlyMatcher[T](expected: Seq[T], equality: (T, T) => Boolean = (_:T) == (_:T)) extends AbstractContainMatcher(expected, equality) { type M[T] = ContainOnlyMatcher[T] def create(seq: =>Seq[T], eq: (T, T) => Boolean) = new ContainOnlyMatcher[T](seq, eq) def apply[S <: GenTraversableOnce[T]](traversable: Expectable[S]) = { val actual = traversable.value result(actual.toList.intersect(expected).sameElementsAs(expected, equality) && expected.size == actual.size, traversable.description + " contains only " + q(expected.mkString(", ")), traversable.description + " doesn't contain only " + q(expected.mkString(", ")), traversable) } def inOrder: Matcher[GenTraversableOnce[T]] = (this and new ContainInOrderMatcher(expected)) } class ContainLikeMatcher[T](pattern: =>String, matchType: String) extends Matcher[GenTraversableOnce[T]] { def apply[S <: GenTraversableOnce[T]](traversable: Expectable[S]) = { val a = pattern result(traversable.value.exists(_.toString.matches(a)), traversable.description + " contains "+matchType+ " " + q(a), traversable.description + " doesn't contain "+matchType+ " " + q(a), traversable) } def onlyOnce = new ContainLikeOnlyOnceMatcher[T](pattern, matchType) } class ContainLikeOnlyOnceMatcher[T](pattern: =>String, matchType: String) extends Matcher[GenTraversableOnce[T]] { def apply[S <: GenTraversableOnce[T]](traversable: Expectable[S]) = { val a = pattern val matchNumber = traversable.value.toSeq.filter(_.toString.matches(a)).size val koMessage = if (matchNumber == 0) traversable.description + " doesn't contain "+matchType+ " " + q(a) else traversable.description + " contains "+matchType+ " " + q(a) + " "+ (matchNumber qty "time") result(matchNumber == 1, traversable.description + " contains "+matchType+ " " + q(a) + " only once", koMessage, traversable) } } /** * This matcher checks if a traversable has the same elements as another one (that is, recursively, in any order) */ class HaveTheSameElementsAs[T](l: =>Seq[T], equality: (T, T) => Boolean = (_:T) == (_:T)) extends AbstractContainMatcher(l, equality) { type M[T] = HaveTheSameElementsAs[T] def create(seq: =>Seq[T], eq: (T, T) => Boolean) = new HaveTheSameElementsAs[T](seq, eq) def apply[S <: GenTraversableOnce[T]](traversable: Expectable[S]) = { result(traversable.value.toSeq.sameElementsAs(l.toSeq, equality), traversable.value.toSeq.toDeepString + " has the same elements as " + q(l.toSeq.toDeepString), traversable.value.toSeq.toDeepString + " doesn't have the same elements as " + q(l.toSeq.toDeepString), traversable, l.toSeq.toDeepString, traversable.value.toSeq.toDeepString) } } abstract class AbstractContainMatcher[T](l: =>Seq[T], equality: (T, T) => Boolean = (_:T) == (_:T)) extends Matcher[GenTraversableOnce[T]] { type M[T] <: AbstractContainMatcher[T] def create(seq: =>Seq[T], eq: (T, T) => Boolean): M[T] /** use a specific equality function */ def ^^[S](equality: (T, T) => Boolean) = create(l, equality) /** use a matcher function to define if 2 values are equal. The first value defines a matcher to use with the second one */ def ^^[S](m: T => Matcher[T]) = create(l, (t1: T, t2: T) => m(t1).apply(Expectable(t2)).isSuccess) /** use a specific adaption function before checking for equality */ def ^^^[S](adaptator: T => S) = create(l, (t1: T, t2: T) => adaptator(t1) == adaptator(t2)) } class SizedMatcher[T : Sized](n: Int, sizeWord: String) extends Matcher[T] { def apply[S <: T](traversable: Expectable[S]) = { val s = implicitly[Sized[T]] val valueSize = s.size(traversable.value) result(valueSize == n, traversable.description + " have "+sizeWord+" "+ n, traversable.description + " doesn't have "+sizeWord+" " + n + " but "+sizeWord+" " + valueSize, traversable) } } class OrderingMatcher[T : Ordering] extends Matcher[Seq[T]] { def apply[S <: Seq[T]](traversable: Expectable[S]) = { result(traversable.value == traversable.value.sorted, traversable.description + " is sorted", traversable.description + " is not sorted", traversable) } } case class HaveOneElementLike[T, U](like: PartialFunction[T, MatchResult[U]]) extends Matcher[GenTraversableOnce[T]] { def apply[S <: GenTraversableOnce[T]](traversable: Expectable[S]) = { val results = traversable.value.toSeq.collect { case v if like.isDefinedAt(v) => (v, ResultExecution.execute(like(v).toResult)) } val inTraversable = "in "+traversable.description+"\n" val (successes, failures) = results.partition(_._2.isSuccess) val failureMessages = failures.map { case (t, r) => t+": "+r }.mkString("\n", "\n", "") result(successes.nonEmpty, "some elements are not correct"+failureMessages, inTraversable+"no element is correct"+failureMessages, traversable) } } case class HaveAllElementsLike[T, U](like: PartialFunction[T, MatchResult[U]]) extends Matcher[GenTraversableOnce[T]] { def apply[S <: GenTraversableOnce[T]](traversable: Expectable[S]) = { val results = traversable.value.toSeq.collect { case v if like.isDefinedAt(v) => (v, ResultExecution.execute(like(v).toResult)) } val inTraversable = "in "+traversable.description+"\n" val (successes, failures) = results.partition(_._2.isSuccess) val failureMessages = failures.map { case (t, r) => t+": "+r }.mkString("\n", "\n", "") result(failures.isEmpty, inTraversable+"all elements are correct", inTraversable+"some elements are not correct"+failureMessages, traversable) } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy