org.scalatest.exceptions.StackDepthException.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.exceptions
import org.scalactic.Requirements._
import org.scalactic.exceptions.NullArgumentException
import org.scalactic.source
import StackDepthExceptionHelper.getStackDepthFun
/**
* Exception class that encapsulates information about the stack depth at which the line of code that failed resides,
* so that information can be presented to the user that makes it quick to find the failing line of code. (In other
* words, the user need not scan through the stack trace to find the correct filename and line number of the problem code.)
* Having a stack depth is more useful in a testing environment in which test failures are implemented as
* thrown exceptions, as is the case in ScalaTest's built-in suite traits.
*
* @param messageFun a function that produces an optional detail message for this StackDepthException
.
* @param cause an optional cause, the Throwable
that caused this StackDepthException
to be thrown.
* @param posOrStackDepthFun either a source position or a function that produces the depth in the stack trace of this exception at which the line of test code that failed resides.
*
* @throws NullArgumentException if either messageFun
, cause
or failedCodeStackDepthFun
is null
, or Some(null)
.
*
* @author Bill Venners
*/
abstract class StackDepthException(
val messageFun: StackDepthException => Option[String],
val cause: Option[Throwable],
posOrStackDepthFun: Either[source.Position, StackDepthException => Int]
) extends RuntimeException(if (cause != null && cause.isDefined) cause.get else null) with StackDepth {
requireNonNull(messageFun, cause, posOrStackDepthFun)
cause match {
case Some(null) => throw new NullArgumentException("cause was a Some(null)")
case _ =>
}
posOrStackDepthFun match {
case Right(null) => throw new NullArgumentException("posOrStackDepthFun was Right(null)")
case Left(null) => throw new NullArgumentException("posOrStackDepthFun was Left(null)")
case _ =>
}
val position: Option[source.Position] = posOrStackDepthFun.left.toOption
/**
* Constructs a StackDepthException
with an optional pre-determined message
, optional cause, and
* a source position.
*
* @param message an optional detail message for this StackDepthException
.
* @param cause an optional cause, the Throwable
that caused this StackDepthException
to be thrown.
* @param position a source position.
*.
*/
def this(message: Option[String], cause: Option[Throwable], position: source.Position) =
this(
message match {
case null => throw new NullArgumentException("message was null")
case Some(null) => throw new NullArgumentException("message was a Some(null)")
case _ => (e: StackDepthException) => message
},
cause,
Left(position)
)
/**
* Constructs a StackDepthException
with message function, optional cause, and
* a failedCodeStackDepth
function.
*
* @param messageFun a function that produces an optional detail message for this StackDepthException
.
* @param cause an optional cause, the Throwable
that caused this StackDepthException
to be thrown.
* @param failedCodeStackDepthFun a function that return the depth in the stack trace of this exception at which the line of test code that failed resides.
*.
*/
def this(messageFun: StackDepthException => Option[String], cause: Option[Throwable], failedCodeStackDepthFun: StackDepthException => Int) =
this(
messageFun,
cause,
failedCodeStackDepthFun match {
case null => throw new NullArgumentException("failedCodeStackDepthFun was null")
case _ => Right(failedCodeStackDepthFun)
}
)
/**
* Constructs a StackDepthException
with an optional pre-determined message
, optional cause, and
* a failedCodeStackDepth
function.
*
* @param message an optional detail message for this StackDepthException
.
* @param cause an optional cause, the Throwable
that caused this StackDepthException
to be thrown.
* @param failedCodeStackDepthFun a function that return the depth in the stack trace of this exception at which the line of test code that failed resides.
*
* @throws NullArgumentException if either message
or cause
is null
or Some(null)
, or failedCodeStackDepthFun
is null
.
*/
def this(message: Option[String], cause: Option[Throwable], failedCodeStackDepthFun: StackDepthException => Int) =
this(
message match {
case null => throw new NullArgumentException("message was null")
case Some(null) => throw new NullArgumentException("message was a Some(null)")
case _ => (e: StackDepthException) => message
},
cause,
failedCodeStackDepthFun match {
case null => throw new NullArgumentException("failedCodeStackDepthFun was null")
case _ => Right(failedCodeStackDepthFun)
}
)
/**
* Constructs a StackDepthException
with an optional pre-determined message
,
* optional cause
, and and failedCodeStackDepth
. (This was
* the primary constructor form prior to ScalaTest 1.5.)
*
* @param message an optional detail message for this StackDepthException
.
* @param cause an optional cause, the Throwable
that caused this StackDepthException
to be thrown.
* @param failedCodeStackDepth the depth in the stack trace of this exception at which the line of test code that failed resides.
*
* @throws NullArgumentException if either message
of cause
is null
, or Some(null)
.
*/
def this(message: Option[String], cause: Option[Throwable], failedCodeStackDepth: Int) =
this(
message match {
case null => throw new NullArgumentException("message was null")
case Some(null) => throw new NullArgumentException("message was a Some(null)")
case _ => (e: StackDepthException) => message
},
cause,
Right((e: StackDepthException) => failedCodeStackDepth)
)
/**
* An optional detail message for this StackDepth
exception.
*
*
* One reason this is lazy is to delay any searching of the stack trace until it is actually needed. It will
* usually be needed, but not always. For example, exceptions thrown during a shrink phase of a failed property
* will often be StackDepthException
s, but whose message
will never be used. Another related reason is to remove the need
* to create a different exception before creating this one just for the purpose of searching through its stack
* trace for the proper stack depth. Still one more reason is to allow the message to contain information about the
* stack depth, such as the failed file name and line number.
*
*/
lazy val message: Option[String] = messageFun(this)
lazy val failedCodeFilePathname: Option[String] = position.map(_.filePathname)
/**
* The depth in the stack trace of this exception at which the line of test code that failed resides.
*
*
* One reason this is lazy is to delay any searching of the stack trace until it is actually needed. It will
* usually be needed, but not always. For example, exceptions thrown during a shrink phase of a failed property
* will often be StackDepthException
s, but whose failedCodeStackDepth
will never be used. Another reason is to remove the need
* to create a different exception before creating this one just for the purpose of searching through its stack
* trace for the proper stack depth. Still one more reason is to allow the message to contain information about the
* stack depth, such as the failed file name and line number.
*
*/
lazy val failedCodeStackDepth: Int = {
val stackDepthFun =
posOrStackDepthFun match {
case Left(pos) => getStackDepthFun(pos)
case Right(sdf) => sdf
}
stackDepthFun(this)
}
/**
* Returns the detail message string of this StackDepthException
.
*
* @return the detail message string of this StackDepthException
instance (which may be null
).
*/
override def getMessage: String = message.orNull
/*
* Throws IllegalStateException
, because StackDepthException
s are
* always initialized with a cause passed to the constructor of superclass RuntimeException
.
*/
override final def initCause(throwable: Throwable): Throwable = { throw new IllegalStateException }
/**
* Indicates whether this object can be equal to the passed object.
*/
def canEqual(other: Any): Boolean = other.isInstanceOf[StackDepthException]
/**
* Indicates whether this object is equal to the passed object. If the passed object is
* a StackDepthException
, equality requires equal message
,
* cause
, and failedCodeStackDepth
fields, as well as equal
* return values of getStackTrace
.
*/
override def equals(other: Any): Boolean =
other match {
case that: StackDepthException =>
(that canEqual this) &&
message == that.message &&
cause == that.cause &&
failedCodeStackDepth == that.failedCodeStackDepth &&
getStackTrace.deep == that.getStackTrace.deep
case _ => false
}
/**
* Returns a hash code value for this object.
*/
override def hashCode: Int =
41 * (
41 * (
41 * (
41 + message.hashCode
) + cause.hashCode
) + failedCodeStackDepth.hashCode
) + getStackTrace.hashCode
}
private[scalatest] object StackDepthException {
/**
* If message or message contents are null, throw a null exception, otherwise
* create a function that returns the option.
*/
def toExceptionFunction(message: Option[String]): StackDepthException => Option[String] = {
message match {
case null => throw new NullArgumentException("message was null")
case Some(null) => throw new NullArgumentException("message was a Some(null)")
case _ => { e => message }
}
}
}