org.scalatest.concurrent.ScalaFutures.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.concurrent
import org.scalactic.source
import org.scalatest.Resources
import org.scalatest.Suite.anExceptionThatShouldCauseAnAbort
import org.scalatest.exceptions.{StackDepthException, TestCanceledException, TestFailedException, TestPendingException, TimeoutField}
import org.scalatest.time.Span
import scala.concurrent.Await
import scala.concurrent.TimeoutException
import scala.concurrent.duration.Duration
import scala.util.Failure
import scala.util.Success
/**
* Provides an implicit conversion from scala.concurrent.Future[T]
to
* FutureConcept[T]
.
*
*
* This trait enables you to invoke the methods defined on FutureConcept
on a Scala Future
, as well as to pass a Scala future
* to the whenReady
methods of supertrait Futures
.
* The three ways this trait enables you to test futures are:
*
*
*
* 1. Invoking isReadyWithin
, to assert that a future is ready within a a specified time period.
* Here's an example:
*
*
*
* assert(result.isReadyWithin(100 millis))
*
*
*
* 2. Invoking futureValue
, to obtain a futures result within a specified or implicit time period,
* like this:
*
*
*
* assert(result.futureValue === 7)
*
* // Or, if you expect the future to fail:
* assert(result.failed.futureValue.isInstanceOf[ArithmeticException])
*
*
*
* 3. Passing the future to whenReady
, and performing assertions on the result value passed
* to the given function, as in:
*
*
*
* whenReady(result) { s =>
* s should be ("hello")
* }
*
*
*
* The whenReady
construct periodically inspects the passed
* future, until it is either ready or the configured timeout has been surpassed. If the future becomes
* ready before the timeout, whenReady
passes the future's value to the specified function.
*
*
*
* To make whenReady
more broadly applicable, the type of future it accepts is a FutureConcept[T]
,
* where T
is the type of value promised by the future. Passing a future to whenReady
requires
* an implicit conversion from the type of future you wish to pass (the modeled type) to
* FutureConcept[T]
. Subtrait JavaFutures
provides an implicit conversion from
* java.util.concurrent.Future[T]
to FutureConcept[T]
.
*
*
*
* For example, the following invocation of whenReady
would succeed (not throw an exception):
*
*
*
* import org.scalatest._
* import Matchers._
* import concurrent.Futures._
* import java.util.concurrent._
*
* val exec = Executors.newSingleThreadExecutor
* val task = new Callable[String] { def call() = { Thread.sleep(50); "hi" } }
* whenReady(exec.submit(task)) { s =>
* s should be ("hi")
* }
*
*
*
* However, because the default timeout is 150 milliseconds, the following invocation of
* whenReady
would ultimately produce a TestFailedException
:
*
*
*
* val task = new Callable[String] { def call() = { Thread.sleep(500); "hi" } }
* whenReady(exec.submit(task)) { s =>
* s should be ("hi")
* }
*
*
*
* Assuming the default configuration parameters, a timeout
of 150 milliseconds and an
* interval
of 15 milliseconds,
* were passed implicitly to whenReady
, the detail message of the thrown
* TestFailedException
would look like:
*
*
*
* The future passed to whenReady was never ready, so whenReady timed out. Queried 95 times, sleeping 10 milliseconds between each query.
*
*
* Configuration of whenReady
*
*
* The whenReady
methods of this trait can be flexibly configured.
* The two configuration parameters for whenReady
along with their
* default values and meanings are described in the following table:
*
*
*
*
*
* Configuration Parameter
*
*
* Default Value
*
*
* Meaning
*
*
*
*
* timeout
*
*
* scaled(150 milliseconds)
*
*
* the maximum amount of time to allow unsuccessful queries before giving up and throwing TestFailedException
*
*
*
*
* interval
*
*
* scaled(15 milliseconds)
*
*
* the amount of time to sleep between each query
*
*
*
*
*
* The default values of both timeout and interval are passed to the scaled
method, inherited
* from ScaledTimeSpans
, so that the defaults can be scaled up
* or down together with other scaled time spans. See the documentation for trait ScaledTimeSpans
* for more information.
*
*
*
* The whenReady
methods of trait Futures
each take a PatienceConfig
* object as an implicit parameter. This object provides values for the two configuration parameters. Trait
* Futures
provides an implicit val
named defaultPatience
with each
* configuration parameter set to its default value.
* If you want to set one or more configuration parameters to a different value for all invocations of
* whenReady
in a suite you can override this
* val (or hide it, for example, if you are importing the members of the Futures
companion object rather
* than mixing in the trait). For example, if
* you always want the default timeout
to be 2 seconds and the default interval
to be 5 milliseconds, you
* can override defaultPatience
, like this:
*
*
* implicit override val defaultPatience =
* PatienceConfig(timeout = Span(2, Seconds), interval = Span(5, Millis))
*
*
*
* Or, hide it by declaring a variable of the same name in whatever scope you want the changed values to be in effect:
*
*
*
* implicit val defaultPatience =
* PatienceConfig(timeout = Span(2, Seconds), interval = Span(5, Millis))
*
*
*
* In addition to taking a PatienceConfig
object as an implicit parameter, the whenReady
methods of trait
* Futures
include overloaded forms that take one or two PatienceConfigParam
* objects that you can use to override the values provided by the implicit PatienceConfig
for a single whenReady
* invocation. For example, if you want to set timeout
to 6 seconds for just one particular whenReady
invocation,
* you can do so like this:
*
*
*
* whenReady (exec.submit(task), timeout(Span(6, Seconds))) { s =>
* s should be ("hi")
* }
*
*
*
* This invocation of eventually
will use 6000 for timeout
and whatever value is specified by the
* implicitly passed PatienceConfig
object for the interval
configuration parameter.
* If you want to set both configuration parameters in this way, just list them separated by commas:
*
*
*
* whenReady (exec.submit(task), timeout(Span(6, Seconds)), interval(Span(500, Millis))) { s =>
* s should be ("hi")
* }
*
*
*
* You can also import or mix in the members of SpanSugar
if
* you want a more concise DSL for expressing time spans:
*
*
*
* whenReady (exec.submit(task), timeout(6 seconds), interval(500 millis)) { s =>
* s should be ("hi")
* }
*
*
*
* Note: The whenReady
construct was in part inspired by the whenDelivered
matcher of the
* BlueEyes project, a lightweight, asynchronous web framework for Scala.
*
*
* @author Bill Venners
*/
trait ScalaFutures extends Futures {
import scala.language.implicitConversions
/**
* Implicitly converts a scala.concurrent.Future[T]
to
* FutureConcept[T]
, allowing you to invoke the methods
* defined on FutureConcept
on a Scala Future
, as well as to pass a Scala future
* to the whenReady
methods of supertrait Futures
.
*
*
* See the documentation for supertrait Futures
for the details on the syntax this trait provides
* for testing with Java futures.
*
*
*
* If the eitherValue
method of the underlying Scala future returns a scala.Some
containing a
* scala.util.Failure
containing a java.util.concurrent.ExecutionException
, and this
* exception contains a non-null
cause, that cause will be included in the TestFailedException
as its cause. The
* ExecutionException
will be be included as the TestFailedException
's cause only if the
* ExecutionException
's cause is null
.
*
*
*
* The isExpired
method of the returned FutureConcept
will always return false
, because
* the underlying type, scala.concurrent.Future
, does not support the notion of expiration. Likewise, the isCanceled
* method of the returned FutureConcept
will always return false
, because
* the underlying type, scala.concurrent.Future
, does not support the notion of cancelation.
*
*
* @param scalaFuture a scala.concurrent.Future[T]
to convert
* @return a FutureConcept[T]
wrapping the passed scala.concurrent.Future[T]
*/
implicit def convertScalaFuture[T](scalaFuture: scala.concurrent.Future[T]): FutureConcept[T] =
new FutureConcept[T] {
def eitherValue: Option[Either[Throwable, T]] =
scalaFuture.value.map {
case Success(o) => Right(o)
case Failure(e) => Left(e)
}
def isExpired: Boolean = false // Scala Futures themselves don't support the notion of a timeout
def isCanceled: Boolean = false // Scala Futures don't seem to be cancelable either
override private[concurrent] def futureValueImpl(pos: source.Position)(implicit config: PatienceConfig): T = {
try {
val result: Either[Throwable, T] =
// SKIP-SCALATESTJS-START
if (scalaFuture.isCompleted)
scalaFuture.value.get.transform(s => Success(Right(s)), f => Success(Left(f))).get
else
Await.ready(scalaFuture, Duration.fromNanos(config.timeout.totalNanos)).eitherValue.get
// SKIP-SCALATESTJS-END
//SCALATESTJS-ONLY scalaFuture.value.getOrElse(throw new TimeoutException("Cannot Await or block in Scala.js.")).transform(s => Success(Right(s)), f => Success(Left(f))).get
result match {
case Right(v) => v
case Left(tpe: TestPendingException) => throw tpe
case Left(tce: TestCanceledException) => throw tce
case Left(e) if anExceptionThatShouldCauseAnAbort(e) => throw e
case Left(ee: java.util.concurrent.ExecutionException) if ee.getCause != null =>
val cause = ee.getCause
cause match {
case tpe: TestPendingException => throw tpe
case tce: TestCanceledException => throw tce
case e if anExceptionThatShouldCauseAnAbort(e) => throw e
case _ =>
throw new TestFailedException(
(_: StackDepthException) => Some {
if (cause.getMessage == null)
Resources.futureReturnedAnException(cause.getClass.getName)
else
Resources.futureReturnedAnExceptionWithMessage(cause.getClass.getName, cause.getMessage)
},
Some(cause),
pos
)
}
case Left(e) =>
throw new TestFailedException(
(_: StackDepthException) => Some {
if (e.getMessage == null)
Resources.futureReturnedAnException(e.getClass.getName)
else
Resources.futureReturnedAnExceptionWithMessage(e.getClass.getName, e.getMessage)
},
Some(e),
pos
)
}
}
catch {
case e: TimeoutException =>
throw new TestFailedException(
(_: StackDepthException) => Some(Resources.wasNeverReady(config.timeout.prettyString)),
None,
pos
) with TimeoutField {
val timeout: Span = config.timeout
}
}
}
}
//SCALATESTJS,NATIVE-ONLY override private[concurrent] val jsAdjustment: Int = -1
}
/**
* Companion object that facilitates the importing of ScalaFutures
members as
* an alternative to mixing in the trait. One use case is to import ScalaFutures
's members so you can use
* them in the Scala interpreter.
*/
object ScalaFutures extends ScalaFutures