trait Eventually extends PatienceConfiguration
Trait that provides the eventually
construct, which periodically retries executing
a passed by-name parameter, until it either succeeds or the configured timeout has been surpassed.
The by-name parameter "succeeds" if it returns a result. It "fails" if it throws any exception that
would normally cause a test to fail. (These are any exceptions except TestPendingException
and
Error
s listed in the
Treatment of java.lang.Error
s section of the
documentation of trait Suite
.)
For example, the following invocation of eventually
would succeed (not throw an exception):
val xs = 1 to 125 val it = xs.iterator eventually { it.next should be (3) }
However, because the default timeout is 150 milliseconds, the following invocation of
eventually
would ultimately produce a TestFailedDueToTimeoutException
:
val xs = 1 to 125 val it = xs.iterator eventually { Thread.sleep(50); it.next should be (110) }
Assuming the default configuration parameters, a timeout
of 150 milliseconds and an interval
of 15 milliseconds,
were passed implicitly to eventually
, the detail message of the thrown
TestFailedDueToTimeoutException
would look like:
The code passed to eventually never returned normally. Attempted 2 times over 166.682 milliseconds. Last failure message: 2 was not equal to 110.
The cause of the thrown TestFailedDueToTimeoutException
will be the exception most recently thrown by the block of code passed to eventually. (In
the previous example, the cause would be the TestFailedException
with the detail message 2 was not equal to 100
.)
Configuration of eventually
The eventually
methods of this trait can be flexibly configured.
The two configuration parameters for eventually
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 attempts before giving up and throwing TestFailedDueToTimeoutException
|
interval
|
scaled(15 milliseconds)
|
the amount of time to sleep between each attempt |
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 eventually
methods of trait Eventually
each take a PatienceConfig
object as an implicit parameter. This object provides values for the two configuration parameters. (These configuration parameters
are called "patience" because they determine how patient tests will be with asynchronous operations: how long
they will tolerate failures before giving up and how long they will wait before checking again after a failure.) Trait
Eventually
provides an implicit val
named patienceConfig
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
eventually
in a suite you can override this
val (or hide it, for example, if you are importing the members of the Eventually
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 patienceConfig
, like this:
implicit override val patienceConfig = PatienceConfig(timeout = scaled(Span(2, Seconds)), interval = scaled(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 patienceConfig = PatienceConfig(timeout = scaled(Span(2, Seconds)), interval = scaled(Span(5, Millis)))
Passing your new default values to scaled
is optional, but a good idea because it allows them to
be easily scaled if run on a slower or faster system.
In addition to taking a PatienceConfig
object as an implicit parameter, the eventually
methods of trait
Eventually
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 eventually
invocation. For example, if you want to set timeout
to 5 seconds for just one particular eventually
invocation,
you can do so like this:
eventually (timeout(Span(5, Seconds))) { Thread.sleep(10); it.next should be (110) }
This invocation of eventually
will use 5 seconds for the 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:
eventually (timeout(Span(5, Seconds)), interval(Span(5, Millis))) { it.next should be (110) }
You can also import or mix in the members of SpanSugar
if
you want a more concise DSL for expressing time spans:
eventually (timeout(5 seconds), interval(5 millis)) { it.next should be (110) }
Note that ScalaTest will not scale any time span that is not explicitly passed to scaled
to make
the meaning of the code as obvious as possible. Thus
if you ask for "timeout(5 seconds)
" you will get exactly that: a timeout of five seconds. If you want such explicitly
given values to be scaled, you must pass them to scale
explicitly like this:
eventually (timeout(scaled(5 seconds))) { it.next should be (110) }
The previous code says more clearly that the timeout will be five seconds, unless scaled higher or lower by the scaled
method.
Simple backoff algorithm
The eventually
methods employ a very simple backoff algorithm to try and maximize the speed of tests. If an asynchronous operation
completes quickly, a smaller interval will yield a faster test. But if an asynchronous operation takes a while, a small interval will keep the CPU
busy repeatedly checking and rechecking a not-ready operation, to some extent taking CPU cycles away from other processes that could proceed. To
strike the right balance between these design tradeoffs, the eventually
methods will check more frequently during the initial interval.
Rather than sleeping an entire interval if the initial attempt fails, eventually
will only sleep 1/10 of the configured interval. It
will continue sleeping only 1/10 of the configured interval until the configured interval has passed, after which it sleeps the configured interval
between attempts. Here's an example in which the timeout is set equal to the interval:
val xs = 1 to 125 val it = xs.iterator eventually(timeout(100 milliseconds), interval(100 milliseconds)) { it.next should be (110) }
Even though this call to eventually
will time out after only one interval, approximately, the error message will likely report that more
than one (and less than ten) attempts were made:
The code passed to eventually never returned normally. Attempted 6 times over 100.485 milliseconds. Last failure message: 6 was not equal to 110.
Note that if the initial attempt takes longer than the configured interval to complete, eventually
will never sleep for
a 1/10 interval. You can observe this behavior in the second example above in which the first statement in the block of code passed to eventually
was Thread.sleep(50)
.
Usage note: Eventually
intended primarily for integration testing
Although the default timeouts of trait Eventually
are tuned for unit testing, the use of Eventually
in unit tests is
a choice you should question. Usually during unit testing you'll want to mock out subsystems that would require Eventually
, such as
network services with varying and unpredictable response times. This will allow your unit tests to run as fast as possible while still testing
the focused bits of behavior they are designed to test.
Nevertheless, because sometimes it will make sense to use Eventually
in unit tests (and
because it is destined to happen anyway even when it isn't the best choice), Eventually
by default uses
timeouts tuned for unit tests: Calls to eventually
are more likely to succeed on fast development machines, and if a call does time out,
it will do so quickly so the unit tests can move on.
When you are using Eventually
for integration testing, therefore, the default timeout and interval may be too small. A
good way to override them is by mixing in trait IntegrationPatience
or a similar trait of your
own making. Here's an example:
class ExampleSpec extends FeatureSpec with Eventually with IntegrationPatience { // Your integration tests here... }
Trait IntegrationPatience
increases the default timeout from 150 milliseconds to 15 seconds, the default
interval from 15 milliseconds to 150 milliseconds. If need be, you can do fine tuning of the timeout and interval by
specifying a time span scale factor when you
run your tests.
- Source
- Eventually.scala
- Alphabetic
- By Inheritance
- Eventually
- PatienceConfiguration
- AbstractPatienceConfiguration
- ScaledTimeSpans
- AnyRef
- Any
- Hide All
- Show All
- Public
- Protected
Type Members
- final case class PatienceConfig(timeout: Span = scaled(Span(150, Millis)), interval: Span = scaled(Span(15, Millis))) extends Product with Serializable
Configuration object for asynchronous constructs, such as those provided by traits
Eventually
andWaiters
.Configuration object for asynchronous constructs, such as those provided by traits
Eventually
andWaiters
.The default values for the parameters are:
Configuration Parameter Default Value timeout
scaled(150 milliseconds)
interval
scaled(15 milliseconds)
- timeout
the maximum amount of time to wait for an asynchronous operation to complete before giving up and throwing
TestFailedException
.- interval
the amount of time to sleep between each check of the status of an asynchronous operation when polling
- Definition Classes
- AbstractPatienceConfiguration
Value Members
- final def !=(arg0: Any): Boolean
- Definition Classes
- AnyRef → Any
- final def ##(): Int
- Definition Classes
- AnyRef → Any
- final def ==(arg0: Any): Boolean
- Definition Classes
- AnyRef → Any
- final def asInstanceOf[T0]: T0
- Definition Classes
- Any
- def clone(): AnyRef
- Attributes
- protected[lang]
- Definition Classes
- AnyRef
- Annotations
- @throws(classOf[java.lang.CloneNotSupportedException]) @native()
- final def eq(arg0: AnyRef): Boolean
- Definition Classes
- AnyRef
- def equals(arg0: AnyRef): Boolean
- Definition Classes
- AnyRef → Any
- def eventually[T](fun: => T)(implicit config: PatienceConfig, retrying: Retrying[T], pos: Position): T
Invokes the passed by-name parameter repeatedly until it either succeeds, or a configured maximum amount of time has passed, sleeping a configured interval between attempts.
Invokes the passed by-name parameter repeatedly until it either succeeds, or a configured maximum amount of time has passed, sleeping a configured interval between attempts.
The by-name parameter "succeeds" if it returns a result. It "fails" if it throws any exception that would normally cause a test to fail. (These are any exceptions except
TestPendingException
andError
s listed in the Treatment ofjava.lang.Error
s section of the documentation of traitSuite
.)The maximum amount of time in milliseconds to tolerate unsuccessful attempts before giving up is configured by the
timeout
field of thePatienceConfig
passed implicitly as the last parameter. The interval to sleep between attempts is configured by theinterval
field of thePatienceConfig
passed implicitly as the last parameter.- fun
the by-name parameter to repeatedly invoke
- config
the
PatienceConfig
object containing thetimeout
andinterval
parameters- retrying
the
Retrying
implementation for typeT
- pos
the position of the call site
- returns
the result of invoking the
fun
by-name parameter, the first time it succeeds
- def eventually[T](interval: Interval)(fun: => T)(implicit config: PatienceConfig, retrying: Retrying[T], pos: Position): T
Invokes the passed by-name parameter repeatedly until it either succeeds, or a configured maximum amount of time has passed, sleeping a configured interval between attempts.
Invokes the passed by-name parameter repeatedly until it either succeeds, or a configured maximum amount of time has passed, sleeping a configured interval between attempts.
The by-name parameter "succeeds" if it returns a result. It "fails" if it throws any exception that would normally cause a test to fail. (These are any exceptions except
TestPendingException
andError
s listed in the Treatment ofjava.lang.Error
s section of the documentation of traitSuite
.)The maximum amount of time in milliseconds to tolerate unsuccessful attempts before giving up is configured by the
timeout
field of thePatienceConfig
passed implicitly as the last parameter. The interval to sleep between attempts is configured by the value contained in the passedinterval
parameter.- interval
the
Interval
configuration parameter- fun
the by-name parameter to repeatedly invoke
- config
the
PatienceConfig
object containing the (used)timeout
and (unused)interval
parameters- retrying
the
Retrying
implementation for typeT
- pos
the position of the call site
- returns
the result of invoking the
fun
by-name parameter, the first time it succeeds
- def eventually[T](timeout: Timeout)(fun: => T)(implicit config: PatienceConfig, retrying: Retrying[T], pos: Position): T
Invokes the passed by-name parameter repeatedly until it either succeeds, or a configured maximum amount of time has passed, sleeping a configured interval between attempts.
Invokes the passed by-name parameter repeatedly until it either succeeds, or a configured maximum amount of time has passed, sleeping a configured interval between attempts.
The by-name parameter "succeeds" if it returns a result. It "fails" if it throws any exception that would normally cause a test to fail. (These are any exceptions except
TestPendingException
andError
s listed in the Treatment ofjava.lang.Error
s section of the documentation of traitSuite
.)The maximum amount of time in milliseconds to tolerate unsuccessful attempts before giving up and throwing
TestFailedException
is configured by the value contained in the passedtimeout
parameter. The interval to sleep between attempts is configured by theinterval
field of thePatienceConfig
passed implicitly as the last parameter.- timeout
the
Timeout
configuration parameter- fun
the by-name parameter to repeatedly invoke
- config
the
PatienceConfig
object containing the (unused)timeout
and (used)interval
parameters- retrying
the
Retrying
implementation for typeT
- pos
the position of the call site
- returns
the result of invoking the
fun
by-name parameter, the first time it succeeds
- def eventually[T](timeout: Timeout, interval: Interval)(fun: => T)(implicit retrying: Retrying[T], pos: Position): T
Invokes the passed by-name parameter repeatedly until it either succeeds, or a configured maximum amount of time has passed, sleeping a configured interval between attempts.
Invokes the passed by-name parameter repeatedly until it either succeeds, or a configured maximum amount of time has passed, sleeping a configured interval between attempts.
The by-name parameter "succeeds" if it returns a result. It "fails" if it throws any exception that would normally cause a test to fail. (These are any exceptions except
TestPendingException
andError
s listed in the Treatment ofjava.lang.Error
s section of the documentation of traitSuite
.)The maximum amount of time in milliseconds to tolerate unsuccessful attempts before giving up and throwing
TestFailedException
is configured by the value contained in the passedtimeout
parameter. The interval to sleep between attempts is configured by the value contained in the passedinterval
parameter.- T
type of the by-name parameter
fun
- timeout
the
Timeout
configuration parameter- interval
the
Interval
configuration parameter- fun
the by-name parameter to repeatedly invoke
- retrying
the
Retrying
implementation for typeT
- pos
the position of the call site
- returns
the result of invoking the
fun
by-name parameter, the first time it succeeds
- def finalize(): Unit
- Attributes
- protected[lang]
- Definition Classes
- AnyRef
- Annotations
- @throws(classOf[java.lang.Throwable])
- final def getClass(): Class[_ <: AnyRef]
- Definition Classes
- AnyRef → Any
- Annotations
- @native()
- def hashCode(): Int
- Definition Classes
- AnyRef → Any
- Annotations
- @native()
- def interval(value: Span): Interval
Returns an
Interval
configuration parameter containing the passed value, which specifies the amount of time to sleep after a retry.Returns an
Interval
configuration parameter containing the passed value, which specifies the amount of time to sleep after a retry.- Definition Classes
- PatienceConfiguration
- final def isInstanceOf[T0]: Boolean
- Definition Classes
- Any
- final def ne(arg0: AnyRef): Boolean
- Definition Classes
- AnyRef
- final def notify(): Unit
- Definition Classes
- AnyRef
- Annotations
- @native()
- final def notifyAll(): Unit
- Definition Classes
- AnyRef
- Annotations
- @native()
- implicit def patienceConfig: PatienceConfig
Implicit
PatienceConfig
value providing default configuration values.Implicit
PatienceConfig
value providing default configuration values.To change the default configuration, override or hide this
def
with another implicitPatienceConfig
containing your desired default configuration values.- Definition Classes
- PatienceConfiguration → AbstractPatienceConfiguration
- final def scaled(span: Span): Span
Scales the passed
Span
by theDouble
factor returned byspanScaleFactor
.Scales the passed
Span
by theDouble
factor returned byspanScaleFactor
.The
Span
is scaled by invoking itsscaledBy
method, thus this method has the same behavior: The value returned byspanScaleFactor
can be any positive number or zero, including a fractional number. A number greater than one will scale theSpan
up to a larger value. A fractional number will scale it down to a smaller value. A factor of 1.0 will cause the exact sameSpan
to be returned. A factor of zero will causeSpan.ZeroLength
to be returned. If overflow occurs,Span.Max
will be returned. If underflow occurs,Span.ZeroLength
will be returned.- Definition Classes
- ScaledTimeSpans
- Exceptions thrown
IllegalArgumentException
if the value returned fromspanScaleFactor
is less than zero
- def spanScaleFactor: Double
The factor by which the
scaled
method will scaleSpan
s.The factor by which the
scaled
method will scaleSpan
s.The default implementation of this method will return the span scale factor that was specified for the run, or 1.0 if no factor was specified. For example, you can specify a span scale factor when invoking ScalaTest via the command line by passing a
-F
argument toRunner
.- Definition Classes
- ScaledTimeSpans
- final def synchronized[T0](arg0: => T0): T0
- Definition Classes
- AnyRef
- def timeout(value: Span): Timeout
Returns a
Timeout
configuration parameter containing the passed value, which specifies the maximum amount to wait for an asynchronous operation to complete.Returns a
Timeout
configuration parameter containing the passed value, which specifies the maximum amount to wait for an asynchronous operation to complete.- Definition Classes
- PatienceConfiguration
- def toString(): String
- Definition Classes
- AnyRef → Any
- final def wait(): Unit
- Definition Classes
- AnyRef
- Annotations
- @throws(classOf[java.lang.InterruptedException])
- final def wait(arg0: Long, arg1: Int): Unit
- Definition Classes
- AnyRef
- Annotations
- @throws(classOf[java.lang.InterruptedException])
- final def wait(arg0: Long): Unit
- Definition Classes
- AnyRef
- Annotations
- @throws(classOf[java.lang.InterruptedException]) @native()