From 7e729ce3d28dd004fe23f287513b8548562bb36a Mon Sep 17 00:00:00 2001 From: Roland Kuhn Date: Wed, 29 Dec 2010 21:47:05 +0100 Subject: [PATCH] add first usage of TestKit - rename expect... methods to expectMsg... to avoid clash with scalatest - add access to testActor, e.g. for registration of that actor with others - add facility for ignoring messages in testActor, which would only clutter the test spec --- .../src/main/scala/akka/util/TestKit.scala | 119 +++++++++++------- .../akka/actor/actor/FSMTimingSpec.scala | 61 +++++++++ 2 files changed, 137 insertions(+), 43 deletions(-) create mode 100644 akka-actor/src/test/scala/akka/actor/actor/FSMTimingSpec.scala diff --git a/akka-actor/src/main/scala/akka/util/TestKit.scala b/akka-actor/src/main/scala/akka/util/TestKit.scala index 2a3e136756..77d3f77ca8 100644 --- a/akka-actor/src/main/scala/akka/util/TestKit.scala +++ b/akka-actor/src/main/scala/akka/util/TestKit.scala @@ -7,22 +7,29 @@ import duration._ import java.util.concurrent.{BlockingQueue, LinkedBlockingQueue} object TestActor { + type Ignore = Option[PartialFunction[AnyRef, Boolean]] + case class SetTimeout(d : Duration) + case class SetIgnore(i : Ignore) } -class TestActor(queue : BlockingQueue[AnyRef]) extends Actor with FSM[Int, Unit] { +class TestActor(queue : BlockingQueue[AnyRef]) extends Actor with FSM[Int, TestActor.Ignore] { import FSM._ import TestActor._ - startWith(0, ()) + startWith(0, None) when(0, stateTimeout = 5 seconds) { case Event(SetTimeout(d), _) => setStateTimeout(0, if (d.finite_?) d else None) stay + case Event(SetIgnore(ign), _) => stay using ign case Event(StateTimeout, _) => stop - case Event(x : AnyRef, _) => - queue offer x + case Event(x : AnyRef, ign) => + val ignore = ign map (z => if (z isDefinedAt x) z(x) else false) getOrElse false + if (!ignore) { + queue offer x + } stay } initialize @@ -31,7 +38,7 @@ class TestActor(queue : BlockingQueue[AnyRef]) extends Actor with FSM[Int, Unit] /** * Test kit for testing actors. Inheriting from this trait enables reception of * replies from actors, which are queued by an internal actor and can be - * examined using the `expect...` methods. Assertions and bounds concerning + * examined using the `expectMsg...` methods. Assertions and bounds concerning * timing are available in the form of `within` blocks. * *
@@ -40,8 +47,8 @@ class TestActor(queue : BlockingQueue[AnyRef]) extends Actor with FSM[Int, Unit]
  *
  *     within (1 second) {
  *         test ! SomeWork
- *         expect(Result1) // bounded to 1 second
- *         expect(Result2) // bounded to the remainder of the 1 second
+ *         expectMsg(Result1) // bounded to 1 second
+ *         expectMsg(Result2) // bounded to the remainder of the 1 second
  *     }
  * }
  * 
@@ -54,14 +61,29 @@ class TestActor(queue : BlockingQueue[AnyRef]) extends Actor with FSM[Int, Unit] * - this trait is not thread-safe (only one actor with one queue, one stack * of `within` blocks); it is expected that the code is executed from a * constructor as shown above, which makes this a non-issue, otherwise take - * care not to run tests within a single test class in parallel. + * care not to run tests within a single test class instance in parallel. + * + * TODO: add receiveWhile for receiving a series of messages (single timeout, + * overall maximum time, partial function for selection, ...) + * + * @author Roland Kuhn + * @since 1.1 */ trait TestKit { private val queue = new LinkedBlockingQueue[AnyRef]() - private val sender = actorOf(new TestActor(queue)).start - protected implicit val senderOption = Some(sender) + /** + * ActorRef of the test actor. Access is provided to enable e.g. + * registration as message target. + */ + protected val testActor = actorOf(new TestActor(queue)).start + + /** + * Implicit sender reference so that replies are possible for messages sent + * from the test class. + */ + protected implicit val senderOption = Some(testActor) private var end : Duration = Duration.Inf @@ -69,7 +91,7 @@ trait TestKit { * Stop test actor. Should be done at the end of the test unless relying on * test actor timeout. */ - def stopTestActor { sender.stop } + def stopTestActor { testActor.stop } /** * Set test actor timeout. By default, the test actor shuts itself down @@ -83,7 +105,18 @@ trait TestKit { * } finally { stopTestActor } * */ - def setTestActorTimeout(d : Duration) { sender ! TestActor.SetTimeout(d) } + def setTestActorTimeout(d : Duration) { testActor ! TestActor.SetTimeout(d) } + + /** + * Ignore all messages in the test actor for which the given partial + * function returns true. + */ + def ignoreMsg(f : PartialFunction[AnyRef, Boolean]) { testActor ! TestActor.SetIgnore(Some(f)) } + + /** + * Stop ignoring messages in the test actor. + */ + def ignoreNoMsg { testActor ! TestActor.SetIgnore(None) } /** * Obtain current time (`System.currentTimeMillis`) as Duration. @@ -104,7 +137,7 @@ trait TestKit { *
      * val ret = within(50 millis) {
      *             test ! "ping"
-     *             expectClass(classOf[String])
+     *             expectMsgClass(classOf[String])
      *           }
      * 
*/ @@ -133,10 +166,10 @@ trait TestKit { def within[T](max : Duration)(f : => T) : T = within(0 seconds, max)(f) /** - * Same as `expect`, but takes the maximum wait time from the innermost + * Same as `expectMsg`, but takes the maximum wait time from the innermost * enclosing `within` block. */ - def expect(obj : Any) : AnyRef = expect(remaining, obj) + def expectMsg(obj : Any) : AnyRef = expectMsg(remaining, obj) /** * Receive one message from the test actor and assert that it equals the @@ -145,18 +178,18 @@ trait TestKit { * * @return the received object */ - def expect(max : Duration, obj : Any) : AnyRef = { + def expectMsg(max : Duration, obj : Any) : AnyRef = { val o = receiveOne(max) - assert (o ne null, "timeout during expect") + assert (o ne null, "timeout during expectMsg") assert (obj == o, "expected "+obj+", found "+o) o } /** - * Same as `expect`, but takes the maximum wait time from the innermost + * Same as `expectMsg`, but takes the maximum wait time from the innermost * enclosing `within` block. */ - def expect[T](f : PartialFunction[Any, T]) : T = expect(remaining)(f) + def expectMsg[T](f : PartialFunction[Any, T]) : T = expectMsg(remaining)(f) /** * Receive one message from the test actor and assert that the given @@ -168,18 +201,18 @@ trait TestKit { * * @return the received object as transformed by the partial function */ - def expect[T](max : Duration)(f : PartialFunction[Any, T]) : T = { + def expectMsg[T](max : Duration)(f : PartialFunction[Any, T]) : T = { val o = receiveOne(max) - assert (o ne null, "timeout during expect") + assert (o ne null, "timeout during expectMsg") assert (f.isDefinedAt(o), "does not match: "+o) f(o) } /** - * Same as `expectClass`, but takes the maximum wait time from the innermost + * Same as `expectMsgClass`, but takes the maximum wait time from the innermost * enclosing `within` block. */ - def expectClass[C](c : Class[C]) : C = expectClass(remaining, c) + def expectMsgClass[C](c : Class[C]) : C = expectMsgClass(remaining, c) /** * Receive one message from the test actor and assert that it conforms to @@ -188,18 +221,18 @@ trait TestKit { * * @return the received object */ - def expectClass[C](max : Duration, c : Class[C]) : C = { + def expectMsgClass[C](max : Duration, c : Class[C]) : C = { val o = receiveOne(max) - assert (o ne null, "timeout during expectClass") + assert (o ne null, "timeout during expectMsgClass") assert (c isInstance o, "expected "+c+", found "+o.getClass) o.asInstanceOf[C] } /** - * Same as `expectAnyOf`, but takes the maximum wait time from the innermost + * Same as `expectMsgAnyOf`, but takes the maximum wait time from the innermost * enclosing `within` block. */ - def expectAnyOf(obj : Any*) : AnyRef = expectAnyOf(remaining, obj : _*) + def expectMsgAnyOf(obj : Any*) : AnyRef = expectMsgAnyOf(remaining, obj : _*) /** * Receive one message from the test actor and assert that it equals one of @@ -208,18 +241,18 @@ trait TestKit { * * @return the received object */ - def expectAnyOf(max : Duration, obj : Any*) : AnyRef = { + def expectMsgAnyOf(max : Duration, obj : Any*) : AnyRef = { val o = receiveOne(max) - assert (o ne null, "timeout during expectAnyOf") + assert (o ne null, "timeout during expectMsgAnyOf") assert (obj exists (_ == o), "found unexpected "+o) o } /** - * Same as `expectAnyClassOf`, but takes the maximum wait time from the innermost + * Same as `expectMsgAnyClassOf`, but takes the maximum wait time from the innermost * enclosing `within` block. */ - def expectAnyClassOf(obj : Class[_]*) : AnyRef = expectAnyClassOf(remaining, obj : _*) + def expectMsgAnyClassOf(obj : Class[_]*) : AnyRef = expectMsgAnyClassOf(remaining, obj : _*) /** * Receive one message from the test actor and assert that it conforms to @@ -228,18 +261,18 @@ trait TestKit { * * @return the received object */ - def expectAnyClassOf(max : Duration, obj : Class[_]*) : AnyRef = { + def expectMsgAnyClassOf(max : Duration, obj : Class[_]*) : AnyRef = { val o = receiveOne(max) - assert (o ne null, "timeout during expectAnyClassOf") + assert (o ne null, "timeout during expectMsgAnyClassOf") assert (obj exists (_ isInstance o), "found unexpected "+o) o } /** - * Same as `expectAllOf`, but takes the maximum wait time from the innermost + * Same as `expectMsgAllOf`, but takes the maximum wait time from the innermost * enclosing `within` block. */ - def expectAllOf(obj : Any*) { expectAllOf(remaining, obj : _*) } + def expectMsgAllOf(obj : Any*) { expectMsgAllOf(remaining, obj : _*) } /** * Receive a number of messages from the test actor matching the given @@ -252,20 +285,20 @@ trait TestKit { * within(1 second) { * dispatcher ! SomeWork1() * dispatcher ! SomeWork2() - * expectAllOf(Result1(), Result2()) + * expectMsgAllOf(Result1(), Result2()) * } * */ - def expectAllOf(max : Duration, obj : Any*) { + def expectMsgAllOf(max : Duration, obj : Any*) { val recv = receiveN(obj.size, now + max) assert (obj forall (x => recv exists (x == _)), "not found all") } /** - * Same as `expectAllClassOf`, but takes the maximum wait time from the innermost + * Same as `expectMsgAllClassOf`, but takes the maximum wait time from the innermost * enclosing `within` block. */ - def expectAllClassOf(obj : Class[_]*) { expectAllClassOf(remaining, obj : _*) } + def expectMsgAllClassOf(obj : Class[_]*) { expectMsgAllClassOf(remaining, obj : _*) } /** * Receive a number of messages from the test actor matching the given @@ -275,16 +308,16 @@ trait TestKit { * Wait time is bounded by the given duration, with an AssertionFailure * being thrown in case of timeout. */ - def expectAllClassOf(max : Duration, obj : Class[_]*) { + def expectMsgAllClassOf(max : Duration, obj : Class[_]*) { val recv = receiveN(obj.size, now + max) assert (obj forall (x => recv exists (_.getClass eq x)), "not found all") } /** - * Same as `expectAllConformingOf`, but takes the maximum wait time from the innermost + * Same as `expectMsgAllConformingOf`, but takes the maximum wait time from the innermost * enclosing `within` block. */ - def expectAllConformingOf(obj : Class[_]*) { expectAllClassOf(remaining, obj : _*) } + def expectMsgAllConformingOf(obj : Class[_]*) { expectMsgAllClassOf(remaining, obj : _*) } /** * Receive a number of messages from the test actor matching the given @@ -297,7 +330,7 @@ trait TestKit { * Beware that one object may satisfy all given class constraints, which * may be counter-intuitive. */ - def expectAllConformingOf(max : Duration, obj : Class[_]*) { + def expectMsgAllConformingOf(max : Duration, obj : Class[_]*) { val recv = receiveN(obj.size, now + max) assert (obj forall (x => recv exists (x isInstance _)), "not found all") } diff --git a/akka-actor/src/test/scala/akka/actor/actor/FSMTimingSpec.scala b/akka-actor/src/test/scala/akka/actor/actor/FSMTimingSpec.scala new file mode 100644 index 0000000000..d02c343fe7 --- /dev/null +++ b/akka-actor/src/test/scala/akka/actor/actor/FSMTimingSpec.scala @@ -0,0 +1,61 @@ +package akka.actor + +import akka.util.TestKit +import akka.util.duration._ + +import org.scalatest.Spec +import org.scalatest.matchers.ShouldMatchers +import org.scalatest.junit.JUnitRunner +import org.junit.runner.RunWith + +@RunWith(classOf[JUnitRunner]) +class FSMTimingSpec + extends Spec + with ShouldMatchers + with TestKit { + + import FSMTimingSpec._ + import FSM._ + + val fsm = Actor.actorOf[StateMachine].start + fsm ! SubscribeTransitionCallBack(testActor) + expectMsg(Initial) + + ignoreMsg { + case Transition(Initial, _) => true + } + + describe("A Finite State Machine") { + + it("should receive StateTimeout") { + within (50 millis, 150 millis) { + fsm ! TestStateTimeout + expectMsg(Transition(TestStateTimeout, Initial)) + } + } + + } + +} + +object FSMTimingSpec { + + trait State + case object Initial extends State + case object TestStateTimeout extends State + + class StateMachine extends Actor with FSM[State, Unit] { + import FSM._ + + startWith(Initial, ()) + when(Initial) { + case Event(TestStateTimeout, _) => goto(TestStateTimeout) + } + when(TestStateTimeout, stateTimeout = 100 millis) { + case Event(StateTimeout, _) => goto(Initial) + } + } + +} + +// vim: set ts=4 sw=4 et: