All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.scalatest.concurrent.Futures.scala Maven / Gradle / Ivy

The newest version!
/*
 * 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.scalatest._
import org.scalatest.exceptions.StackDepthExceptionHelper.getStackDepth
import org.scalatest.Suite.anExceptionThatShouldCauseAnAbort
import scala.annotation.tailrec
import org.scalatest.time.Span
import exceptions.{TestCanceledException, TestFailedException, TestPendingException, TimeoutField}
import PatienceConfiguration._
import org.scalactic.source
import exceptions.StackDepthException

/**
 * Trait that facilitates testing with futures.
 *
 * 

* This trait defines a FutureConcept trait that can be used to implicitly wrap * different kinds of futures, thereby providing a uniform testing API for 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 Futures extends PatienceConfiguration { private[concurrent] val jsAdjustment: Int = 0 /** * Concept trait for futures, instances of which are passed to the whenReady * methods of trait Futures. * *

* See the documentation for trait Futures for the details on the syntax this trait * provides for testing with futures. *

* * @author Bill Venners */ trait FutureConcept[T] { thisFuture => /** * Queries this future for its value. * *

* If the future is not ready, this method will return None. If ready, it will either return an exception * or a T. *

*/ def eitherValue: Option[Either[Throwable, T]] /** * Indicates whether this future has expired (timed out). * *

* The timeout detected by this method is different from the timeout supported by whenReady. This timeout * is a timeout of the underlying future. If the underlying future does not support timeouts, this method must always * return false. *

*/ def isExpired: Boolean /** * Indicates whether this future has been canceled. * *

* If the underlying future does not support the concept of cancellation, this method must always return false. *

*/ def isCanceled: Boolean /** * Indicates whether this future is ready within the specified timeout. * *

* 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. *

* * @param timeout * @param config * @return */ final def isReadyWithin(timeout: Span)(implicit config: PatienceConfig, pos: source.Position): Boolean = { try { futureValueImpl(pos)(PatienceConfig(timeout, config.interval)) true } catch { case e: TimeoutField => false } } /** * Returns the result of this FutureConcept, once it is ready, or throws either the * exception returned by the future (i.e., value returned a Left) * or TestFailedException. * *

* The maximum amount of time to wait for the future to become ready before giving up and throwing * TestFailedException is configured by the value contained in the passed * timeout parameter. * The interval to sleep between queries of the future (used only if the future is polled) is configured by the value contained in the passed * interval parameter. *

* *

* This method invokes the overloaded futureValue form with only one (implicit) argument * list that contains only one argument, a PatienceConfig, passing a new * PatienceConfig with the Timeout specified as timeout and * the Interval specified as interval. *

* *

* 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. *

* * @param timeout the Timeout configuration parameter * @param interval the Interval configuration parameter * @return the result of the future once it is ready, if value is defined as a Right * @throws Throwable if once ready, the value of this future is defined as a * Left (in this case, this method throws that same exception) * @throws TestFailedException if the future is cancelled, expires, or is still not ready after * the specified timeout has been exceeded */ final def futureValue(timeout: Timeout, interval: Interval)(implicit pos: source.Position): T = { futureValueImpl(pos)(PatienceConfig(timeout.value, interval.value)) } /** * Returns the result of this FutureConcept, once it is ready, or throws either the * exception returned by the future (i.e., value returned a Left) * or TestFailedException. * *

* The maximum amount of time to wait for the future to become ready before giving up and throwing * TestFailedException is configured by the value contained in the passed * timeout parameter. * The interval to sleep between queries of the future (used only if the future is polled) is configured by the interval field of * the PatienceConfig passed implicitly as the last parameter. *

* *

* This method invokes the overloaded futureValue form with only one (implicit) argument * list that contains only one argument, a PatienceConfig, passing a new * PatienceConfig with the Timeout specified as timeout and * the Interval specified as config.interval. *

* *

* 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. *

* * @param timeout the Timeout configuration parameter * @param config an PatienceConfig object containing timeout and * interval parameters that are unused by this method * @return the result of the future once it is ready, if eitherValue is defined as a Right * @throws Throwable if once ready, the eitherValue of this future is defined as a * Left (in this case, this method throws that same exception) * @throws TestFailedException if the future is cancelled, expires, or is still not ready after * the specified timeout has been exceeded */ final def futureValue(timeout: Timeout)(implicit config: PatienceConfig, pos: source.Position): T = { futureValueImpl(pos)(PatienceConfig(timeout.value, config.interval)) } /** * Returns the result of this FutureConcept, once it is ready, or throws either the * exception returned by the future (i.e., eitherValue returned a Left) * or TestFailedException. * *

* The maximum amount of time to wait for the future to become ready before giving up and throwing * TestFailedException is configured by the timeout field of * the PatienceConfig passed implicitly as the last parameter. * The interval to sleep between queries of the future (used only if the future is polled) is configured by the value contained in the passed * interval parameter. *

* *

* This method invokes the overloaded futureValue form with only one (implicit) argument * list that contains only one argument, a PatienceConfig, passing a new * PatienceConfig with the Interval specified as interval and * the Timeout specified as config.timeout. *

* *

* 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. *

* * @param interval the Interval configuration parameter * @param config an PatienceConfig object containing timeout and * interval parameters that are unused by this method * @return the result of the future once it is ready, if value is defined as a Right * @throws Throwable if once ready, the value of this future is defined as a * Left (in this case, this method throws that same exception) * @throws TestFailedException if the future is cancelled, expires, or is still not ready after * the specified timeout has been exceeded */ final def futureValue(interval: Interval)(implicit config: PatienceConfig, pos: source.Position): T = { futureValueImpl(pos)(PatienceConfig(config.timeout, interval.value)) } /** * Returns the result of this FutureConcept, once it is ready, or throws either the * exception returned by the future (i.e., futureValue returned a Left) * or TestFailedException. * *

* This trait's implementation of this method queries the future repeatedly until it either is * ready, or a configured maximum amount of time has passed, sleeping a configured interval between * attempts; and when ready, returns the future's value. For greater efficiency, implementations of * this trait may override this method so that it blocks the specified timeout while waiting for * the result, if the underlying future supports this. *

* *

* The maximum amount of time to wait for the future to become ready before giving up and throwing * TestFailedException is configured by the timeout field of * the PatienceConfig passed implicitly as the last parameter. * The interval to sleep between queries of the future (used only if the future is polled) is configured by the interval field of * the PatienceConfig passed implicitly as the last parameter. *

* *

* 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. *

* * @param config a PatienceConfig object containing timeout and * interval parameters that are unused by this method * @return the result of the future once it is ready, if value is defined as a Right * @throws Throwable if once ready, the value of this future is defined as a * Left (in this case, this method throws that same exception) * @throws TestFailedException if the future is cancelled, expires, or is still not ready after * the specified timeout has been exceeded */ def futureValue(implicit config: PatienceConfig, pos: source.Position): T = futureValueImpl(pos)(config) private[concurrent] def futureValueImpl(pos: source.Position)(implicit config: PatienceConfig): T } import scala.quoted._ /** * Queries the passed future repeatedly until it either is ready, or a configured maximum * amount of time has passed, sleeping a configured interval between attempts; and when ready, passes the future's value * to the passed function. * *

* The maximum amount of time to tolerate unsuccessful queries before giving up and throwing * TestFailedException is configured by the value contained in the passed * timeout parameter. * The interval to sleep between attempts is configured by the value contained in the passed * interval parameter. *

* *

* 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. *

* * @param future the future to query * @param timeout the Timeout configuration parameter * @param interval the Interval configuration parameter * @param fun the function to which pass the future's value when it is ready * @param config an PatienceConfig object containing timeout and * interval parameters that are unused by this method * @return the result of invoking the fun parameter */ final inline def whenReady[T, U](future: FutureConcept[T], timeout: Timeout, interval: Interval)(fun: T => U)(implicit config: PatienceConfig, pos: source.Position): U = ${ Futures.whenReadyMacro('{this}, '{future}, '{fun}, '{timeout.value}, '{interval.value}) } /** * Queries the passed future repeatedly until it either is ready, or a configured maximum * amount of time has passed, sleeping a configured interval between attempts; and when ready, passes the future's value * to the passed function. * *

* The maximum amount of time in milliseconds to tolerate unsuccessful queries before giving up and throwing * TestFailedException is configured by the value contained in the passed * timeout parameter. * The interval to sleep between attempts is configured by the interval field of * the PatienceConfig passed implicitly as the last parameter. *

* *

* 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. *

* * @param future the future to query * @param timeout the Timeout configuration parameter * @param fun the function to which pass the future's value when it is ready * @param config an PatienceConfig object containing timeout and * interval parameters that are unused by this method * @return the result of invoking the fun parameter */ final inline def whenReady[T, U](future: FutureConcept[T], timeout: Timeout)(fun: T => U)(implicit config: PatienceConfig, pos: source.Position): U = ${ Futures.whenReadyMacro('{this}, '{future}, '{fun}, '{timeout.value}, '{config.interval}) } /** * Queries the passed future repeatedly until it either is ready, or a configured maximum * amount of time has passed, sleeping a configured interval between attempts; and when ready, passes the future's value * to the passed function. * *

* The maximum amount of time in milliseconds to tolerate unsuccessful attempts before giving up is configured by the timeout field of * the PatienceConfig passed implicitly as the last parameter. * The interval to sleep between attempts is configured by the value contained in the passed * interval parameter. *

* * @param future the future to query * @param interval the Interval configuration parameter * @param fun the function to which pass the future's value when it is ready * @param config an PatienceConfig object containing timeout and * interval parameters that are unused by this method * @return the result of invoking the fun parameter */ final inline def whenReady[T, U](future: FutureConcept[T], interval: Interval)(fun: T => U)(implicit config: PatienceConfig, pos: source.Position): U = ${ Futures.whenReadyMacro('{this}, '{future}, '{fun}, '{config.timeout}, '{interval.value}) } /** * Queries the passed future repeatedly until it either is ready, or a configured maximum * amount of time has passed, sleeping a configured interval between attempts; and when ready, passes the future's value * to the passed function. * *

* The maximum amount of time in milliseconds to tolerate unsuccessful attempts before giving up is configured by the timeout field of * the PatienceConfig passed implicitly as the last parameter. * The interval to sleep between attempts is configured by the interval field of * the PatienceConfig passed implicitly as the last parameter. *

* *

* 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. *

* * * @param future the future to query * @param fun the function to which pass the future's value when it is ready * @param config an PatienceConfig object containing timeout and * interval parameters that are unused by this method * @return the result of invoking the fun parameter */ final inline def whenReady[T, U](future: FutureConcept[T])(fun: T => U)(implicit config: PatienceConfig, pos: source.Position): U = ${ Futures.whenReadyMacro('{this}, '{future}, '{fun}, '{config.timeout}, '{config.interval}) } /**/ } object Futures extends Futures { final def whenReadyImpl[T, U](futures: Futures)(future: futures.FutureConcept[T], fun: T => U, timeout: Span, interval: Span, pos: source.Position): U = { val result = future.futureValueImpl(pos)(futures.PatienceConfig(timeout, interval)) fun(result) } import scala.quoted._ final def workaroundWhenReadyImpl[T, U](futures: Futures)(future: Futures#FutureConcept[T], fun: T => U, timeout: Span, interval: Span, pos: source.Position): U = whenReadyImpl(futures)(future.asInstanceOf[futures.FutureConcept[T]], fun, timeout, interval, pos) // Ideally, we can use future: Expr[futures.FutureConcept[T]] and fun Expr[T => U] here, can't get it to work so we have the above workaroundWhenReadyImpl that takes Any. private[concurrent] def whenReadyMacro[T, U](futures: Expr[Futures], future: Expr[Futures#FutureConcept[T]], fun: Expr[T => U], timeout: Expr[Span], interval: Expr[Span])(using quotes: Quotes, typeT: Type[T], typeU: Type[U]): Expr[U] = { source.Position.withPosition[U]('{(pos: source.Position) => workaroundWhenReadyImpl(${futures})(${future}, ${fun}, ${timeout}, ${interval}, pos) }) } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy