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

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

Go to download

ScalaTest is a free, open-source testing toolkit for Scala and Java programmers.

There is a newer version: 2.0.M6-SNAP4
Show newest version
/*
 * Copyright 2001-2012 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.getStackDepthFun
import org.scalatest.Suite.anErrorThatShouldCauseAnAbort
import scala.annotation.tailrec
import org.scalatest.time.Span
import exceptions.{TestCanceledException, TestFailedException, TestPendingException, TimeoutField}

/**
 * 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)
 * 
* *

* 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.ShouldMatchers._
 * 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 an 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 5000 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 { /** * 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 this future *

* * @param timeout * @param config * @return */ final def isReadyWithin(timeout: Span)(implicit config: PatienceConfig): Boolean = { try { futureValue(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. *

* * @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): T = futureValue(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. *

* * @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 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)(implicit config: PatienceConfig): T = futureValue(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., 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 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. *

* * @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): T = futureValue(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., value 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. *

* * @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 */ def futureValue(implicit config: PatienceConfig): T = { val st = Thread.currentThread.getStackTrace val callerStackFrame = if (!st(2).getMethodName.contains("futureValue")) st(2) else st(3) val methodName = if (callerStackFrame.getFileName == "Futures.scala" && callerStackFrame.getMethodName == "whenReady") "whenReady" else if (callerStackFrame.getFileName == "Futures.scala" && callerStackFrame.getMethodName == "isReadyWithin") "isReadyWithin" else "futureValue" val adjustment = methodName match { case "whenReady" => 3 case "isReadyWithin" => 3 case _ => 0 } val startNanos = System.nanoTime @tailrec def tryTryAgain(attempt: Int): T = { val timeout = config.timeout val interval = config.interval if (thisFuture.isCanceled) throw new TestFailedException( sde => Some(Resources("futureWasCanceled")), None, getStackDepthFun("Futures.scala", methodName, adjustment) ) if (thisFuture.isExpired) throw new TestFailedException( sde => Some(Resources("futureExpired", attempt.toString, interval.prettyString)), None, getStackDepthFun("Futures.scala", methodName, adjustment) ) thisFuture.eitherValue match { case Some(Right(v)) => v case Some(Left(tpe: TestPendingException)) => throw tpe case Some(Left(tce: TestCanceledException)) => throw tce case Some(Left(e)) if anErrorThatShouldCauseAnAbort(e) => throw e case Some(Left(e)) => throw new TestFailedException( sde => Some { if (e.getMessage == null) Resources("futureReturnedAnException", e.getClass.getName) else Resources("futureReturnedAnExceptionWithMessage", e.getClass.getName, e.getMessage) }, Some(e), getStackDepthFun("Futures.scala", methodName, adjustment) ) case None => val duration = System.nanoTime - startNanos if (duration < timeout.totalNanos) Thread.sleep(interval.millisPart, interval.nanosPart) else { throw new TestFailedException( sde => Some(Resources("wasNeverReady", attempt.toString, interval.prettyString)), None, getStackDepthFun("Futures.scala", methodName, adjustment) ) with TimeoutField { val timeout: Span = config.timeout } } tryTryAgain(attempt + 1) } } tryTryAgain(1) } } /** * 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. *

* * @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 def whenReady[T, U](future: FutureConcept[T], timeout: Timeout, interval: Interval)(fun: T => U)(implicit config: PatienceConfig): U = { val result = future.futureValue(PatienceConfig(timeout.value, interval.value)) fun(result) } // whenReady(future)(fun)(PatienceConfig(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. *

* * @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 def whenReady[T, U](future: FutureConcept[T], timeout: Timeout)(fun: T => U)(implicit config: PatienceConfig): U = { val result = future.futureValue(PatienceConfig(timeout.value, config.interval)) fun(result) } // whenReady(future)(fun)(PatienceConfig(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 def whenReady[T, U](future: FutureConcept[T], interval: Interval)(fun: T => U)(implicit config: PatienceConfig): U = { val result = future.futureValue(PatienceConfig(config.timeout, interval.value)) fun(result) } // whenReady(future)(fun)(PatienceConfig(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. *

* * * @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 def whenReady[T, U](future: FutureConcept[T])(fun: T => U)(implicit config: PatienceConfig): U = { val result = future.futureValue(config) fun(result) /* val startNanos = System.nanoTime @tailrec def tryTryAgain(attempt: Int): U = { val timeout = config.timeout val interval = config.interval if (future.isCanceled) throw new TestFailedException( sde => Some(Resources("futureWasCanceled")), None, getStackDepthFun("Futures.scala", "whenReady") ) if (future.isExpired) throw new TestFailedException( sde => Some(Resources("futureExpired", attempt.toString, interval.prettyString)), None, getStackDepthFun("Futures.scala", "whenReady") ) future.value match { case Some(Right(v)) => fun(v) case Some(Left(tpe: TestPendingException)) => throw tpe // TODO: In 2.0 add TestCanceledException here case Some(Left(e)) if anErrorThatShouldCauseAnAbort(e) => throw e case Some(Left(e)) => val hasMessage = e.getMessage != null throw new TestFailedException( sde => Some { if (e.getMessage == null) Resources("futureReturnedAnException", e.getClass.getName) else Resources("futureReturnedAnExceptionWithMessage", e.getClass.getName, e.getMessage) }, Some(e), getStackDepthFun("Futures.scala", "whenReady") ) case None => val duration = System.nanoTime - startNanos if (duration < timeout.totalNanos) Thread.sleep(interval.millisPart, interval.nanosPart) else { throw new TestFailedException( sde => Some(Resources("wasNeverReady", attempt.toString, interval.prettyString)), None, getStackDepthFun("Futures.scala", "whenReady") ) } tryTryAgain(attempt + 1) } } tryTryAgain(1) */ } } /** * Companion object that facilitates the importing of Futures members as * an alternative to mixing in the trait. One use case is to import Futures's members so you can use * them in the Scala interpreter: * *
 * $ scala -cp scalatest-1.8.jar
 * Welcome to Scala version 2.9.1.final (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_29).
 * Type in expressions to have them evaluated.
 * Type :help for more information.
 *
 * scala> import org.scalatest._
 * import org.scalatest._
 *
 * scala> import matchers.ShouldMatchers._
 * import matchers.ShouldMatchers._
 *
 * scala> import concurrent.Futures._
 * import concurrent.Futures._
 *
 * scala> import java.util.concurrent._
 * import java.util.concurrent._
 *
 * scala> val exec = Executors.newSingleThreadExecutor
 * newSingleThreadExecutor   
 * 
 * scala> val task = new Callable[String] { def call() = { Thread.sleep(500); "hi" } }
 * task: java.lang.Object with java.util.concurrent.Callable[String] = $anon$1@e1a973
 * 
 * scala> whenReady(exec.submit(task)) { s => s shouldBe "hi" }
 * 
 * scala> val task = new Callable[String] { def call() = { Thread.sleep(5000); "hi" } }
 * task: java.lang.Object with java.util.concurrent.Callable[String] = $anon$1@2993dfb0
 * 
 * scala> whenReady(exec.submit(task)) { s => s shouldBe "hi" }
 * org.scalatest.TestFailedException: The future passed to whenReady was never ready, so whenReady timed out. Queried 95 times, sleeping 10 milliseconds between each query.
 *   at org.scalatest.concurrent.Futures$class.tryTryAgain$1(Futures.scala03)
 *   ...
 * 
*/ private[scalatest] object Futures extends Futures




© 2015 - 2024 Weber Informatics LLC | Privacy Policy