org.scalautils.TripleEqualsSupport.scala Maven / Gradle / Ivy
Show all versions of scalatest_2.11.0-RC2 Show documentation
/* * 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.scalautils import TripleEqualsSupport._ /** * Trait that defines abstract methods used to enforce compile-time type constraints for equality comparisons, and defines
B * @return a===
and!==
operators * used by matchers. * ** The abstract methods of this trait are selectively implemented as implicit by subclasses to enable a spectrum of type constraints for the *
* *===
and!==
operators. As an illustration, if in the expression,a === b
, the type ofa
* isA
andb
isB
, the following three levels of compile-time checking can be obtained from *TripleEqualsSupport
subtraits: ** Unchecked -
* *A
andB
can be any two types. This (weakest) constraint level is available from * subtraitsTripleEquals
andLegacyTripleEquals
. ** Conversion checked -
* *A
must be a subtype ofB
, or vice versa, or an implicit conversion must be available that converts *A
toB
, or vice versa. (BothA
andB
can be the same type, because a type is considered a subtype * of itself.) * This (intermediate) constraint level is available from subtraitsConversionCheckedTripleEquals
andConversionCheckedLegacyTripleEquals
. ** Type checked -
* *A
must be a subtype ofB
, or vice versa. * (BothA
andB
can be the same type, because a type is considered a subtype * of itself.) * This (strongest) constraint level is available from subtraitsTypeCheckedTripleEquals
andTypeCheckedLegacyTripleEquals
. ** The difference between the regular and “legacy” variants of each pair of traits is that the
* *===
and!==
operators * provided by the regular variants result inBoolean
, whereas those of the legacy variants result inOption[String]
. For example, were you * to mix inTripleEquals
, the expression1 + 1 === 3
would returnfalse
. Were you to mix inLegacyTripleEquals
, * by contrast, the expression1 + 1 === 3
would returnSome("2 did not equal 3")
. ** The purpose of the legacy variants is to maintain compatibility with * existing code that uses ScalaTest's original
* *===
defined in traitorg.scalatest.Assertions
. This *===
operator returned an *Option[String]
to facilitate better error messages. With the advent of macros in Scala 2.10, it is possible to obtain good error messages by making *assert
a macro. Once ScalaTest no longer supports Scala 2.9, the legacy variants (LegacyTripleEquals
, *ConversionCheckedLegacyTripleEquals
, andTypeCheckedLegacyTripleEquals
) will be deprecated and eventually removed,===
will * return onlyBoolean
, and good error * messages will be obtained via macros. ** This trait defines all methods that need to be defined implicitly by the six subtraits so that if multiple subtraits are used together, the inner-most * subtrait in scope can not only enable the implicits it needs by overriding or hiding those methods (currently-in-scope as regular, non-implicit methods) and making * them implicit, it can also disable any implicits enabled by its sibling subtraits in enclosing scopes. For example, if your test class mixes * in
* *TypeCheckedTripleEquals
, inside your test class the following methods will be implicit: **
* *- *
convertToCheckingEqualizer
- *
typeCheckedConstraint
- *
lowPriorityTypeCheckedConstraint
- *
convertEquivalenceToAToBConstraint
- *
convertEquivalenceToBToAConstraint
* If in the body of a test you want to turn off the type checking, you can import the members * of
* *TripleEquals
in the body of that test. This will not only hide * non-implicit methodsconvertToEqualizer
unconstrainedEquality
ofTypeCheckedTripleEquals
, * replacing those with implicit ones defined inTripleEquals
, it will also hide the three methods made implicit inTypeCheckedTripleEquals
* (and listed above), replacing them by non-implicit ones. ** In short, you should be able to select a primary constraint level via either a mixin or import, then change that in nested scopes * however you want, again either through a mixin or import, without getting any implicit conversion ambiguity. The innermost constraint level in scope * will always be in force. *
* * @author Bill Venners */ trait TripleEqualsSupport { /** * Class used via an implicit conversion to enable any two objects to be compared with *
===
and!==
with aBoolean
result and no enforced type constraint between * two object types. For example: * ** assert(a === b) * assert(c !== d) ** ** You can also check numeric values against another with a tolerance. Here are some examples: *
* ** assert(a === (2.0 +- 0.1)) * assert(c !== (2.0 +- 0.1)) ** * @param leftSide An object to convert toEqualizer
, which represents the value * on the left side of a===
or!==
invocation. * * @author Bill Venners */ class Equalizer[L](val leftSide: L) { // Note: This is called leftSide not left to avoid a conflict with scalaz's implicit that adds left /** * Compare two objects for equality, returning aBoolean
, using theEquality
type class passed asequality
. * * @param rightSide the object to compare for equality withleftSide
, passed to the constructor * @param equality an implicitEquality
type class that defines a way of calculating equality for objects of typeL
* @return true if theleftSide
andrightSide
objects are equal according to the passedEquality
type class. */ def ===(rightSide: Any)(implicit equality: Equality[L]): Boolean = equality.areEqual(leftSide, rightSide) /** * Compare two objects for inequality, returning aBoolean
, using theEquality
type class passed asequality
. * * @param rightSide the object to compare for inequality withleftSide
, passed to the constructor * @param equality an implicitEquality
type class that defines a way of calculating equality for objects of typeL
* @return true if theleftSide
andrightSide
objects are not equal according to the passedEquality
type class. */ def !==(rightSide: Any)(implicit equality: Equality[L]): Boolean = !equality.areEqual(leftSide, rightSide) /** * Determine whether a numeric object is within the passedSpread
, returning aBoolean
. * * @param spread theSpread
against which to compare the value passed to the constructor asleftSide
* @return true if the value passed to the constructor asleftSide
is within theSpread
passed to this method. */ def ===(spread: Spread[L]): Boolean = if (spread != null) spread.isWithin(leftSide) else leftSide == spread /** * Determine whether a numeric object is outside the passedSpread
, returning aBoolean
. * * @param spread theSpread
against which to compare the value passed to the constructor asleftSide
* @return true if the value passed to the constructor asleftSide
is not within theSpread
passed to this method. */ def !==(spread: Spread[L]): Boolean = if (spread != null) !spread.isWithin(leftSide) else leftSide != spread /** * Determine whether an object reference isnull
. * * @param literalNull anull
value against which to compare the value passed to the constructor asleftSide
for equality * @return true if the value passed to the constructor asleftSide
isnull
. */ def ===(literalNull: Null): Boolean = leftSide == null /** * Determines whether an object reference is non-null
. * * @param literalNull anull
value against which to compare the value passed to the constructor asleftSide
for inequality * @return true if the value passed to the constructor asleftSide
is non-null
. */ def !==(literalNull: Null): Boolean = leftSide != null } /** * Class used via an implicit conversion to enable any two objects to be compared with *===
and!==
with anOption[String]
result and no enforced type constraint between * two object types. For example: * ** assert(a === b) * assert(c !== d) ** ** You can also check numeric values against another with a tolerance. Here are some examples: *
* ** assert(a === (2.0 +- 0.1)) * assert(c !== (2.0 +- 0.1)) ** ** The benefit of using
* *assert(a === b)
rather thanassert(a == b)
in ScalaTest code is * that aTestFailedException
produced by the former will include the values ofa
andb
* in its detail message. ** * Note: This class has "Legacy" in its name because its approach to error messages will eventually be replaced by macros. Once ScalaTest no longer supports Scala 2.9, * this class will be deprecated in favor of class
* *Equalizer
. Instead of obtaining nice error messages via theOption[String]
* returned by the methods of this class, the error messages will be obtained by a macro. The "legacy" approach to good error messages will continue to be * used, however, until ScalaTest no longer supports Scala 2.9, since macros were introduced to Scala (in experimental form) in 2.10. * ** The primary constructor takes one object,
* * @param left An object to convert toleft
, whose type is being converted toEqualizer
. Theleft
* value may be anull
reference, because this is allowed by Scala's==
operator. *Equalizer
, which represents theleft
value * of a===
or!==
equality check. * * @author Bill Venners */ class LegacyEqualizer[L](left: L) { private def diffStrings(s: String, t: String): Tuple2[String, String] = { def findCommonPrefixLength(s: String, t: String): Int = { val max = s.length.min(t.length) // the maximum potential size of the prefix var i = 0 var found = false while (i < max & !found) { found = (s.charAt(i) != t.charAt(i)) if (!found) i = i + 1 } i } def findCommonSuffixLength(s: String, t: String): Int = { val max = s.length.min(t.length) // the maximum potential size of the suffix var i = 0 var found = false while (i < max & !found) { found = (s.charAt(s.length - 1 - i) != t.charAt(t.length - 1 - i)) if (!found) i = i + 1 } i } val commonPrefixLength = findCommonPrefixLength(s, t) val commonSuffixLength = findCommonSuffixLength(s.substring(commonPrefixLength), t.substring(commonPrefixLength)) val prefix = s.substring(0, commonPrefixLength) val suffix = if (s.length - commonSuffixLength < 0) "" else s.substring(s.length - commonSuffixLength) val sMiddleEnd = s.length - commonSuffixLength val tMiddleEnd = t.length - commonSuffixLength val sMiddle = s.substring(commonPrefixLength, sMiddleEnd) val tMiddle = t.substring(commonPrefixLength, tMiddleEnd) val MaxContext = 20 val shortPrefix = if (commonPrefixLength > MaxContext) "..." + prefix.substring(prefix.length - MaxContext) else prefix val shortSuffix = if (commonSuffixLength > MaxContext) suffix.substring(0, MaxContext) + "..." else suffix (shortPrefix + "[" + sMiddle + "]" + shortSuffix, shortPrefix + "[" + tMiddle + "]" + shortSuffix) } // If the objects are two strings, replace them with whatever is returned by diffStrings. // Otherwise, use the same objects. private def getObjectsForFailureMessage(a: Any, b: Any) = a match { case aStr: String => { b match { case bStr: String => { diffStrings(aStr, bStr) } case _ => (a, b) } } case _ => (a, b) } /** * Compare two objects for equality, returning anOption[String]
, using theEquality
type class passed asequality
. * * @param right the object to compare for equality withleft
, passed to the constructor * @param equality an implicitEquality
type class that defines a way of calculating equality for objects of typeL
* @return None if theleft
andright
objects are equal according to the passedEquality
type class. * else returns an error message string wrapped in aSome
. */ def ===(right: Any)(implicit equality: Equality[L]): Option[String] = if (equality.areEqual(left, right)) None else { val (leftee, rightee) = getObjectsForFailureMessage(left, right) Some(FailureMessages("didNotEqual", leftee, rightee)) } /** * Compare two objects for inequality, returning anOption[String]
, using theEquality
type class passed asequality
. * * @param right the object to compare for inequality withleft
, passed to the constructor * @param equality an implicitEquality
type class that defines a way of calculating equality for objects of typeL
* @return None if theleft
andright
objects are not equal according to the passedEquality
type class. * else returns an error message string wrapped in aSome
. */ def !==(right: Any)(implicit equality: Equality[L]): Option[String] = if (!equality.areEqual(left, right)) None else { val (leftee, rightee) = getObjectsForFailureMessage(left, right) Some(FailureMessages("equaled", leftee, rightee)) } /** * Determine whether a numeric object is within the passedSpread
, returning anOption[String]
. * * @param spread theSpread
against which to compare the value passed to the constructor asleft
* @return None if the value passed to the constructor asleft
is not within theSpread
passed to this method, * else returns an error message string wrapped in aSome
. */ def ===(spread: Spread[L]): Option[String] = if (spread == null) { if (left == null) None else { val (leftee, rightee) = getObjectsForFailureMessage(left, spread) Some(FailureMessages("didNotEqual", leftee, rightee)) } } else { if (spread.isWithin(left)) None else Some(FailureMessages("wasNotPlusOrMinus", left, spread.pivot, spread.tolerance)) } /** * Determine whether a numeric object is outside the passedSpread
, returning anOption[String]
. * * @param spread theSpread
against which to compare the value passed to the constructor asleft
* @return true if the value passed to the constructor asleft
is not within theSpread
passed to this method. * else returns an error message string wrapped in aSome
. */ def !==(spread: Spread[L]): Option[String] = if (spread == null) { if (left != null) None else { val (leftee, rightee) = getObjectsForFailureMessage(left, spread) Some(FailureMessages("equaled", leftee, rightee)) } } else { if (if (spread != null) !spread.isWithin(left) else left != spread) None else Some(FailureMessages("wasPlusOrMinus", left, spread.pivot, spread.tolerance)) } } /** * Class used via an implicit conversion to enable two objects to be compared with *===
and!==
with aBoolean
result and an enforced type constraint between * two object types. For example: * ** assert(a === b) * assert(c !== d) ** ** You can also check numeric values against another with a tolerance. Here are some examples: *
* ** assert(a === (2.0 +- 0.1)) * assert(c !== (2.0 +- 0.1)) ** * @param leftSide An object to convert toEqualizer
, which represents the value * on the left side of a===
or!==
invocation. * * @author Bill Venners */ class CheckingEqualizer[L](val leftSide: L) { // Note: This is called leftSide not left to avoid a conflict with scalaz's implicit that adds left /** * Compare two objects for equality, returning aBoolean
, using theConstraint
instance passed asconstraint
. * * @param rightSide the object to compare for equality withleftSide
, passed to the constructor * @param constraint an implicitConstraint
instance that enforces a relationship between typesL
andR
and * defines a way of calculating equality for objects of typeL
* @return true if theleftSide
andrightSide
objects are equal according to the passedConstraint
instance. */ def ===[R](rightSide: R)(implicit constraint: Constraint[L, R]): Boolean = constraint.areEqual(leftSide, rightSide) /** * Compare two objects for inequality, returning aBoolean
, using theConstraint
instance passed asconstraint
. * * @param rightSide the object to compare for inequality withleftSide
, passed to the constructor * @param constraint an implicitConstraint
instance that enforces a relationship between typesL
andR
and * defines a way of calculating equality for objects of typeL
* @return true if theleftSide
andrightSide
objects are not equal according to the passedConstraint
instance. */ def !==[R](rightSide: R)(implicit constraint: Constraint[L, R]): Boolean = !constraint.areEqual(leftSide, rightSide) /** * Determine whether a numeric object is within the passedSpread
, returning aBoolean
. * * @param spread theSpread
against which to compare the value passed to the constructor asleftSide
* @return true if the value passed to the constructor asleftSide
is not within theSpread
passed to this method. */ def ===(spread: Spread[L]): Boolean = if (spread != null) spread.isWithin(leftSide) else leftSide == spread /** * Determine whether a numeric object is outside the passedSpread
, returning aBoolean
. * * @param spread theSpread
against which to compare the value passed to the constructor asleftSide
* @return true if the value passed to the constructor asleftSide
is not within theSpread
passed to this method. */ def !==(spread: Spread[L]): Boolean = if (spread != null) !spread.isWithin(leftSide) else leftSide != spread } /** * Class used via an implicit conversion to enable any two objects to be compared with *===
and!==
with anOption[String]
result and an enforced type constraint between * two object types. For example: * ** assert(a === b) * assert(c !== d) ** ** You can also check numeric values against another with a tolerance. Here are some examples: *
* ** assert(a === (2.0 +- 0.1)) * assert(c !== (2.0 +- 0.1)) ** ** The benefit of using
* *assert(a === b)
rather thanassert(a == b)
in ScalaTest code is * that aTestFailedException
produced by the former will include the values ofa
andb
* in its detail message. ** * Note: This class has "Legacy" in its name because its approach to error messages will eventually be replaced by macros. Once ScalaTest no longer supports Scala 2.9, * this class will be deprecated in favor of class
* *Equalizer
. Instead of obtaining nice error messages via theOption[String]
* returned by the methods of this class, the error messages will be obtained by a macro. The "legacy" approach to good error messages will continue to be * used, however, until ScalaTest no longer supports Scala 2.9, since macros were introduced to Scala (in experimental form) in 2.10. * ** The primary constructor takes one object,
* * @param left An object to convert toleft
, whose type is being converted toEqualizer
. Theleft
* value may be anull
reference, because this is allowed by Scala's==
operator. *Equalizer
, which represents theleft
value * of a===
or!==
equality check. * * @author Bill Venners */ class LegacyCheckingEqualizer[L](left: L) { private def diffStrings(s: String, t: String): Tuple2[String, String] = { def findCommonPrefixLength(s: String, t: String): Int = { val max = s.length.min(t.length) // the maximum potential size of the prefix var i = 0 var found = false while (i < max & !found) { found = (s.charAt(i) != t.charAt(i)) if (!found) i = i + 1 } i } def findCommonSuffixLength(s: String, t: String): Int = { val max = s.length.min(t.length) // the maximum potential size of the suffix var i = 0 var found = false while (i < max & !found) { found = (s.charAt(s.length - 1 - i) != t.charAt(t.length - 1 - i)) if (!found) i = i + 1 } i } val commonPrefixLength = findCommonPrefixLength(s, t) val commonSuffixLength = findCommonSuffixLength(s.substring(commonPrefixLength), t.substring(commonPrefixLength)) val prefix = s.substring(0, commonPrefixLength) val suffix = if (s.length - commonSuffixLength < 0) "" else s.substring(s.length - commonSuffixLength) val sMiddleEnd = s.length - commonSuffixLength val tMiddleEnd = t.length - commonSuffixLength val sMiddle = s.substring(commonPrefixLength, sMiddleEnd) val tMiddle = t.substring(commonPrefixLength, tMiddleEnd) val MaxContext = 20 val shortPrefix = if (commonPrefixLength > MaxContext) "..." + prefix.substring(prefix.length - MaxContext) else prefix val shortSuffix = if (commonSuffixLength > MaxContext) suffix.substring(0, MaxContext) + "..." else suffix (shortPrefix + "[" + sMiddle + "]" + shortSuffix, shortPrefix + "[" + tMiddle + "]" + shortSuffix) } // If the objects are two strings, replace them with whatever is returned by diffStrings. // Otherwise, use the same objects. private def getObjectsForFailureMessage(a: Any, b: Any) = a match { case aStr: String => { b match { case bStr: String => { diffStrings(aStr, bStr) } case _ => (a, b) } } case _ => (a, b) } /** * Compare two objects for equality, returning anOption[String]
, using theConstraint
instance passed asconstraint
. * * @param right the object to compare for equality withleft
, passed to the constructor * @param constraint an implicitConstraint
instance that enforces a relationship between typesL
andR
and * defines a way of calculating equality for objects of typeL
* @return None if theleft
andright
objects are equal according to the passedEquality
type class. * else returns an error message string wrapped in aSome
. */ def ===[R](right: R)(implicit constraint: Constraint[L, R]): Option[String] = if (constraint.areEqual(left, right)) None else { val (leftee, rightee) = getObjectsForFailureMessage(left, right) Some(FailureMessages("didNotEqual", leftee, rightee)) } /** * Compare two objects for inequality, returning anOption[String]
, using theConstraint
instance passed asconstraint
. * * @param right the object to compare for inequality withleft
, passed to the constructor * @param constraint an implicitConstraint
instance that enforces a relationship between typesL
andR
and * defines a way of calculating equality for objects of typeL
* @return None if theleft
andright
objects are not equal according to the passedEquality
type class. * else returns an error message string wrapped in aSome
. */ def !==[R](right: R)(implicit constraint: Constraint[L, R]): Option[String] = if (!constraint.areEqual(left, right)) None else { val (leftee, rightee) = getObjectsForFailureMessage(left, right) Some(FailureMessages("equaled", leftee, rightee)) } /** * Determine whether a numeric object is within the passedSpread
, returning anOption[String]
. * * @param spread theSpread
against which to compare the value passed to the constructor asleft
* @return None if the value passed to the constructor asleft
is not within theSpread
passed to this method, * else returns an error message string wrapped in aSome
. */ def ===(spread: Spread[L]): Option[String] = if (spread == null) { if (left == null) None else { val (leftee, rightee) = getObjectsForFailureMessage(left, spread) Some(FailureMessages("equaled", leftee, rightee)) } } else { if (spread.isWithin(left)) None else Some(FailureMessages("wasNotPlusOrMinus", left, spread.pivot, spread.tolerance)) } /** * Determine whether a numeric object is outside the passedSpread
, returning anOption[String]
. * * @param spread theSpread
against which to compare the value passed to the constructor asleft
* @return true if the value passed to the constructor asleft
is not within theSpread
passed to this method. * else returns an error message string wrapped in aSome
. */ def !==(spread: Spread[L]): Option[String] = if (spread == null) { if (left != null) None else { val (leftee, rightee) = getObjectsForFailureMessage(left, spread) Some(FailureMessages("equaled", leftee, rightee)) } } else { if (if (spread != null) !spread.isWithin(left) else left != spread) None else Some(FailureMessages("wasPlusOrMinus", left, spread.pivot, spread.tolerance)) } } /** * Returns anEquality[A]
for any typeA
that determines equality * by first calling.deep
on anyArray
(on either the left or right side), * then comparing the resulting objects with==
. * * @return a defaultEquality
for typeA
*/ def defaultEquality[A]: Equality[A] = Equality.default /** * Converts to anEqualizer
that provides===
and!==
operators that * result inBoolean
and enforce no type constraint. * ** This method is overridden and made implicit by subtrait
* * @param left the object whose type to convert toTripleEquals
and overriden as non-implicit by the other * subtraits in this package. *Equalizer
. * @throws NullPointerException ifleft
isnull
. */ def convertToEqualizer[T](left: T): Equalizer[T] /** * Converts to aLegacyEqualizer
that provides===
and!==
operators that * result inOption[String]
and enforce no type constraint. * ** This method is overridden and made implicit by subtrait
* * @param left the object whose type to convert toLegacyTripleEquals
and overriden as non-implicit * by the other subtraits in this package. *LegacyEqualizer
. * @throws NullPointerException ifleft
isnull
. */ def convertToLegacyEqualizer[T](left: T): LegacyEqualizer[T] /** * Converts to anCheckingEqualizer
that provides===
and!==
operators * that result inBoolean
and enforce a type constraint. * ** This method is overridden and made implicit by subtraits
* * @param left the object whose type to convert toTypeCheckedTripleEquals
and *ConversionCheckedTripleEquals
, and overriden as * non-implicit by the other subtraits in this package. *CheckingEqualizer
. * @throws NullPointerException ifleft
isnull
. */ def convertToCheckingEqualizer[T](left: T): CheckingEqualizer[T] /** * Converts to aLegacyCheckingEqualizer
that provides===
and!==
operators * that result inOption[String]
and enforce a type constraint. * ** This method is overridden and made implicit by subtraits
* * @param left the object whose type to convert toTypeCheckedLegacyTripleEquals
* andConversionCheckedLegacyTripleEquals
, and * overriden as non-implicit by the other subtraits in this package. *LegacyCheckingEqualizer
. * @throws NullPointerException ifleft
isnull
. */ def convertToLegacyCheckingEqualizer[T](left: T): LegacyCheckingEqualizer[T] /** * Provides aConstraint[A, B]
class for any two typesA
andB
, with no type constraint enforced, given an * implicitEquality[A]
. * ** The returned
* *Constraint
'sareEqual
method uses the implicitly passedEquality[A]
's *areEqual
method to determine equality. ** This method is overridden and made implicit by subtraits
* * @param equalityOfA anTripleEquals
and *LegacyTripleEquals
, and * overriden as non-implicit by the other subtraits in this package. *Equality[A]
type class to which theConstraint.areEqual
method will delegate to determine equality. * @return aConstraint[A, B]
whoseareEqual
method delegates to theareEqual
method of * the passedEquality[A]
. */ def unconstrainedEquality[A, B](implicit equalityOfA: Equality[A]): Constraint[A, B] /** * Provides aConstraint[A, B]
for any two typesA
andB
, enforcing the type constraint * thatA
must be a subtype ofB
, given an implicitEquivalence[B]
. * ** The returned
* *Constraint
'sareEqual
method uses the implicitly passedEquivalence[A]
's *areEquivalent
method to determine equality. ** This method is overridden and made implicit by subtraits *
* * @param equivalenceOfB anLowPriorityTypeCheckedConstraint
(extended by *TypeCheckedTripleEquals
), and *LowPriorityTypeCheckedLegacyConstraint
(extended by *TypeCheckedLegacyTripleEquals
), and * overriden as non-implicit by the other subtraits in this package. *Equivalence[B]
type class to which theConstraint.areEqual
method * will delegate to determine equality. * @param ev evidence thatA
is a subype ofConstraint[A, B]
whoseareEqual
method delegates to the *areEquivalent
method of the passedEquivalence[B]
. */ def lowPriorityTypeCheckedConstraint[A, B](implicit equivalenceOfB: Equivalence[B], ev: A <:< B): Constraint[A, B] /** * Provides aConstraint[A, B]
for any two typesA
andB
, enforcing the type constraint * thatA
must be a subtype ofB
, given an explicitEquivalence[B]
. * ** This method is used to enable the
* *Explicitly
DSL for *TypeCheckedTripleEquals
by requiring an explicitEquivalance[B]
, but * taking an implicit function that provides evidence thatA
is a subtype of B. ** The returned
* *Constraint
'sareEqual
method uses the implicitly passedEquivalence[B]
's *areEquivalent
method to determine equality. ** This method is overridden and made implicit by subtraits *
* * @param equivalenceOfB anLowPriorityTypeCheckedConstraint
(extended by *TypeCheckedTripleEquals
), and *LowPriorityTypeCheckedLegacyConstraint
(extended by *TypeCheckedLegacyTripleEquals
), and * overriden as non-implicit by the other subtraits in this package. *Equivalence[B]
type class to which theConstraint.areEqual
method * will delegate to determine equality. * @param ev evidence thatA
is a subype of B * @return aConstraint[A, B]
whoseareEqual
method delegates to the *areEquivalent
method of the passedEquivalence[B]
. */ def convertEquivalenceToAToBConstraint[A, B](equivalenceOfB: Equivalence[B])(implicit ev: A <:< B): Constraint[A, B] /** * Provides aConstraint[A, B]
for any two typesA
andB
, enforcing the type constraint * thatB
must be a subtype ofA
, given an implicitEquivalence[A]
. * ** The returned
* *Constraint
'sareEqual
method uses the implicitly passedEquivalence[A]
's *areEquivalent
method to determine equality. ** This method is overridden and made implicit by subtraits *
* * @param equalityOfA anTypeCheckedTripleEquals
) and *TypeCheckedLegacyTripleEquals
, and * overriden as non-implicit by the other subtraits in this package. *Equivalence[A]
type class to which theConstraint.areEqual
method will delegate to determine equality. * @param ev evidence thatB
is a subype of A * @return aConstraint[A, B]
whoseareEqual
method delegates to theareEquivalent
method of * the passedEquivalence[A]
. */ def typeCheckedConstraint[A, B](implicit equivalenceOfA: Equivalence[A], ev: B <:< A): Constraint[A, B] /** * Provides aConstraint[A, B]
for any two typesA
andB
, enforcing the type constraint * thatB
must be a subtype ofA
, given an explicitEquivalence[A]
. * ** This method is used to enable the
* *Explicitly
DSL for *TypeCheckedTripleEquals
by requiring an explicitEquivalance[B]
, but * taking an implicit function that provides evidence thatA
is a subtype of B. For example, underTypeCheckedTripleEquals
, * this method (as an implicit method), would be used to compile this statement: ** def closeEnoughTo1(num: Double): Boolean = * (num === 1.0)(decided by forgivingEquality) ** ** The returned
* *Constraint
'sareEqual
method uses the implicitly passedEquivalence[A]
's *areEquivalent
method to determine equality. ** This method is overridden and made implicit by subtraits *
* * @param equalityOfA anTypeCheckedTripleEquals
) and *TypeCheckedLegacyTripleEquals
, and * overriden as non-implicit by the other subtraits in this package. *Equivalence[A]
type class to which theConstraint.areEqual
method will delegate to determine equality. * @param ev evidence thatB
is a subype of A * @return aConstraint[A, B]
whoseareEqual
method delegates to theareEquivalent
method of * the passedEquivalence[A]
. */ def convertEquivalenceToBToAConstraint[A, B](equivalenceOfA: Equivalence[A])(implicit ev: B <:< A): Constraint[A, B] /** * Provides aConstraint[A, B]
class for any two typesA
andB
, enforcing the type constraint thatA
is * implicitly convertible toB
, given an implicitEquivalence[B]
. * ** The returned
* *Constraint
'sareEqual
method uses the implicitly passedEquivalence[B]
's *areEquivalent
method to determine equality. ** This method is overridden and made implicit by subtraits *
* * @param equalityOfB anLowPriorityConversionCheckedConstraint
(extended by *ConversionCheckedTripleEquals
), and *LowPriorityConversionCheckedLegacyConstraint
(extended by *ConversionCheckedLegacyTripleEquals
), and * overriden as non-implicit by the other subtraits in this package. *Equivalence[B]
type class to which theConstraint.areEqual
method will delegate to determine equality. * @param cnv an implicit conversion fromA
to B * @return aConstraint[A, B]
whoseareEqual
method delegates to theareEquivalent
method of * the passedEquivalence[B]
. */ def lowPriorityConversionCheckedConstraint[A, B](implicit equivalenceOfB: Equivalence[B], cnv: A => B): Constraint[A, B] /** * Provides aConstraint[A, B]
class for any two typesA
andB
, enforcing the type constraint thatA
is * implicitly convertible toB
, given an explicitEquivalence[B]
. * ** This method is used to enable the
* *Explicitly
DSL for *ConversionCheckedTripleEquals
by requiring an explicitEquivalance[B]
, but * taking an implicit function that converts fromA
to B. ** The returned
* *Constraint
'sareEqual
method uses the implicitly passedEquivalence[B]
's *areEquivalent
method to determine equality. ** This method is overridden and made implicit by subtraits *
* * @param equalityOfB anLowPriorityConversionCheckedConstraint
(extended by *ConversionCheckedTripleEquals
), and *LowPriorityConversionCheckedLegacyConstraint
(extended by *ConversionCheckedLegacyTripleEquals
), and * overriden as non-implicit by the other subtraits in this package. *Equivalence[B]
type class to which theConstraint.areEqual
method will delegate to determine equality. * @param cnv an implicit conversion fromA
to B * @return aConstraint[A, B]
whoseareEqual
method delegates to theareEquivalent
method of * the passedEquivalence[B]
. */ def convertEquivalenceToAToBConversionConstraint[A, B](equivalenceOfB: Equivalence[B])(implicit ev: A => B): Constraint[A, B] /** * Provides aConstraint[A, B]
class for any two typesA
andB
, enforcing the type constraint thatB
is * implicitly convertible toA
, given an implicitEquivalence[A]
. * ** The returned
* *Constraint
'sareEqual
method uses the implicitly passedEquivalence[A]
's *areEquivalent
method to determine equality. ** This method is overridden and made implicit by subtraits *
* * @param equivalenceOfA anConversionCheckedTripleEquals
) and *ConversionCheckedLegacyTripleEquals
, and * overriden as non-implicit by the other subtraits in this package. *Equivalence[A]
type class to which theConstraint.areEqual
method will delegate to determine equality. * @param cnv an implicit conversion fromB
to A * @return aConstraint[A, B]
whoseareEqual
method delegates to theareEquivalent
method of * the passedEquivalence[A]
. */ def conversionCheckedConstraint[A, B](implicit equivalenceOfA: Equivalence[A], cnv: B => A): Constraint[A, B] /** * Provides aConstraint[A, B]
class for any two typesA
andB
, enforcing the type constraint thatB
is * implicitly convertible toA
, given an explicitEquivalence[A]
. * ** This method is used to enable the
* *Explicitly
DSL for *ConversionCheckedTripleEquals
by requiring an explicitEquivalance[A]
, but * taking an implicit function that converts fromB
to A. For example, underConversionCheckedTripleEquals
, * this method (as an implicit method), would be used to compile this statement: ** def closeEnoughTo1(num: Double): Boolean = * (num === 1.0)(decided by forgivingEquality) ** ** The returned
* *Constraint
'sareEqual
method uses the implicitly passedEquivalence[A]
's *areEquivalent
method to determine equality. ** This method is overridden and made implicit by subtraits *
* * @param equivalenceOfA anConversionCheckedTripleEquals
) and *ConversionCheckedLegacyTripleEquals
, and * overriden as non-implicit by the other subtraits in this package. *Equivalence[A]
type class to which theConstraint.areEqual
method will delegate to determine equality. * @param cnv an implicit conversion fromB
to A * @return aConstraint[A, B]
whoseareEqual
method delegates to theareEquivalent
method of * the passedEquivalence[A]
. */ def convertEquivalenceToBToAConversionConstraint[A, B](equivalenceOfA: Equivalence[A])(implicit ev: B => A): Constraint[A, B] /** * Returns aTripleEqualsInvocation[T]
, given an object of typeT
, to facilitate * the “<left> should === <right>
” syntax * ofMatchers
. * * @param right the right-hand side value for an equality assertion * @return aTripleEqualsInvocation
wrapping the passed right value, withexpectingEqual
* set totrue
. */ def ===[T](right: T): TripleEqualsInvocation[T] = new TripleEqualsInvocation[T](right, true) /** * Returns aTripleEqualsInvocation[T]
, given an object of typeT
, to facilitate * the “<left> should !== <right>
” syntax * ofMatchers
. * * @param right the right-hand side value for an equality assertion * @return aTripleEqualsInvocation
wrapping the passed right value, withexpectingEqual
* set tofalse
. */ def !==[T](right: T): TripleEqualsInvocation[T] = new TripleEqualsInvocation[T](right, false) /** * Returns aTripleEqualsInvocation[Null]
, given anull
reference, to facilitate * the “<left> should === null
” syntax * ofMatchers
. * * @param right a null reference * @return aTripleEqualsInvocation
wrapping the passednull
value, withexpectingEqual
* set totrue
. */ def ===(right: Null): TripleEqualsInvocation[Null] = new TripleEqualsInvocation[Null](right, true) /** * Returns aTripleEqualsInvocation[Null]
, given anull
reference, to facilitate * the “<left> should !== null
” syntax * ofMatchers
. * * @param right a null reference * @return aTripleEqualsInvocation
wrapping the passednull
value, withexpectingEqual
* set tofalse
. */ def !==(right: Null): TripleEqualsInvocation[Null] = new TripleEqualsInvocation[Null](right, false) /** * Returns aTripleEqualsInvocationOnSpread[T]
, given anSpread[T]
, to facilitate * the “<left> should === (<pivot> +- <tolerance>)
” * syntax ofMatchers
. * * @param right theSpread[T]
against which to compare the left-hand value * @return aTripleEqualsInvocationOnSpread
wrapping the passedSpread[T]
value, with *expectingEqual
set totrue
. */ def ===[T](right: Spread[T]): TripleEqualsInvocationOnSpread[T] = new TripleEqualsInvocationOnSpread[T](right, true) /** * Returns aTripleEqualsInvocationOnSpread[T]
, given anSpread[T]
, to facilitate * the “<left> should !== (<pivot> +- <tolerance>)
” * syntax ofMatchers
. * * @param right theSpread[T]
against which to compare the left-hand value * @return aTripleEqualsInvocationOnSpread
wrapping the passedSpread[T]
value, with *expectingEqual
set tofalse
. */ def !==[T](right: Spread[T]): TripleEqualsInvocationOnSpread[T] = new TripleEqualsInvocationOnSpread[T](right, false) } object TripleEqualsSupport { /** * An implementation ofConstraint
for two typesA
andB
that requires anEquality[A]
to * which itsareEqual
method can delegate an equality comparison. * * @param equalityOfA anEquality
type class forA
*/ final class EqualityConstraint[A, B](equalityOfA: Equality[A]) extends Constraint[A, B] { /** * Indicates whether the objects passed asa
andb
are equal by returning the * result of invokingareEqual(a, b)
on the passedequalityOfA
object. * * @param a a left-hand-side object being compared with another (right-hand-side one) for equality (e.g.,a == b
) * @param b a right-hand-side object being compared with another (left-hand-side one) for equality (e.g.,a == b
) */ def areEqual(a: A, b: B): Boolean = equalityOfA.areEqual(a, b) } /** * An implementation ofConstraint
for two typesA
andB
that requires anEquality[B]
* and a conversion function fromA
toB
. * * @param equivalenceOfB anEquivalence
type class forB
*/ final class AToBEquivalenceConstraint[A, B](equivalenceOfB: Equivalence[B], cnv: A => B) extends Constraint[A, B] { /** * Indicates whether the objects passed asa
andb
are equal by return the * result of invokingareEqual(cnv(a), b)
on the passedequalityOfB
object. * ** In other words, the
* * @param a a left-hand-side object being compared with another (right-hand-side one) for equality (e.g.,a
object of typeA
is first converted to aB
via the passed conversion * function,cnv
, then compared for equality with theb
object. *a == b
) * @param b a right-hand-side object being compared with another (left-hand-side one) for equality (e.g.,a == b
) */ override def areEqual(a: A, b: B): Boolean = equivalenceOfB.areEquivalent(cnv(a), b) } /** * An implementation ofConstraint
for two typesA
andB
that requires anEquality[A]
* and a conversion function fromB
toA
. * * @param equivalenceOfA anEquivalence
type class forA
*/ final class BToAEquivalenceConstraint[A, B](equivalenceOfA: Equivalence[A], cnv: B => A) extends Constraint[A, B] { /** * Indicates whether the objects passed asa
andb
are equal by returning the * result of invokingareEqual(a, cnv(b))
on the passedequalityOfA
object. * ** In other words, the
* * @param a a left-hand-side object being compared with another (right-hand-side one) for equality (e.g.,b
object of typeB
is first converted to anA
via the passed conversion * function,cnv
, then compared for equality with thea
object. *a == b
) * @param b a right-hand-side object being compared with another (left-hand-side one) for equality (e.g.,a == b
) */ override def areEqual(a: A, b: B): Boolean = equivalenceOfA.areEquivalent(a, cnv(b)) } /** * Facilitates the “should === (x += y)
” and “should !== (x += y)
” syntax of ScalaTest's matchers DSL. * ** Instances of this class are created and returned by the
* * @param spread the===
and!==
methods of * traitTripleEqualsSupport
. *Spread[T]
against which to compare the left-hand value * @param expectingEqualtrue
if the result of a===
invocation;false
if the result of a!==
invocation. */ final case class TripleEqualsInvocationOnSpread[T](spread: Spread[T], expectingEqual: Boolean) /** * Facilitates the “should ===
” and “should !==
” syntax of ScalaTest's matchers DSL. * ** Instances of this class are created and returned by the
* * @param right the right-hand side value for an equality assertion * @param expectingEqual===
and!==
methods of * traitTripleEqualsSupport
. *true
if the result of a===
invocation;false
if the result of a!==
invocation. */ final case class TripleEqualsInvocation[T](right: T, expectingEqual: Boolean) { override def toString: String = (if (expectingEqual) "===" else "!==") + " " + Prettifier.default(right) } /** * Class representing an spread (i.e., range) between two numbers. * ** The spread is expressed in terms of a
* * @param pivot the pivot number at the center of the spread * @param tolerance the tolerance that determines the high and low point of the spread * * @author Bill Venners */ final case class Spread[T : Numeric](pivot: T, tolerance: T) { private val numeric = implicitly[Numeric[T]] require(numeric.signum(tolerance) >= 0, "tolerance must be zero or greater, but was " + tolerance) private val max = numeric.plus(pivot, tolerance) private val min = numeric.minus(pivot, tolerance) /** * Determines whether the passedNumeric
pivot and tolerance. * The spread extends frompivot - tolerance
topivot + tolerance
, inclusive. *Numeric
valuen
is within the spread represented * by thisSpread
instance. */ def isWithin(n: T): Boolean = { numeric.gteq(n, min) && numeric.lteq(n, max) } /** * Returnstrue
if the passed number,n
, is within the spread represented by thisSpread
instance * ** The purpose of this method, which will likely be used only rarely, is to achieve symmetry around the
* *===
operator. The *TripleEquals
trait (and its type-checking siblingsTypeCheckedTripleEquals
andConversionCheckedTripleEquals
) enable you to write: ** a === (1.0 +- 0.1) ** ** This method ensures the following mirrored form means the same thing: *
* ** (1.0 +- 0.1) === a ** * @param n a number that may or may not lie within this spread */ def ===(n: T): Boolean = isWithin(n) /** * Returnsfalse
if the passed number,n
, is within the spread represented by thisSpread
instance * ** The purpose of this method, which will likely be used only rarely, is to achieve symmetry around the
* *!==
operator. The *TripleEquals
trait (and its type-checking siblingsTypeCheckedTripleEquals
andConversionCheckedTripleEquals
) enable you to write: ** a !== (1.0 +- 0.1) ** ** This method ensures the following mirrored form means the same thing: *
* ** (1.0 +- 0.1) !== a ** * @param n a number that may or may not lie within this spread */ def !==(n: T): Boolean = !isWithin(n) /** * Overrides toString to return "[pivot] plusOrMinus [tolerance]" */ override def toString: String = Prettifier.default(pivot) + " +- " + Prettifier.default(tolerance) } }