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

org.scalatest.enablers.Aggregating.scala Maven / Gradle / Ivy

/*
 * Copyright 2001-2013 Artima, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.scalatest.enablers

import scala.collection.JavaConverters._
import org.scalactic.{Equality, Every}
import scala.collection.GenTraversable
import org.scalatest.FailureMessages
import org.scalatest.verbs.ArrayWrapper
import org.scalactic.ColCompatHelper.aggregate
import scala.annotation.tailrec

/**
 * Typeclass that enables for aggregations certain contain syntax in the ScalaTest matchers DSL.
 *
 * 

* An Aggregating[A] provides access to the "aggregating nature" of type A in such * a way that relevant contain matcher syntax can be used with type A. An A * can be any type of aggregation—an object that in some way aggregates or brings together other objects. ScalaTest provides * implicit implementations for several types out of the box in the * Aggregating companion object: *

* *
    *
  • scala.collection.GenTraversable
  • *
  • String
  • *
  • Array
  • *
  • java.util.Collection
  • *
  • java.util.Map
  • *
* *

* The contain syntax enabled by this trait is: *

* *

    *
  • result should contain atLeastOneOf (1, 2, 3)
  • *
  • result should contain atMostOneOf (1, 2, 3)
  • *
  • result should contain only (1, 2, 3)
  • *
  • result should contain allOf (1, 2, 3)
  • *
  • result should contain theSameElementsAs (List(1, 2, 3))
  • *
* *

* You can enable the contain matcher syntax enabled by Aggregating on your own * type U by defining an Aggregating[U] for the type and making it available implicitly. *

* *

* Note, for an explanation of the difference between Containing and Aggregating, both of which * enable contain matcher syntax, see the Containing * versus Aggregating section of the main documentation for trait Containing. *

*/ trait Aggregating[-A] { // TODO: Write tests that a NotAllowedException is thrown when no elements are passed, maybe if only one element is passed, and // likely if an object is repeated in the list. /** * Implements contain atLeastOneOf syntax for aggregations of type A. * * @param aggregation an aggregation about which an assertion is being made * @param eles elements at least one of which should be contained in the passed aggregation * @return true if the passed aggregation contains at least one of the passed elements */ def containsAtLeastOneOf(aggregation: A, eles: scala.collection.Seq[Any]): Boolean /** * Implements contain theSameElementsAs syntax for aggregations of type A. * * @param leftAggregation an aggregation about which an assertion is being made * @param rightAggregation an aggregation that should contain the same elements as the passed leftAggregation * @return true if the passed leftAggregation contains the same elements as the passed rightAggregation */ def containsTheSameElementsAs(leftAggregation: A, rightAggregation: GenTraversable[Any]): Boolean /** * Implements contain only syntax for aggregations of type A. * * @param aggregation an aggregation about which an assertion is being made * @param eles the only elements that should be contained in the passed aggregation * @return true if the passed aggregation contains only the passed elements */ def containsOnly(aggregation: A, eles: scala.collection.Seq[Any]): Boolean /** * Implements contain allOf syntax for aggregations of type A. * * @param aggregation an aggregation about which an assertion is being made * @param eles elements all of which should be contained in the passed aggregation * @return true if the passed aggregation contains all of the passed elements */ def containsAllOf(aggregation: A, eles: scala.collection.Seq[Any]): Boolean /** * Implements contain atMostOneOf syntax for aggregations of type A. * * @param aggregation an aggregation about which an assertion is being made * @param eles elements at most one of which should be contained in the passed aggregation * @return true if the passed aggregation contains at most one of the passed elements */ def containsAtMostOneOf(aggregation: A, eles: scala.collection.Seq[Any]): Boolean } trait AggregatingImpls { // TODO: Throwing exceptions is slow. Just do a pattern match and test the type before trying to cast it. private[scalatest] def tryEquality[T](left: Any, right: Any, equality: Equality[T]): Boolean = try equality.areEqual(left.asInstanceOf[T], right) catch { case cce: ClassCastException => false } private[scalatest] def checkTheSameElementsAs[T](left: GenTraversable[T], right: GenTraversable[Any], equality: Equality[T]): Boolean = { case class ElementCount(element: Any, leftCount: Int, rightCount: Int) object ZipNoMatch def leftNewCount(next: Any, count: IndexedSeq[ElementCount]): IndexedSeq[ElementCount] = { val idx = count.indexWhere(ec => tryEquality(next, ec.element, equality)) if (idx >= 0) { val currentElementCount = count(idx) count.updated(idx, ElementCount(currentElementCount.element, currentElementCount.leftCount + 1, currentElementCount.rightCount)) } else count :+ ElementCount(next, 1, 0) } def rightNewCount(next: Any, count: IndexedSeq[ElementCount]): IndexedSeq[ElementCount] = { val idx = count.indexWhere(ec => tryEquality(next, ec.element, equality)) if (idx >= 0) { val currentElementCount = count(idx) count.updated(idx, ElementCount(currentElementCount.element, currentElementCount.leftCount, currentElementCount.rightCount + 1)) } else count :+ ElementCount(next, 0, 1) } val counts = aggregate(right.toIterable.zipAll(left.toIterable, ZipNoMatch, ZipNoMatch), IndexedSeq.empty[ElementCount])( { case (count, (nextLeft, nextRight)) => if (nextLeft == ZipNoMatch || nextRight == ZipNoMatch) return false // size not match, can fail early rightNewCount(nextRight, leftNewCount(nextLeft, count)) }, { case (count1, count2) => count2.foldLeft(count1) { case (count, next) => val idx = count.indexWhere(ec => tryEquality(next.element, ec.element, equality)) if (idx >= 0) { val currentElementCount = count(idx) count.updated(idx, ElementCount(currentElementCount.element, currentElementCount.leftCount + next.leftCount, currentElementCount.rightCount + next.rightCount)) } else count :+ next } } ) !counts.exists(e => e.leftCount != e.rightCount) } private[scalatest] def checkOnly[T](left: GenTraversable[T], right: GenTraversable[Any], equality: Equality[T]): Boolean = left.forall(l => right.find(r => tryEquality(l, r, equality)).isDefined) && right.forall(r => left.find(l => tryEquality(l, r, equality)).isDefined) private[scalatest] def checkAllOf[T](left: GenTraversable[T], right: GenTraversable[Any], equality: Equality[T]): Boolean = { @tailrec def checkEqual(left: GenTraversable[T], rightItr: Iterator[Any]): Boolean = { if (rightItr.hasNext) { val nextRight = rightItr.next if (left.exists(t => equality.areEqual(t, nextRight))) checkEqual(left, rightItr) else false // Element not found, let's fail early } else // No more element in right, left contains all of right. true } checkEqual(left, right.toIterator) } private[scalatest] def checkAtMostOneOf[T](left: GenTraversable[T], right: GenTraversable[Any], equality: Equality[T]): Boolean = { def countElements: Int = aggregate(right, 0)( { case (count, nextRight) => if (left.exists(l => equality.areEqual(l, nextRight))) { val newCount = count + 1 if (newCount > 1) return newCount else newCount } else count }, { case (count1, count2) => count1 + count2 } ) val count = countElements count <= 1 } } trait AggregatingJavaImplicits extends AggregatingImpls { /** * Implicit to support Aggregating nature of java.util.Collection. * * @param equality Equality type class that is used to check equality of element in the java.util.Collection * @tparam E the type of the element in the java.util.Collection * @tparam JCOL any subtype of java.util.Collection * @return Aggregating[JCOL[E]] that supports java.util.Collection in relevant contain syntax */ implicit def aggregatingNatureOfJavaCollection[E, JCOL[e] <: java.util.Collection[e]](implicit equality: Equality[E]): Aggregating[JCOL[E]] = new Aggregating[JCOL[E]] { def containsAtLeastOneOf(col: JCOL[E], elements: scala.collection.Seq[Any]): Boolean = { col.asScala.exists((e: E) => elements.exists((ele: Any) => equality.areEqual(e, ele))) } def containsTheSameElementsAs(col: JCOL[E], elements: GenTraversable[Any]): Boolean = { checkTheSameElementsAs(col.asScala, elements, equality) } def containsOnly(col: JCOL[E], elements: scala.collection.Seq[Any]): Boolean = { checkOnly(col.asScala, elements, equality) } def containsAllOf(col: JCOL[E], elements: scala.collection.Seq[Any]): Boolean = { checkAllOf(col.asScala, elements, equality) } def containsAtMostOneOf(col: JCOL[E], elements: scala.collection.Seq[Any]): Boolean = { checkAtMostOneOf(col.asScala, elements, equality) } } /** * Implicit conversion that converts an Equality of type E * into Aggregating of type JCOL[E], where JCOL is a subtype of java.util.Collection. * This is required to support the explicit Equality syntax, for example: * *
    * val javaList = new java.util.ArrayList[String]()
    * javaList.add("hi")
    * (javaList should contain ("HI")) (after being lowerCased)
    * 
* * (after being lowerCased) will returns an Equality[String] * and this implicit conversion will convert it into Aggregating[java.util.ArrayList[String]]. * * @param equality Equality of type E * @tparam E type of elements in the java.util.Collection * @tparam JCOL subtype of java.util.Collection * @return Aggregating of type JCOL[E] */ implicit def convertEqualityToJavaCollectionAggregating[E, JCOL[e] <: java.util.Collection[e]](equality: Equality[E]): Aggregating[JCOL[E]] = aggregatingNatureOfJavaCollection(equality) /** * Implicit to support Aggregating nature of java.util.Map. * * @param equality Equality type class that is used to check equality of entry in the java.util.Map * @tparam K the type of the key in the java.util.Map * @tparam V the type of the value in the java.util.Map * @tparam JMAP any subtype of java.util.Map * @return Aggregating[JMAP[K, V]] that supports java.util.Map in relevant contain syntax */ implicit def aggregatingNatureOfJavaMap[K, V, JMAP[k, v] <: java.util.Map[k, v]](implicit equality: Equality[java.util.Map.Entry[K, V]]): Aggregating[JMAP[K, V]] = new Aggregating[JMAP[K, V]] { import scala.collection.JavaConverters._ def containsAtLeastOneOf(map: JMAP[K, V], elements: scala.collection.Seq[Any]): Boolean = { map.entrySet.asScala.exists((e: java.util.Map.Entry[K, V]) => elements.exists((ele: Any) => equality.areEqual(e, ele))) } def containsTheSameElementsAs(map: JMAP[K, V], elements: GenTraversable[Any]): Boolean = { checkTheSameElementsAs(map.entrySet.asScala, elements, equality) } def containsOnly(map: JMAP[K, V], elements: scala.collection.Seq[Any]): Boolean = { checkOnly(map.entrySet.asScala, elements, equality) } def containsAllOf(map: JMAP[K, V], elements: scala.collection.Seq[Any]): Boolean = { checkAllOf(map.entrySet.asScala, elements, equality) } def containsAtMostOneOf(map: JMAP[K, V], elements: scala.collection.Seq[Any]): Boolean = { checkAtMostOneOf(map.entrySet.asScala, elements, equality) } } /** * Implicit conversion that converts an Equality of type java.util.Map.Entry[K, V] * into Aggregating of type JMAP[K, V], where JMAP is a subtype of java.util.Map. * This is required to support the explicit Equality syntax, for example: * *
    * val javaMap = new java.util.HashMap[Int, String]()
    * javaMap.put(1, "one")
    * // lowerCased needs to be implemented as Normalization[java.util.Map.Entry[K, V]]
    * (javaMap should contain (Entry(1, "ONE"))) (after being lowerCased)
    * 
* * (after being lowerCased) will returns an java.util.Map.Entry[Int, String] * and this implicit conversion will convert it into Aggregating[java.util.HashMap[Int, String]]. * * @param equality Equality of type java.util.Map.Entry[K, V] * @tparam K the type of the key in the java.util.Map * @tparam V the type of the value in the java.util.Map * @tparam JMAP any subtype of java.util.Map * @return Aggregating of type JMAP[K, V] */ implicit def convertEqualityToJavaMapAggregating[K, V, JMAP[k, v] <: java.util.Map[k, v]](equality: Equality[java.util.Map.Entry[K, V]]): Aggregating[JMAP[K, V]] = aggregatingNatureOfJavaMap(equality) } trait AggregatingStandardImplicits extends AggregatingJavaImplicits { import scala.language.higherKinds import scala.language.implicitConversions /** * Implicit to support Aggregating nature of Array. * * @param equality Equality type class that is used to check equality of element in the Array * @tparam E the type of the element in the Array * @return Aggregating[Array[E]] that supports Array in relevant contain syntax */ implicit def aggregatingNatureOfArray[E](implicit equality: Equality[E]): Aggregating[Array[E]] = new Aggregating[Array[E]] { def containsAtLeastOneOf(array: Array[E], elements: scala.collection.Seq[Any]): Boolean = { new ArrayWrapper(array).exists((e: E) => elements.exists((ele: Any) => equality.areEqual(e, ele))) } def containsTheSameElementsAs(array: Array[E], elements: GenTraversable[Any]): Boolean = { checkTheSameElementsAs[E](new ArrayWrapper(array), elements, equality) } def containsOnly(array: Array[E], elements: scala.collection.Seq[Any]): Boolean = { checkOnly(new ArrayWrapper(array), elements, equality) } def containsAllOf(array: Array[E], elements: scala.collection.Seq[Any]): Boolean = { checkAllOf(new ArrayWrapper(array), elements, equality) } def containsAtMostOneOf(array: Array[E], elements: scala.collection.Seq[Any]): Boolean = { checkAtMostOneOf(new ArrayWrapper(array), elements, equality) } } /** * Implicit conversion that converts an Equality of type E * into Aggregating of type Array[E]. * This is required to support the explicit Equality syntax, for example: * *
    * (Array("hi") should contain ("HI")) (after being lowerCased)
    * 
* * (after being lowerCased) will returns an Equality[String] * and this implicit conversion will convert it into Aggregating[Array[String]]. * * @param equality Equality of type E * @tparam E type of elements in the Array * @return Aggregating of type Array[E] */ implicit def convertEqualityToArrayAggregating[E](equality: Equality[E]): Aggregating[Array[E]] = aggregatingNatureOfArray(equality) /** * Implicit to support Aggregating nature of String. * * @param equality Equality type class that is used to check equality of Char in the String * @return Aggregating[String] that supports String in relevant contain syntax */ implicit def aggregatingNatureOfString(implicit equality: Equality[Char]): Aggregating[String] = new Aggregating[String] { def containsAtLeastOneOf(s: String, elements: scala.collection.Seq[Any]): Boolean = { s.exists((e: Char) => elements.exists((ele: Any) => equality.areEqual(e, ele))) } def containsTheSameElementsAs(s: String, elements: GenTraversable[Any]): Boolean = { checkTheSameElementsAs(s, elements, equality) } def containsOnly(s: String, elements: scala.collection.Seq[Any]): Boolean = { checkOnly(s, elements, equality) } def containsAllOf(s: String, elements: scala.collection.Seq[Any]): Boolean = { checkAllOf(s, elements, equality) } def containsAtMostOneOf(s: String, elements: scala.collection.Seq[Any]): Boolean = { checkAtMostOneOf(s, elements, equality) } } /** * Implicit conversion that converts an Equality of type Char * into Aggregating of type String. * This is required to support the explicit Equality syntax, for example: * *
    * // lowerCased needs to be implemented as Normalization[Char]
    * ("hi hello" should contain ('E')) (after being lowerCased)
    * 
* * (after being lowerCased) will returns an Equality[Char] * and this implicit conversion will convert it into Aggregating[String]. * * @param equality Equality of type Char * @return Aggregating of type String */ implicit def convertEqualityToStringAggregating(equality: Equality[Char]): Aggregating[String] = aggregatingNatureOfString(equality) /** * Implicit to support Aggregating nature of Every. * * @param equality Equality type class that is used to check equality of element in the Every * @tparam E the type of the element in the Every * @return Aggregating[Every[E]] that supports Every in relevant contain syntax */ implicit def aggregatingNatureOfEvery[E](implicit equality: Equality[E]): Aggregating[Every[E]] = new Aggregating[Every[E]] { def containsAtLeastOneOf(every: Every[E], elements: scala.collection.Seq[Any]): Boolean = { every.exists((e: E) => elements.exists((ele: Any) => equality.areEqual(e, ele))) } def containsTheSameElementsAs(every: Every[E], elements: GenTraversable[Any]): Boolean = { checkTheSameElementsAs[E](every, elements, equality) } def containsOnly(every: Every[E], elements: scala.collection.Seq[Any]): Boolean = { checkOnly(every, elements, equality) } def containsAllOf(every: Every[E], elements: scala.collection.Seq[Any]): Boolean = { checkAllOf(every, elements, equality) } def containsAtMostOneOf(every: Every[E], elements: scala.collection.Seq[Any]): Boolean = { checkAtMostOneOf(every, elements, equality) } } /** * Implicit conversion that converts an Equality of type E * into Aggregating of type Every[E]. * This is required to support the explicit Equality syntax, for example: * *
    * (Every("hi") should contain ("HI")) (after being lowerCased)
    * 
* * (after being lowerCased) will returns an Equality[String] * and this implicit conversion will convert it into Aggregating[Every[String]]. * * @param equality Equality of type E * @tparam E type of elements in the Every * @return Aggregating of type Every[E] */ implicit def convertEqualityToEveryAggregating[E](equality: Equality[E]): Aggregating[Every[E]] = aggregatingNatureOfEvery(equality) /** * Implicit to support Aggregating nature of GenTraversable. * * @param equality Equality type class that is used to check equality of element in the GenTraversable * @tparam E the type of the element in the GenTraversable * @tparam TRAV any subtype of GenTraversable * @return Aggregating[TRAV[E]] that supports GenTraversable in relevant contain syntax */ implicit def aggregatingNatureOfGenTraversable[E, TRAV[e] <: scala.collection.GenTraversable[e]](implicit equality: Equality[E]): Aggregating[TRAV[E]] = new Aggregating[TRAV[E]] { def containsAtLeastOneOf(trav: TRAV[E], elements: scala.collection.Seq[Any]): Boolean = { trav.exists((e: E) => elements.exists((ele: Any) => equality.areEqual(e, ele))) } def containsTheSameElementsAs(trav: TRAV[E], elements: GenTraversable[Any]): Boolean = { checkTheSameElementsAs[E](trav, elements, equality) } def containsOnly(trav: TRAV[E], elements: scala.collection.Seq[Any]): Boolean = { checkOnly[E](trav, elements, equality) } def containsAllOf(trav: TRAV[E], elements: scala.collection.Seq[Any]): Boolean = { checkAllOf(trav, elements, equality) } def containsAtMostOneOf(trav: TRAV[E], elements: scala.collection.Seq[Any]): Boolean = { checkAtMostOneOf(trav, elements, equality) } } /** * Implicit conversion that converts an Equality of type E * into Aggregating of type TRAV[E], where TRAV is a subtype of GenTraversable. * This is required to support the explicit Equality syntax, for example: * *
    * (List("hi") should contain ("HI")) (after being lowerCased)
    * 
* * (after being lowerCased) will returns an Equality[String] * and this implicit conversion will convert it into Aggregating[List[String]]. * * @param equality Equality of type E * @tparam E type of elements in the GenTraversable * @tparam TRAV subtype of GenTraversable * @return Aggregating of type TRAV[E] */ implicit def convertEqualityToGenTraversableAggregating[E, TRAV[e] <: scala.collection.GenTraversable[e]](equality: Equality[E]): Aggregating[TRAV[E]] = aggregatingNatureOfGenTraversable(equality) } trait AggregatingHighPriorityImplicits extends AggregatingStandardImplicits { /** * Implicit to support Aggregating nature of scala.collection.GenMap. * * @param equality Equality type class that is used to check equality of entry in the scala.collection.GenMap * @tparam K the type of the key in the scala.collection.GenMap * @tparam V the type of the value in the scala.collection.GenMap * @tparam MAP any subtype of scala.collection.GenMap * @return Aggregating[MAP[K, V]] that supports scala.collection.GenMap in relevant contain syntax */ implicit def aggregatingNatureOfMap[K, V, MAP[k, v] <: scala.collection.GenMap[k, v]](implicit equality: Equality[(K, V)]): Aggregating[MAP[K, V]] = new Aggregating[MAP[K, V]] { def containsAtLeastOneOf(map: MAP[K, V], elements: scala.collection.Seq[Any]): Boolean = { map.exists((e: (K, V)) => elements.exists((ele: Any) => equality.areEqual(e, ele))) } def containsTheSameElementsAs(map: MAP[K, V], elements: GenTraversable[Any]): Boolean = { checkTheSameElementsAs(map, elements, equality) } def containsOnly(map: MAP[K, V], elements: scala.collection.Seq[Any]): Boolean = { checkOnly(map, elements, equality) } def containsAllOf(map: MAP[K, V], elements: scala.collection.Seq[Any]): Boolean = { checkAllOf(map, elements, equality) } def containsAtMostOneOf(map: MAP[K, V], elements: scala.collection.Seq[Any]): Boolean = { checkAtMostOneOf(map, elements, equality) } } /** * Implicit conversion that converts an Equality of type Tuple2[K, V] * into Aggregating of type MAP[K, V], where MAP is a subtype of scala.collection.GenMap. * This is required to support the explicit Equality syntax, for example: * *
    * val map = Map(1 -> "one")
    * // lowerCased needs to be implemented as Normalization[Tuple2[K, V]]
    * (map should contain ((1, "ONE"))) (after being lowerCased)
    * 
* * (after being lowerCased) will returns an Tuple2[Int, String] * and this implicit conversion will convert it into Aggregating[scala.collection.GenMap[Int, String]]. * * @param equality Equality of type Tuple2[K, V] * @tparam K the type of the key in the scala.collection.GenMap * @tparam V the type of the value in the scala.collection.GenMap * @tparam MAP any subtype of scala.collection.GenMap * @return Aggregating of type MAP[K, V] */ implicit def convertEqualityToMapAggregating[K, V, MAP[k, v] <: scala.collection.GenMap[k, v]](equality: Equality[(K, V)]): Aggregating[scala.collection.GenMap[K, V]] = aggregatingNatureOfMap(equality) } /** * Companion object for Aggregating that provides implicit implementations for the following types: * *
    *
  • scala.collection.GenTraversable
  • *
  • String
  • *
  • Array
  • *
  • java.util.Collection
  • *
  • java.util.Map
  • *
*/ object Aggregating extends AggregatingHighPriorityImplicits




© 2015 - 2024 Weber Informatics LLC | Privacy Policy