make TestKit methods return most specific type

document all testkit methods
This commit is contained in:
Roland 2011-05-29 20:18:07 +02:00
parent 1e4084e843
commit 1970b96ae5
2 changed files with 178 additions and 45 deletions

View file

@ -212,6 +212,125 @@ classes, receiving nothing for some time, etc.
The test actor shuts itself down by default after 5 seconds (configurable) The test actor shuts itself down by default after 5 seconds (configurable)
of inactivity, relieving you of the duty of explicitly managing it. of inactivity, relieving you of the duty of explicitly managing it.
Built-In Assertions
-------------------
The abovementioned :meth:`expectMsg` is not the only method for formulating
assertions concerning received messages. Here is the full list:
* :meth:`expectMsg[T](d: Duration, msg: T): T`
The given message object must be received within the specified time; the
object will be returned.
* :meth:`expectMsgPF[T](d: Duration)(pf: PartialFunction[Any, T]): T`
Within the given time period, a message must be received and the given
partial function must be defined for that message; the result from applying
the partial function to the received message is returned. The duration may
be left unspecified (empty parentheses are required in this case) to use
the deadline from the innermost enclosing :ref:`within <TestKit.within>`
block instead.
* :meth:`expectMsgClass[T](d: Duration, c: Class[T]): T`
An object which is an instance of the given :class:`Class` must be received
within the allotted time frame; the object will be returned. Note that this
does a conformance check; if you need the class to be equal, have a look at
:meth:`expectMsgAllClassOf` with a single given class argument.
* :meth:`expectMsgAnyOf[T](d: Duration, obj: T*): T`
An object must be received within the given time, and it must be equal (
compared with ``==``) to at least one of the passed reference objects; the
received object will be returned.
* :meth:`expectMsgAnyClassOf[T](d: Duration, obj: Class[_ <: T]*): T`
An object must be received within the given time, and it must be an
instance of at least one of the supplied :class:`Class` objects; the
received object will be returned.
* :meth:`expectMsgAllOf[T](d: Duration, obj: T*): Seq[T]`
A number of objects matching the size of the supplied object array must be
received within the given time, and for each of the given objects there
must exist at least one among the received ones which equals (compared with
``==``) it. The full sequence of received objects is returned.
* :meth:`expectMsgAllClassOf[T](d: Duration, c: Class[_ <: T]*): Seq[T]`
A number of objects matching the size of the supplied :class:`Class` array
must be received within the given time, and for each of the given classes
there must exist at least one among the received objects whose class equals
(compared with ``==``) it (this is *not* a conformance check). The full
sequence of received objects is returned.
* :meth:`expectMsgAllConformingOf[T](d: Duration, c: Class[_ <: T]*): Seq[T]`
A number of objects matching the size of the supplied :class:`Class` array
must be received within the given time, and for each of the given classes
there must exist at least one among the received objects which is an
instance of this class. The full sequence of received objects is returned.
* :meth:`expectNoMsg(d: Duration)`
No message must be received within the given time. This also fails if a
message has been received before calling this method which has not been
removed from the queue using one of the other methods.
* :meth:`receiveN(n: Int, d: Duration): Seq[AnyRef]`
``n`` messages must be received within the given time; the received
messages are returned.
In addition to message reception assertions there are also methods which help
with message flows:
* :meth:`receiveOne(d: Duration): AnyRef`
Tries to receive one message for at most the given time interval and
returns ``null`` in case of failure. If the given Duration is zero, the
call is non-blocking (polling mode).
* :meth:`receiveWhile[T](max: Duration, idle: Duration)(pf: PartialFunction[Any, T]): Seq[T]`
Collect messages as long as
* they are matching the given partial function
* the given time interval is not used up
* the next message is received within the idle timeout
All collected messages are returned. The maximum duration defaults to the
time remaining in the innermost enclosing :ref:`within <TestKit.within>`
block and the idle duration defaults to infinity (thereby disabling the
idle timeout feature).
* :meth:`awaitCond(p: => Boolean, max: Duration, interval: Duration)`
Poll the given condition every :obj:`interval` until it returns ``true`` or
the :obj:`max` duration is used up. The interval defaults to 100 ms and the
maximum defaults to the time remaining in the innermost enclosing
:ref:`within <TestKit.within>` block.
* :meth:`ignoreMsg(pf: PartialFunction[AnyRef, Boolean])`
:meth:`ignoreNoMsg`
The internal :obj:`testActor` contains a partial function for ignoring
messages: it will only enqueue messages which do not match the function or
for which the function returns ``false``. This function can be set and
reset using the methods given above; each invocation replaces the previous
function, they are not composed.
This feature is useful e.g. when testing a logging system, where you want
to ignore regular messages and are only interested in your specific ones.
.. _TestKit.within:
Timing Assertions
-----------------
Another important part of functional testing concerns timing: certain events Another important part of functional testing concerns timing: certain events
must not happen immediately (like a timer), others need to happen before a must not happen immediately (like a timer), others need to happen before a
deadline. Therefore, all examination methods accept an upper time limit within deadline. Therefore, all examination methods accept an upper time limit within

View file

@ -149,7 +149,7 @@ trait TestKit {
def ignoreNoMsg { testActor ! TestActor.SetIgnore(None) } def ignoreNoMsg { testActor ! TestActor.SetIgnore(None) }
/** /**
* Obtain current time (`System.currentTimeMillis`) as Duration. * Obtain current time (`System.nanoTime`) as Duration.
*/ */
def now: Duration = System.nanoTime.nanos def now: Duration = System.nanoTime.nanos
@ -241,7 +241,7 @@ trait TestKit {
/** /**
* Same as `expectMsg(remaining, obj)`, but correctly treating the timeFactor. * Same as `expectMsg(remaining, obj)`, but correctly treating the timeFactor.
*/ */
def expectMsg(obj: Any): AnyRef = expectMsg_internal(remaining, obj) def expectMsg[T](obj: T): T = expectMsg_internal(remaining, obj)
/** /**
* Receive one message from the test actor and assert that it equals the * Receive one message from the test actor and assert that it equals the
@ -250,13 +250,13 @@ trait TestKit {
* *
* @return the received object * @return the received object
*/ */
def expectMsg(max: Duration, obj: Any): AnyRef = expectMsg_internal(max.dilated, obj) def expectMsg[T](max: Duration, obj: T): T = expectMsg_internal(max.dilated, obj)
private def expectMsg_internal(max: Duration, obj: Any): AnyRef = { private def expectMsg_internal[T](max: Duration, obj: T): T = {
val o = receiveOne(max) val o = receiveOne(max)
assert(o ne null, "timeout during expectMsg") assert(o ne null, "timeout during expectMsg")
assert(obj == o, "expected " + obj + ", found " + o) assert(obj == o, "expected " + obj + ", found " + o)
o o.asInstanceOf[T]
} }
/** /**
@ -320,7 +320,7 @@ trait TestKit {
/** /**
* Same as `expectMsgAnyOf(remaining, obj...)`, but correctly treating the timeFactor. * Same as `expectMsgAnyOf(remaining, obj...)`, but correctly treating the timeFactor.
*/ */
def expectMsgAnyOf(obj: Any*): AnyRef = expectMsgAnyOf_internal(remaining, obj: _*) def expectMsgAnyOf[T](obj: T*): T = expectMsgAnyOf_internal(remaining, obj: _*)
/** /**
* Receive one message from the test actor and assert that it equals one of * Receive one message from the test actor and assert that it equals one of
@ -329,19 +329,19 @@ trait TestKit {
* *
* @return the received object * @return the received object
*/ */
def expectMsgAnyOf(max: Duration, obj: Any*): AnyRef = expectMsgAnyOf_internal(max.dilated, obj: _*) def expectMsgAnyOf[T](max: Duration, obj: T*): T = expectMsgAnyOf_internal(max.dilated, obj: _*)
private def expectMsgAnyOf_internal(max: Duration, obj: Any*): AnyRef = { private def expectMsgAnyOf_internal[T](max: Duration, obj: T*): T = {
val o = receiveOne(max) val o = receiveOne(max)
assert(o ne null, "timeout during expectMsgAnyOf") assert(o ne null, "timeout during expectMsgAnyOf")
assert(obj exists (_ == o), "found unexpected " + o) assert(obj exists (_ == o), "found unexpected " + o)
o o.asInstanceOf[T]
} }
/** /**
* Same as `expectMsgAnyClassOf(remaining, obj...)`, but correctly treating the timeFactor. * Same as `expectMsgAnyClassOf(remaining, obj...)`, but correctly treating the timeFactor.
*/ */
def expectMsgAnyClassOf(obj: Class[_]*): AnyRef = expectMsgAnyClassOf_internal(remaining, obj: _*) def expectMsgAnyClassOf[C](obj: Class[_ <: C]*): C = expectMsgAnyClassOf_internal(remaining, obj: _*)
/** /**
* Receive one message from the test actor and assert that it conforms to * Receive one message from the test actor and assert that it conforms to
@ -350,26 +350,26 @@ trait TestKit {
* *
* @return the received object * @return the received object
*/ */
def expectMsgAnyClassOf(max: Duration, obj: Class[_]*): AnyRef = expectMsgAnyClassOf_internal(max.dilated, obj: _*) def expectMsgAnyClassOf[C](max: Duration, obj: Class[_ <: C]*): C = expectMsgAnyClassOf_internal(max.dilated, obj: _*)
private def expectMsgAnyClassOf_internal(max: Duration, obj: Class[_]*): AnyRef = { private def expectMsgAnyClassOf_internal[C](max: Duration, obj: Class[_ <: C]*): C = {
val o = receiveOne(max) val o = receiveOne(max)
assert(o ne null, "timeout during expectMsgAnyClassOf") assert(o ne null, "timeout during expectMsgAnyClassOf")
assert(obj exists (_ isInstance o), "found unexpected " + o) assert(obj exists (_ isInstance o), "found unexpected " + o)
o o.asInstanceOf[C]
} }
/** /**
* Same as `expectMsgAllOf(remaining, obj...)`, but correctly treating the timeFactor. * Same as `expectMsgAllOf(remaining, obj...)`, but correctly treating the timeFactor.
*/ */
def expectMsgAllOf(obj: Any*) { expectMsgAllOf_internal(remaining, obj: _*) } def expectMsgAllOf[T](obj: T*): Seq[T] = expectMsgAllOf_internal(remaining, obj: _*)
/** /**
* Receive a number of messages from the test actor matching the given * Receive a number of messages from the test actor matching the given
* number of objects and assert that for each given object one is received * number of objects and assert that for each given object one is received
* which equals it. This construct is useful when the order in which the * which equals it and vice versa. This construct is useful when the order in
* objects are received is not fixed. Wait time is bounded by the given * which the objects are received is not fixed. Wait time is bounded by the
* duration, with an AssertionFailure being thrown in case of timeout. * given duration, with an AssertionFailure being thrown in case of timeout.
* *
* <pre> * <pre>
* within(1 second) { * within(1 second) {
@ -379,17 +379,19 @@ trait TestKit {
* } * }
* </pre> * </pre>
*/ */
def expectMsgAllOf(max: Duration, obj: Any*) { expectMsgAllOf_internal(max.dilated, obj: _*) } def expectMsgAllOf[T](max: Duration, obj: T*): Seq[T] = expectMsgAllOf_internal(max.dilated, obj: _*)
private def expectMsgAllOf_internal(max: Duration, obj: Any*) { private def expectMsgAllOf_internal[T](max: Duration, obj: T*): Seq[T] = {
val recv = receiveN(obj.size, now + max) val recv = receiveN_internal(obj.size, max)
assert(obj forall (x recv exists (x == _)), "not found all") obj foreach (x assert(recv exists (x == _), "not found " + x))
recv foreach (x assert(obj exists (x == _), "found unexpected " + x))
recv.asInstanceOf[Seq[T]]
} }
/** /**
* Same as `expectMsgAllClassOf(remaining, obj...)`, but correctly treating the timeFactor. * Same as `expectMsgAllClassOf(remaining, obj...)`, but correctly treating the timeFactor.
*/ */
def expectMsgAllClassOf(obj: Class[_]*) { expectMsgAllClassOf_internal(remaining, obj: _*) } def expectMsgAllClassOf[T](obj: Class[_ <: T]*): Seq[T] = expectMsgAllClassOf_internal(remaining, obj: _*)
/** /**
* Receive a number of messages from the test actor matching the given * Receive a number of messages from the test actor matching the given
@ -399,34 +401,38 @@ trait TestKit {
* Wait time is bounded by the given duration, with an AssertionFailure * Wait time is bounded by the given duration, with an AssertionFailure
* being thrown in case of timeout. * being thrown in case of timeout.
*/ */
def expectMsgAllClassOf(max: Duration, obj: Class[_]*) { expectMsgAllClassOf_internal(max.dilated, obj: _*) } def expectMsgAllClassOf[T](max: Duration, obj: Class[_ <: T]*): Seq[T] = expectMsgAllClassOf_internal(max.dilated, obj: _*)
private def expectMsgAllClassOf_internal(max: Duration, obj: Class[_]*) { private def expectMsgAllClassOf_internal[T](max: Duration, obj: Class[_ <: T]*): Seq[T] = {
val recv = receiveN(obj.size, now + max) val recv = receiveN_internal(obj.size, max)
assert(obj forall (x recv exists (_.getClass eq x)), "not found all") obj foreach (x assert(recv exists (_.getClass eq x), "not found " + x))
recv foreach (x assert(obj exists (_ eq x.getClass), "found non-matching object " + x))
recv.asInstanceOf[Seq[T]]
} }
/** /**
* Same as `expectMsgAllConformingOf(remaining, obj...)`, but correctly treating the timeFactor. * Same as `expectMsgAllConformingOf(remaining, obj...)`, but correctly treating the timeFactor.
*/ */
def expectMsgAllConformingOf(obj: Class[_]*) { expectMsgAllClassOf_internal(remaining, obj: _*) } def expectMsgAllConformingOf[T](obj: Class[_ <: T]*): Seq[T] = expectMsgAllClassOf_internal(remaining, obj: _*)
/** /**
* Receive a number of messages from the test actor matching the given * Receive a number of messages from the test actor matching the given
* number of classes and assert that for each given class one is received * number of classes and assert that for each given class one is received
* which conforms to that class. This construct is useful when the order in * which conforms to that class (and vice versa). This construct is useful
* which the objects are received is not fixed. Wait time is bounded by * when the order in which the objects are received is not fixed. Wait time
* the given duration, with an AssertionFailure being thrown in case of * is bounded by the given duration, with an AssertionFailure being thrown in
* timeout. * case of timeout.
* *
* Beware that one object may satisfy all given class constraints, which * Beware that one object may satisfy all given class constraints, which
* may be counter-intuitive. * may be counter-intuitive.
*/ */
def expectMsgAllConformingOf(max: Duration, obj: Class[_]*) { expectMsgAllConformingOf(max.dilated, obj: _*) } def expectMsgAllConformingOf[T](max: Duration, obj: Class[_ <: T]*): Seq[T] = expectMsgAllConformingOf(max.dilated, obj: _*)
private def expectMsgAllConformingOf_internal(max: Duration, obj: Class[_]*) { private def expectMsgAllConformingOf_internal[T](max: Duration, obj: Class[_ <: T]*): Seq[T] = {
val recv = receiveN(obj.size, now + max) val recv = receiveN_internal(obj.size, max)
assert(obj forall (x recv exists (x isInstance _)), "not found all") obj foreach (x assert(recv exists (x isInstance _), "not found " + x))
recv foreach (x assert(obj exists (_ isInstance x), "found non-matching object " + x))
recv.asInstanceOf[Seq[T]]
} }
/** /**
@ -448,12 +454,13 @@ trait TestKit {
/** /**
* Same as `receiveWhile(remaining)(f)`, but correctly treating the timeFactor. * Same as `receiveWhile(remaining)(f)`, but correctly treating the timeFactor.
*/ */
def receiveWhile[T](f: PartialFunction[AnyRef, T]): Seq[T] = receiveWhile_internal(remaining)(f) @deprecated("insert empty first parameter list", "1.2")
def receiveWhile[T](f: PartialFunction[AnyRef, T]): Seq[T] = receiveWhile(remaining / Duration.timeFactor)(f)
/** /**
* Receive a series of messages as long as the given partial function * Receive a series of messages until one does not match the given partial
* accepts them or the idle timeout is met or the overall maximum duration * function or the idle timeout is met (disabled by default) or the overall
* is elapsed. Returns the sequence of messages. * maximum duration is elapsed. Returns the sequence of messages.
* *
* Note that it is not an error to hit the `max` duration in this case. * Note that it is not an error to hit the `max` duration in this case.
* *
@ -468,15 +475,13 @@ trait TestKit {
* assert(series == (1 to 7).toList) * assert(series == (1 to 7).toList)
* </pre> * </pre>
*/ */
def receiveWhile[T](max: Duration)(f: PartialFunction[AnyRef, T]): Seq[T] = receiveWhile_internal(max.dilated)(f) def receiveWhile[T](max: Duration = Duration.MinusInf, idle: Duration = Duration.Inf)(f: PartialFunction[AnyRef, T]): Seq[T] = {
val stop = now + (if (max == Duration.MinusInf) remaining else max.dilated)
private def receiveWhile_internal[T](max: Duration)(f: PartialFunction[AnyRef, T]): Seq[T] = {
val stop = now + max
var msg: Message = NullMessage var msg: Message = NullMessage
@tailrec @tailrec
def doit(acc: List[T]): List[T] = { def doit(acc: List[T]): List[T] = {
receiveOne(stop - now) receiveOne((stop - now) min idle)
lastMessage match { lastMessage match {
case NullMessage case NullMessage
lastMessage = msg lastMessage = msg
@ -496,10 +501,19 @@ trait TestKit {
ret ret
} }
/**
* Same as `receiveN(n, remaining)` but correctly taking into account
* Duration.timeFactor.
*/
def receiveN(n: Int): Seq[AnyRef] = receiveN_internal(n, remaining)
/** /**
* Receive N messages in a row before the given deadline. * Receive N messages in a row before the given deadline.
*/ */
def receiveN(n: Int, stop: Duration): Seq[AnyRef] = { def receiveN(n: Int, max: Duration): Seq[AnyRef] = receiveN_internal(n, max.dilated)
private def receiveN_internal(n: Int, max: Duration): Seq[AnyRef] = {
val stop = max + now
for { x 1 to n } yield { for { x 1 to n } yield {
val timeout = stop - now val timeout = stop - now
val o = receiveOne(timeout) val o = receiveOne(timeout)