org.scalatest.AsyncTestSuite.scala Maven / Gradle / Ivy
/* * Copyright 2001-2014 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 scala.concurrent.Future import scala.concurrent.ExecutionContext import scala.language.implicitConversions // To convert Assertion to Future[Assertion] import enablers.Futuristic /* * TODO: Fill in here and also add a lifecycle-methods to Suite, which is linked to * from SuiteMixin. * *Pending. This is * also true when* This trait provides a final override of
* *withFixture(NoArgTest), declared in * supertraitSuite, because thewithFixture(NoArgTest)lifecycle * method assumes synchronous testing. Here is its signature: ** def withFixture(test: NoArgTest): Outcome ** ** The test function interface,
* *NoArgTest, offers anapplymethod * that also returnsOutcome: ** // In trait NoArgTest: * def apply(): Outcome ** ** Because the result of a test is an
Outcome, when the test function returns, the test body must have determined an outcome already. It * will already be one ofSucceeded,Failed,Canceled, orwithFixture(NoArgTest)returns: because the result type ofwithFixture(NoArgTest)isOutcome, * the test has by definition already finished execution. * * */ /** * The base trait of ScalaTest's asynchronous testing styles, which defines a *withFixturelifecycle method that accepts as its parameter a test function * that returns aFutureOutcome. * ** The
* *withFixturemethod add by this trait has the * following signature and implementation: ** def withFixture(test: NoArgAsyncTest): FutureOutcome = { * test() * } ** ** This trait enables testing of asynchronous code without blocking. Instead of returning *
* *OutcomelikeTestSuite's *withFixture, this trait'swithFixturemethod returns a *FutureOutcome. Similarly, theapplymethod of test function interface, *NoArgAsyncTest, returnsFutureOutcome: ** // In trait NoArgAsyncTest: * def apply(): FutureOutcome ** ** The
* *withFixturemethod supports async testing, because when the test function returns, * the test body has not necessarily finished execution. ** The recommended way to ensure cleanup is performed after a test body finishes execution is * to use a
* *complete-lastlyclause, syntax that is defined in trait *CompleteLastly, which this trait extends. * Usingcleanup-lastlywill ensure that cleanup will occur whether *FutureOutcome-producing code completes abruptly by throwing an exception, or returns * normally yielding aFutureOutcome. In the latter case, *complete-lastlywill * register the cleanup code to execute asynchronously when theFutureOutcomecompletes. ** The
* *withFixturemethod is designed to be stacked, and to enable this, you should always call thesuperimplementation * ofwithFixture, and let it invoke the test function rather than invoking the test function directly. In other words, instead of writing * “test()”, you should write “super.withFixture(test)”. Thus, the recommended * structure of awithFixtureimplementation that performs cleanup looks like this: ** // Your implementation * override def withFixture(test: NoArgAsyncTest) = { * // Perform setup here * complete { * super.withFixture(test) // Invoke the test function * } lastly { * // Perform cleanup here * } * } ** ** If you have no cleanup to perform, you can write
* *withFixturelike this instead: ** // Your implementation * override def withFixture(test: NoArgAsyncTest) = { * // Perform setup here * super.withFixture(test) // Invoke the test function * } ** ** The test function and
* *withFixturemethod returns a *FutureOutcome, * a ScalaTest class that wraps a ScalaFuture[Outcome]and offers methods * more specific to asynchronous test outcomes. In a ScalaFuture, any exception * results in ascala.util.Failure. In aFutureOutcome, a * thrownTestPendingExceptionalways results in aPending, * a thrownTestCanceledExceptionalways results in aCanceled, * and any other exception, so long as it isn't suite-aborting, results in a *Failed. This is true of the asynchronous test code itself that's represented by * theFutureOutcomeand any transformation or callback registered on the *FutureOutcomeinwithFixture. ** If you want to perform an action only for certain outcomes, you'll need to * register code performing that action on the
* *FutureOutcomeusing * one ofFutureOutcome's callback registration methods: *
-
*
onSucceededThen- executed if theOutcomeis aSucceeded. *onFailedThen- executed if theOutcomeis aFailed. *onCanceledThen- executed if theOutcomeis aCanceled. *onPendingThen- executed if theOutcomeis aPending. *onOutcomeThen- executed on anyOutcome(i.e., no * suite-aborting exception is thrown). *onAbortedThen- executed if a suite-aborting exception is thrown. *onCompletedThen- executed whether the result is anOutcome* or a thrown suite-aborting exception. *
* For example, if you want to perform an action if a test fails, you'd register the
* callback using onFailedThen, like this:
*
* // Your implementation
* override def withFixture(test: NoArgAsyncTest) = {
*
* // Perform setup here
*
* val futureOutcome = super.withFixture(test) // Invoke the test function
*
* futureOutcome onFailedThen { ex =>
* // perform action that you want to occur
* // only if a test fails here
* }
* }
*
*
*
* Note that all callback registration methods, such as onFailedThen used in the
* previous example, return a new FutureOutcome that won't complete until the
* the original FutureOutcome and the callback has completed. If the callback
* throws an exception, the resulting FutureOutcome will represent that exception.
* For example, if a FutureOutcome results in Failed, but a callback
* registered on that FutureOutcome with onFailedThen throws TestPendingException, the
* result of the FutureOutcome returned by onFailedThen will
* be Pending.
*
* Lastly, if you want to change the outcome in some way in withFixture, you'll need to use
* the change method of FutureOutcome, like this:
*
* // Your implementation
* override def withFixture(test: NoArgAsyncTest) = {
*
* // Perform setup here
*
* val futureOutcome = super.withFixture(test) // Invoke the test function
*
* futureOutcome change { outcome =>
* // transform the outcome into a new outcome here
* }
* }
*
*/
trait AsyncTestSuite extends Suite with RecoverMethods with CompleteLastly { thisAsyncTestSuite =>
/**
* An implicit execution context used by async styles to transform Future[Assertion] values
* returned by tests into FutureOutcome values, and can be used within the async tests themselves,
* for example, when mapping assertions onto futures.
*/
private final val serialExecutionContext: ExecutionContext = new concurrent.SerialExecutionContext
implicit def executionContext: ExecutionContext = serialExecutionContext
private def anAsyncExceptionThatShouldCauseAnAbort(ex: Throwable): Boolean =
ex match {
// Not sure why a thrown OutOfMemoryError in our test is showing up nested inside
// an ExecutionException, but since it is, look inside.
case ee: java.util.concurrent.ExecutionException if ex.getCause != null => Suite.anExceptionThatShouldCauseAnAbort(ex.getCause)
case other => Suite.anExceptionThatShouldCauseAnAbort(other)
}
/**
* Transform the test outcome, `Registration` type to `AsyncOutcome`.
*
* @param testFun test function
* @return function that returns `AsyncOutcome`
*/
private[scalatest] def transformToOutcome(testFun: => Future[compatible.Assertion]): () => AsyncOutcome =
() => {
val futureSucceeded: Future[Succeeded.type] = testFun.map(_ => Succeeded)
InternalFutureOutcome(
futureSucceeded.recover {
case ex: exceptions.TestCanceledException => Canceled(ex)
case _: exceptions.TestPendingException => Pending
case tfe: exceptions.TestFailedException => Failed(tfe)
case ex: Throwable if !anAsyncExceptionThatShouldCauseAnAbort(ex) => Failed(ex)
}
)/* fills in executionContext here */
}
/**
* Implicitly converts an Assertion to a Future[Assertion].
*
*
* This implicit conversion is used to allow synchronous tests to be included along with
* asynchronous tests in an AsyncTestSuite. It will be
*
Assertion to convert
* @return a Future[Assertion] that has already completed successfully
* (containing the Succeeded singleton).
*/
implicit def convertAssertionToFutureAssertion(assertion: compatible.Assertion): Future[compatible.Assertion] = Future.successful(assertion)
protected[scalatest] def parallelAsyncTestExecution: Boolean = thisAsyncTestSuite.isInstanceOf[org.scalatest.ParallelTestExecution] ||
thisAsyncTestSuite.isInstanceOf[org.scalatest.RandomTestOrder]
// TODO: Document how exceptions are treated. I.e., that TestConceledException becomes Success(Canceled),
// TestPendingException becomes Success(Pending), non-test-fatal exceptions become Success(Failed), and
// test-fatal exceptions become Failure(ex)
/**
* A test function taking no arguments and returning a FutureOutcome.
*
*
* For more detail and examples, see the relevant section in the
* documentation for trait AsyncFlatSpec.
*
FutureOutcome.
*/
def apply(): FutureOutcome
}
/**
* Run the passed test function in the context of a fixture established by this method.
*
*
* This method should set up the fixture needed by the tests of the
* current suite, invoke the test function, and if needed, register a callback
* on the resulting FutureOutcome to perform any clean
* up needed after the test completes. Because the NoArgAsyncTest function
* passed to this method takes no parameters, preparing the fixture will require
* side effects, such as reassigning instance vars in this Suite or initializing
* a globally accessible external database. If you want to avoid reassigning instance vars
* you can use fixture.AsyncTestSuite.
*
* This trait's implementation of runTest invokes this method for each test, passing
* in a NoArgAsyncTest whose apply method will execute the code of the test
* and returns its result.
*
* This trait's implementation of this method simply invokes the passed NoArgAsyncTest function.
*
* If the by-name passed as the first parameter, future, completes abruptly with an exception
* this method will catch that exception, invoke the cleanup function passed as the second parameter,
* cleanup, then rethrow the exception. Otherwise, this method will register the cleanup
* function to be invoked after the resulting future completes.
*
* This method is redefine in this trait solely to narrow its contract. Subclasses must implement
* this method to call the withFixture(NoArgAsyncTest) method, which is defined in
* this trait.
*
* This trait's implementation of this method simply returns SucceededStatus
* and has no other effect.
*
Args for this run
* @return a Status object that indicates when the test started by this method has completed, and whether or not it failed.
*
* @throws NullArgumentException if either testName or args
* is null.
*/
protected override def runTest(testName: String, args: Args): Status = SucceededStatus
}