From 1970b96ae56eeea09e609d14971d6e36c6b51f74 Mon Sep 17 00:00:00 2001 From: Roland Date: Sun, 29 May 2011 20:18:07 +0200 Subject: [PATCH] make TestKit methods return most specific type document all testkit methods --- akka-docs/scala/testing.rst | 119 ++++++++++++++++++ .../src/main/scala/akka/testkit/TestKit.scala | 104 ++++++++------- 2 files changed, 178 insertions(+), 45 deletions(-) diff --git a/akka-docs/scala/testing.rst b/akka-docs/scala/testing.rst index 70eda94e11..871e708fd3 100644 --- a/akka-docs/scala/testing.rst +++ b/akka-docs/scala/testing.rst @@ -212,6 +212,125 @@ classes, receiving nothing for some time, etc. The test actor shuts itself down by default after 5 seconds (configurable) 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 ` + 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 ` + 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 ` 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 must not happen immediately (like a timer), others need to happen before a deadline. Therefore, all examination methods accept an upper time limit within diff --git a/akka-testkit/src/main/scala/akka/testkit/TestKit.scala b/akka-testkit/src/main/scala/akka/testkit/TestKit.scala index 2618ed5a17..5992e783a1 100644 --- a/akka-testkit/src/main/scala/akka/testkit/TestKit.scala +++ b/akka-testkit/src/main/scala/akka/testkit/TestKit.scala @@ -149,7 +149,7 @@ trait TestKit { 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 @@ -241,7 +241,7 @@ trait TestKit { /** * 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 @@ -250,13 +250,13 @@ trait TestKit { * * @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) assert(o ne null, "timeout during expectMsg") 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. */ - 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 @@ -329,19 +329,19 @@ trait TestKit { * * @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) assert(o ne null, "timeout during expectMsgAnyOf") assert(obj exists (_ == o), "found unexpected " + o) - o + o.asInstanceOf[T] } /** * 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 @@ -350,26 +350,26 @@ trait TestKit { * * @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) assert(o ne null, "timeout during expectMsgAnyClassOf") assert(obj exists (_ isInstance o), "found unexpected " + o) - o + o.asInstanceOf[C] } /** * 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 * 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 - * objects are received is not fixed. Wait time is bounded by the given - * duration, with an AssertionFailure being thrown in case of timeout. + * which equals it and vice versa. This construct is useful when the order in + * which the objects are received is not fixed. Wait time is bounded by the + * given duration, with an AssertionFailure being thrown in case of timeout. * *
    * within(1 second) {
@@ -379,17 +379,19 @@ trait TestKit {
    * }
    * 
*/ - 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*) { - val recv = receiveN(obj.size, now + max) - assert(obj forall (x ⇒ recv exists (x == _)), "not found all") + private def expectMsgAllOf_internal[T](max: Duration, obj: T*): Seq[T] = { + val recv = receiveN_internal(obj.size, max) + 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. */ - 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 @@ -399,34 +401,38 @@ trait TestKit { * Wait time is bounded by the given duration, with an AssertionFailure * 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[_]*) { - val recv = receiveN(obj.size, now + max) - assert(obj forall (x ⇒ recv exists (_.getClass eq x)), "not found all") + private def expectMsgAllClassOf_internal[T](max: Duration, obj: Class[_ <: T]*): Seq[T] = { + val recv = receiveN_internal(obj.size, max) + 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. */ - 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 * 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 the objects are received is not fixed. Wait time is bounded by - * the given duration, with an AssertionFailure being thrown in case of - * timeout. + * which conforms to that class (and vice versa). This construct is useful + * when the order in which the objects are received is not fixed. Wait time + * is bounded by the given duration, with an AssertionFailure being thrown in + * case of timeout. * * Beware that one object may satisfy all given class constraints, which * 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[_]*) { - val recv = receiveN(obj.size, now + max) - assert(obj forall (x ⇒ recv exists (x isInstance _)), "not found all") + private def expectMsgAllConformingOf_internal[T](max: Duration, obj: Class[_ <: T]*): Seq[T] = { + val recv = receiveN_internal(obj.size, max) + 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. */ - 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 - * accepts them or the idle timeout is met or the overall maximum duration - * is elapsed. Returns the sequence of messages. + * Receive a series of messages until one does not match the given partial + * function or the idle timeout is met (disabled by default) or the overall + * maximum duration is elapsed. Returns the sequence of messages. * * 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) * */ - def receiveWhile[T](max: Duration)(f: PartialFunction[AnyRef, T]): Seq[T] = receiveWhile_internal(max.dilated)(f) - - private def receiveWhile_internal[T](max: Duration)(f: PartialFunction[AnyRef, T]): Seq[T] = { - val stop = now + max + 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) var msg: Message = NullMessage @tailrec def doit(acc: List[T]): List[T] = { - receiveOne(stop - now) + receiveOne((stop - now) min idle) lastMessage match { case NullMessage ⇒ lastMessage = msg @@ -496,10 +501,19 @@ trait TestKit { 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. */ - 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 { val timeout = stop - now val o = receiveOne(timeout)