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 anapply
method * 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 *withFixture
lifecycle method that accepts as its parameter a test function * that returns aFutureOutcome
. * ** The
* *withFixture
method 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 *
* *Outcome
likeTestSuite
's *withFixture
, this trait'swithFixture
method returns a *FutureOutcome
. Similarly, theapply
method of test function interface, *NoArgAsyncTest
, returnsFutureOutcome
: ** // In trait NoArgAsyncTest: * def apply(): FutureOutcome ** ** The
* *withFixture
method 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
-lastly
clause, syntax that is defined in trait *CompleteLastly
, which this trait extends. * Usingcleanup
-lastly
will 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
-lastly
will * register the cleanup code to execute asynchronously when theFutureOutcome
completes. ** The
* *withFixture
method is designed to be stacked, and to enable this, you should always call thesuper
implementation * 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 awithFixture
implementation 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
* *withFixture
like this instead: ** // Your implementation * override def withFixture(test: NoArgAsyncTest) = { * // Perform setup here * super.withFixture(test) // Invoke the test function * } ** ** The test function and
* *withFixture
method 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 * thrownTestPendingException
always results in aPending
, * a thrownTestCanceledException
always 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 * theFutureOutcome
and any transformation or callback registered on the *FutureOutcome
inwithFixture
. ** If you want to perform an action only for certain outcomes, you'll need to * register code performing that action on the
* *FutureOutcome
using * one ofFutureOutcome
's callback registration methods: *
-
*
onSucceededThen
- executed if theOutcome
is aSucceeded
. *onFailedThen
- executed if theOutcome
is aFailed
. *onCanceledThen
- executed if theOutcome
is aCanceled
. *onPendingThen
- executed if theOutcome
is 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]): () => AsyncTestHolder =
() => {
val futureSucceeded: Future[Succeeded.type] = testFun.map(_ => Succeeded)
FutureAsyncTestHolder(
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)
//DOTTY-ONLY implicit def convertTestDataAssertionFunToTestDataFutureAssertionFun(fun: TestData => compatible.Assertion): TestData => Future[compatible.Assertion] =
//DOTTY-ONLY (testData: TestData) => Future.successful(fun(testData))
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 var
s in this Suite
or initializing
* a globally accessible external database. If you want to avoid reassigning instance var
s
* you can use FixtureAsyncTestSuite.
*
* 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
}