org.scalatest.Outcome.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.scalactic.{source, Requirements} import Requirements.requireNonNull import org.scalatest.exceptions.{TestPendingException, TestCanceledException, TestFailedException, StackDepthException} /** * Superclass for the possible outcomes of running a test. * *
Canceled outcomes, * extracting the contained exception. * **
* *Outcome
is the result type of thewithFixture
methods of traits *Suite
andFixtureSuite
, as well as their *NoArgTest
andOneArgTest
function types. * The four possible outcomes are: **
* *- *
Succeeded
- indicates a test succeeded- *
Failed
- indicates a test failed and contains an exception describing the failure- *
Canceled
- indicates a test was canceled and contains an exception describing the cancelation- *
Pending
- indicates a test was pending* Note that "ignored" does not appear as a type of
*/ sealed abstract class Outcome extends Product with Serializable { /** * Indicates whether thisOutcome
, because tests are * marked as ignored on the outside and skipped over as the suite executes. So an ignored test never runs, and therefore * never has an outcome. By contrast, a test is determined to be pending by running the test * and observing the actual outcome. If the test body completes abruptly with aTestPendingException
, * then the outcome was that the test was pending. *Outcome
represents a test that succeeded. * ** This class's implementation of this method always returns
* * @return true if thisfalse
. *Outcome
is an instance ofSucceeded
. */ val isSucceeded: Boolean = false /** * Indicates whether thisOutcome
represents a test that failed. * ** This class's implementation of this method always returns
* * @return true if thisfalse
. *Outcome
is an instance ofFailed
. */ val isFailed: Boolean = false /** * Indicates whether thisOutcome
represents a test that was canceled. * ** This class's implementation of this method always returns
* * @return true if thisfalse
. *Outcome
is an instance ofCanceled
. */ val isCanceled: Boolean = false /** * Indicates whether thisOutcome
represents a test that was pending. * ** This class's implementation of this method always returns
* * @return true if thisfalse
. *Outcome
is an instance ofPending
. */ val isPending: Boolean = false /** * Indicates whether thisOutcome
represents a test that either failed or was canceled, in which case thisOutcome
will contain an exception. * * @return true if thisOutcome
is an instance of eitherFailed
orCanceled
. */ val isExceptional: Boolean = false /** * Converts thisOutcome
to anOption[Throwable]
. * ** This class's implementation of this method always returns
* * @return aNone
. *Some
wrapping the contained exception if thisOutcome
is an instance of eitherFailed
orCanceled
. */ def toOption: Option[Throwable] = None /** * Converts thisOutcome
to aSucceeded
. * ** When this
* *Outcome
instance is not Succeeded, it behaves as followed: **
* * @return Succeeded if this- Failed(ex) - throws ex
*- Canceled(tce) - throws tce
*- Pending - throws TestPendingException
*Outcome
instance is a Succeeded. */ def toSucceeded: Succeeded.type // Used internally to resuse the old code that was catching these exceptions when running tests. Eventually I would // like to rewrite that old code to use the result type, but it will still needs to catch and handle these exceptions // in the same way in case they come back from a user's withFixture implementation. private[scalatest] def toUnit: Unit = { this match { case Succeeded => case Exceptional(e) => throw e case Pending => throw new TestPendingException } } } /** * Companion object for traitOutcome
that contains an implicit method that enables * collections ofOutcome
s to be flattened into a collections of contained exceptions. */ object Outcome { import scala.language.implicitConversions /** * Enables collections ofOutcome
s to be flattened into a collections of contained exceptions. * * ** Here's an example: *
* ** scala> import org.scalatest._ * import org.scalatest._ * * scala> import prop.TableDrivenPropertyChecks._ * import prop.TableDrivenPropertyChecks._ * * scala> val squares = // (includes errors) * | Table( * | ("x", "square"), * | ( 0 , 0 ), * | ( 1 , 1 ), * | ( 2 , 4 ), * | ( 3 , 8 ), * | ( 4 , 16 ), * | ( 5 , 26 ), * | ( 6 , 36 ) * | ) * squares: org.scalatest.prop.TableFor2[Int,Int] = * TableFor2((x,square), (0,0), (1,1), (2,4), (3,8), (4,16), (5,26), (6,36)) ** ** Given the above table, which includes some errors, you can obtain an
* *IndexedSeq
of theOutcome
s * of executing an assertion on each row of the table withoutcomeOf
, like this: ** scala> import OutcomeOf._ * import OutcomeOf._ * * scala> import Matchers._ * import Matchers._ * * scala> val outcomes = for ((x, square) <- squares) yield outcomeOf { square shouldEqual x * x } * outcomes: IndexedSeq[org.scalatest.Outcome] = * Vector(Succeeded, Succeeded, Succeeded, * Failed(org.scalatest.exceptions.TestFailedException: 8 did not equal 9), Succeeded, * Failed(org.scalatest.exceptions.TestFailedException: 26 did not equal 25), Succeeded) ** ** Now you have a collection of all the outcomes, including successful ones. If you just want the
* *Failed
andCanceled
outcomes, which * contain exceptions, you can filter out anything that isn't "exceptional," like this: ** scala> outcomes.filter(_.isExceptional) * res1: IndexedSeq[org.scalatest.Outcome] = * Vector(Failed(org.scalatest.exceptions.TestFailedException: 8 did not equal 9), * Failed(org.scalatest.exceptions.TestFailedException: 26 did not equal 25)) ** ** But if you just wanted the contained exceptions, you can (thanks to this implicit method) invoke
* *flatten
on your collection: ** scala> outcomes.flatten * res2: IndexedSeq[Throwable] = * Vector(org.scalatest.exceptions.TestFailedException: 8 did not equal 9, * org.scalatest.exceptions.TestFailedException: 26 did not equal 25) **/ implicit def convertOutcomeToIterator(outcome: Outcome): Iterator[Throwable] = outcome match { case Exceptional(ex) => // Return an iterator with one Throwable in it new Iterator[Throwable] { private var spent: Boolean = false def hasNext: Boolean = !spent def next(): Throwable = if (!spent) { spent = true ex } else throw new NoSuchElementException } case _ => // Return an empty iterator new Iterator[Throwable] { def hasNext: Boolean = false def next(): Throwable = throw new NoSuchElementException } } } /** * Superclass for the two outcomes of running a test that contain an exception:Failed
andCanceled
. * ** This class provides a
* *toOption
method that returns aSome
wrapping the contained exception, and * anisExceptional
field with the valuetrue
. It's companion object provides an extractor that * enables patterns that match a test that either failed or canceled, as in: ** outcome match { * case Exceptional(ex) => // handle failed or canceled case * case _ => // handle succeeded, pending, or omitted case * } ** * @param ex theThrowable
contained in thisExceptional
. */ sealed abstract class Exceptional(ex: Throwable) extends Outcome { /** * Indicates that thisOutcome
represents a test that either failed or was canceled. * * @return true */ override val isExceptional: Boolean = true /** * Converts thisExceptional
to aSome
that wraps the contained exception. * * @return ASome
wrapping the exception contained in thisExceptional
. */ override def toOption: Option[Throwable] = Some(ex) } /** * Companion object to classExceptional
that provides a factory method and an extractor that enables * patterns that match bothFailed
andCanceled
outcomes and * extracts the contained exception and a factory method. */ object Exceptional { /** * Creates anExceptional
instance given the passedThrowable
. * ** If the passed
* *Throwable
is an instance ofTestCanceledException
, this * method will returnCanceled
containing thatTestCanceledException
. Otherwise, * it returns aFailed
containing theThrowable
. ** For example, trait
* *SeveredStackTraces
uses this * factory method to sever the stack trace of the exception contained in either aFailed
andCanceled
* like this: ** abstract override def withFixture(test: NoArgTest): Outcome = { * super.withFixture(test) match { * case Exceptional(e: StackDepth) => Exceptional(e.severedAtStackDepth) * case o => o * } * } ** * @return aFailed
orCanceled
containing the passed exception. */ def apply(e: Throwable): Exceptional = e match { case tce: TestCanceledException => Canceled(tce) case _ => Failed(e) } /** * Extractor enabling patterns that match bothFailed
and* For example, trait
* *SeveredStackTraces
uses this * extractor to sever the stack trace of the exception contained in either aFailed
andCanceled
* like this: ** abstract override def withFixture(test: NoArgTest): Outcome = { * super.withFixture(test) match { * case Exceptional(e: StackDepth) => Exceptional(e.severedAtStackDepth) * case o => o * } * } ** * @param res theOutcome
to extract the throwable from. * @return aSome
wrapping the contained throwable ifres
is an instance of * eitherFailed
orCanceled
, elseNone
. */ def unapply(res: Outcome): Option[Throwable] = res match { case Failed(ex) => Some(ex) case Canceled(ex) => Some(ex) case _ => None } } /** * Outcome for a test that succeeded. * ** Note: the difference between this
*/ case object Succeeded extends Outcome with compatible.Assertion { /** * Indicates that thisSucceeded
object and the similarly namedSucceededStatus
* object is that this object indicates one test (or assertion) succeeded, whereas theSucceededStatus
object indicates the absence of any failed tests or * aborted suites during a run. Both are used as the result type ofSuite
lifecycle methods, butSucceeded
* is a possible result ofwithFixture
, whereasSucceededStatus
is a possible result ofrun
,runNestedSuites
, *runTests
, orrunTest
. In short,Succeeded
is always just about one test (or assertion), whereasSucceededStatus
could be * about something larger: multiple tests or an entire suite. *Outcome
represents a test that succeeded. * ** This class's implementation of this method always returns
* * @return true */ override val isSucceeded: Boolean = true /** * Converts thistrue
. *Outcome
to aSucceeded
. * * @return This Succeeded instance. */ def toSucceeded: Succeeded.type = this } /** * Outcome for a test that failed, containing an exception describing the cause of the failure. * ** Note: the difference between this
* * @param ex theFailed
class and the similarly namedFailedStatus
* object is that an instance of this class indicates one test failed, whereas theFailedStatus
object indicates either one or more tests failed * and/or one or more suites aborted during a run. Both are used as the result type ofSuite
lifecycle methods, butFailed
* is a possible result ofwithFixture
, whereasFailedStatus
is a possible result ofrun
,runNestedSuites
, *runTests
, orrunTest
. In short,Failed
is always just about one test, whereasFailedStatus
could be * about something larger: multiple tests or an entire suite. *Throwable
contained in thisFailed
. */ case class Failed(exception: Throwable) extends Exceptional(exception) { require(!exception.isInstanceOf[TestCanceledException], "a TestCanceledException was passed to Failed's constructor") require(!exception.isInstanceOf[TestPendingException], "a TestPendingException was passed to Failed's constructor") /** * Indicates that thisOutcome
represents a test that failed. * ** This class's implementation of this method always returns
* * @return true */ override val isFailed: Boolean = true /** * Converts thistrue
. *Outcome
to aSucceeded
. * ** The implmentation of this class will re-throw the passed in exception. *
*/ def toSucceeded: Succeeded.type = throw exception } /** * Companion object for `Failed` offering several factory methods. */ object Failed { /** * Creates aFailed
instance, with aTestFailedException
set as itsexception
field. * * @return An instance ofFailed
with aTestFailedException
set as itsexception
field. */ def apply()(implicit pos: source.Position): Failed = new Failed(new TestFailedException((_: StackDepthException) => None, None, pos)) /** * Creates aFailed
instance with the passed in message. * * @param message the message for theTestFailedException
set as itsexception
field * @return An instance ofFailed
with aTestFailedException
created from passed inmessage
set as itsexception
field. */ def apply(message: String)(implicit pos: source.Position): Failed = new Failed(new TestFailedException((_: StackDepthException) => Some(message), None, pos)) /** * Creates aFailed
instance with the passed in message and cause. * * @param message the message for theTestFailedException
set as itsexception
field * @param cause the cause for theTestFailedException
set as itsexception
field * @return An instance ofFailed
with aTestFailedException
created from passed inmessage
andcause
set as itsexception
field. */ def apply(message: String, cause: Throwable)(implicit pos: source.Position): Failed = { // I always wrap this in a TFE because I need to do that to get the message in there. require(!cause.isInstanceOf[TestCanceledException], "a TestCanceledException was passed to a factory method in object Failed") require(!cause.isInstanceOf[TestPendingException], "a TestPendingException was passed to a factory method in object Failed") new Failed(new TestFailedException((_: StackDepthException) => Some(message), Some(cause), pos)) } /** * Creates aFailed
with the passed in cause. * * @param cause the passed in cause * @return AFailed
withexception
field set to a newly createdTestFailedException
using the passed incause
. */ def here(cause: Throwable)(implicit pos: source.Position): Failed = { require(!cause.isInstanceOf[TestCanceledException], "a TestCanceledException was passed to the \"here\" factory method in object Failed") require(!cause.isInstanceOf[TestPendingException], "a TestPendingException was passed to the \"here\" factory method in object Failed") new Failed( if (cause.getMessage != null) new TestFailedException((_: StackDepthException) => Some(cause.getMessage), Some(cause), pos) else new TestFailedException((_: StackDepthException) => None, Some(cause), pos) ) } } /** * Outcome for a test that was canceled, containing an exception describing the cause of the cancelation. * * @param ex theTestCanceledException
contained in thisExceptional
. */ case class Canceled(exception: TestCanceledException) extends Exceptional(exception) { /** * Indicates that thisOutcome
represents a test that was canceled. * ** This class's implementation of this method always returns
* * @return true */ override val isCanceled: Boolean = true /** * Converts thistrue
. *Outcome
to aSucceeded
. * ** The implmentation of this class will re-throw the passed in exception. *
*/ def toSucceeded: Succeeded.type = throw exception } /** * Companion object to classCanceled
that provides, in addition to the extractor and factory method * provided by the compiler given its companion is a case class, a second factory method * that produces aCanceled
outcome given a string message. */ object Canceled { /** * Creates aCanceled
instance, with aTestCanceledException
set as itsexception
field. * * @return An instance ofCanceled
with aTestCanceledException
set as itsexception
field. */ def apply()(implicit pos: source.Position): Canceled = new Canceled(new TestCanceledException((_: StackDepthException) => None, None, Left(pos), None)) /** * Creates aCanceled
instance with the passed in message and cause. * * @param message the message for theTestCanceledException
set as itsexception
field * @param cause the cause for theTestCanceledException
set as itsexception
field * @return An instance ofCanceled
with aTestCanceledException
created from passed inmessage
andcause
set as itsexception
field. */ def apply(message: String, cause: Throwable)(implicit pos: source.Position): Canceled = // TODO write tests for NPEs new Canceled(new TestCanceledException((_: StackDepthException) => Some(message), Some(cause), Left(pos), None)) /** * Creates aCanceled
instance with the passed inThrowable
. If the passed inThrowable
is aTestCanceledException
, * it will be set asexception
field, in other case a newTestCanceledException
will be created usingex
as itscause
* * @param ex the passed inThrowable
* @return An instance ofCanceled
withex
set as itsexception
field ifex
is aTestCanceledException
, or a newly createdTestCanceledException
withex
set as itscause
ifex
is not aTestCanceledException
. */ def apply(ex: Throwable)(implicit pos: source.Position): Canceled = { // TODO write tests for NPEs ex match { case tce: TestCanceledException => new Canceled(tce) case _ => val msg = ex.getMessage if (msg == null) new Canceled(new TestCanceledException((_: StackDepthException) => None, Some(ex), Left(pos), None)) else new Canceled(new TestCanceledException((_: StackDepthException) => Some(msg), Some(ex), Left(pos), None)) } } /** * Creates aCanceled
outcome given a string message. * ** For example, trait
* *CancelAfterFailure
uses this factory method to create * aCanceled
status if acancelRemaining
flag is set, which will * be the case if a test failed previously while running the suite: ** abstract override def withFixture(test: NoArgTest): Outcome = { * if (cancelRemaining) * Canceled("Canceled by CancelOnFailure because a test failed previously") * else * super.withFixture(test) match { * case failed: Failed => * cancelRemaining = true * failed * case outcome => outcome * } * } **/ def apply(message: String)(implicit pos: source.Position): Canceled = { requireNonNull(message) val e = new TestCanceledException((_: StackDepthException) => Some(message), None, Left(pos), None) //e.fillInStackTrace() Canceled(e) } /** * Creates aCanceled
with the passed in cause. * * @param cause the passed in cause * @return ACanceled
withexception
field set to a newly createdTestCanceledException
using the passed incause
. */ def here(cause: Throwable)(implicit pos: source.Position): Canceled = { new Canceled( if (cause.getMessage != null) new TestCanceledException((_: StackDepthException) => Some(cause.getMessage), Some(cause), Left(pos), None) else new TestCanceledException((_: StackDepthException) => None, Some(cause), Left(pos), None) ) } } /** * Outcome for a test that was pending, which contains an optional string giving more information on what exactly is needed * for the test to become non-pending. * * @param message an optional message describing the reason the test is pending */ case object Pending extends Outcome { /** * Indicates that thisOutcome
represents a test that was pending. * ** This class's implementation of this method always returns
* * @return true */ override val isPending: Boolean = true /** * Converts thistrue
. *Outcome
to aSucceeded
. * ** The implmentation of this class will throw
*/ def toSucceeded: Succeeded.type = throw new TestPendingException }TestPendingException
with the passed in message. *