org.scalatest.Inside.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
import org.scalatest.exceptions.TestFailedException
import org.scalatest.exceptions.StackDepthException
import scala.annotation.tailrec
import org.scalactic.{Resources => _, _}
/**
* Trait containing the inside
construct, which allows you to make statements about nested object graphs using pattern matching.
*
*
* For example, given the following case classes:
*
*
* case class Address(street: String, city: String, state: String, zip: String)
* case class Name(first: String, middle: String, last: String)
* case class Record(name: Name, address: Address, age: Int)
*
*
* You could write:
*
*
* inside (rec) { case Record(name, address, age) =>
* inside (name) { case Name(first, middle, last) =>
* first should be ("Sally")
* middle should be ("Ann")
* last should be ("Jones")
* }
* inside (address) { case Address(street, city, state, zip) =>
* street should startWith ("25")
* city should endWith ("Angeles")
* state should equal ("CA")
* zip should be ("12345")
* }
* age should be < 99
* }
*
*
*
* If an assertion fails, the error message will include the toString
of each value passed
* to inside
clauses enclosing the failed assertion. For example, if rec
in
* the previous expression was defined like this:
*
*
*
* val rec = Record(
* Name("Sally", "Anna", "Jones"),
* Address("25 Main St", "Los Angeles", "CA", "12345"),
* 38
* )
*
*
*
* The error message will read:
*
*
*
* "Ann[a]" was not equal to "Ann[]", inside Name(Sally,Anna,Jones),
* inside Record(Name(Sally,Anna,Jones),Address(25 Main St,Los Angeles,CA,12345),38)
*
*
*/
trait Inside {
/**
* Inspects inside the passed value using the passed partial function.
*
*
* The inside
method checks to see whether the partial function passed as the second curried
* parameter is defined at the value passed as the first parameter, and if so, passes that value to the
* partial function.
*
*
*
* If the partial function is not defined at the passed value, inside
will throw a
* TestFailedException
with a detail message describing the problem. Otherwise, if the
* partial function returns normally, inside
will return normally. If the partial function
* completes abruptly with an exception that mixes in ModifiableMessage
(such as
* TestFailedException
), inside
will append the value's toString
of
* to the exception's detail message, and rethrow it. If the exception thrown by the partial function does not mix
* in ModifiableMessage
, inside
completes abruptly with that same exception.
*
*
* @param value the value inside of which to inspect
* @param pf the partial function to use to inspect inside the passed value
* @throws TestFailedException if the passed partial function is not defined at the passed value
*/
inline def inside[T, U](value: T)(pf: PartialFunction[T, U]): U =
${ Inside.insideMacro('{value})('{pf}) }
}
/**
* Companion object that facilitates the importing of the inside
construct as
* an alternative to mixing it in. One use case is to import the inside
construct so you can use
* it in the Scala interpreter:
*
*
* $ scala -cp scalatest-1.8.jar
* Welcome to Scala version 2.9.1.final (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_29).
* Type in expressions to have them evaluated.
* Type :help for more information.
*
* scala> import org.scalatest._
* import org.scalatest._
*
* scala> import matchers.Matchers._
* import matchers.Matchers._
*
* scala> import Inside._
* import Inside._
*
* scala> inside (List(1, 2, 3)) { case List(x, y, z) =>
* | y should equal (2)
* | }
*
* scala> inside (List(1, 2, 3)) { case List(x, y, z) =>
* | x should equal (2)
* | }
* org.scalatest.TestFailedException: 1 did not equal 2, inside List(1, 2, 3)
* at org.scalatest.matchers.Matchers$class.newTestFailedException(Matchers.scala:150)
* at org.scalatest.matchers.Matchers$.newTestFailedException(Matchers.scala:2331)
* at org.scalatest.matchers.Matchers$ShouldMethodHelper$.shouldMatcher(Matchers.scala:873)
* ...
*
*/
object Inside extends Inside {
private val level = new ThreadLocal[Int]
def insideWithPos[T, U](value: T, pf: PartialFunction[T, U], pos: source.Position): U = {
def appendInsideMessage(currentMessage: Option[String]) = {
val st = Thread.currentThread.getStackTrace
/*val levelCount =
st.count { elem =>
elem.getClassName == "org.scalatest.Inside$class" && elem.getMethodName == "inside"
}*/
val levelCount = Inside.level.get
val indentation = " " * (levelCount)
currentMessage match {
case Some(msg) => Some(Resources.insidePartialFunctionAppendSomeMsg(msg.trim, indentation, value.toString()))
case None => Some(Resources.insidePartialFunctionAppendNone(indentation, value.toString()))
}
}
Inside.level.set(Option(Inside.level.get).getOrElse(0) + 1)
if (pf.isDefinedAt(value)) {
try {
val result = pf(value)
Inside.level.set(Inside.level.get - 1)
result
}
catch {
case e: org.scalatest.exceptions.ModifiableMessage[_] =>
Inside.level.set(Inside.level.get - 1)
throw e.modifyMessage(appendInsideMessage)
}
}
else {
Inside.level.set(Inside.level.get - 1)
throw new TestFailedException((_: StackDepthException) => Some(Resources.insidePartialFunctionNotDefined(value.toString())), None, pos)
}
}
import scala.quoted._
private[scalatest] def insideMacro[T, U](value: Expr[T])(pf: Expr[PartialFunction[T, U]])(using quotes: Quotes, typeT: Type[T], typeU: Type[U]): Expr[U] = {
source.Position.withPosition[U]('{(pos: source.Position) => insideWithPos(${value}, ${pf}, pos) })
}
}