2011-05-19 21:34:21 +02:00
|
|
|
|
/**
|
2012-01-19 18:21:06 +01:00
|
|
|
|
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
2011-05-19 21:34:21 +02:00
|
|
|
|
*/
|
2011-03-05 14:36:08 +01:00
|
|
|
|
package akka.testkit
|
2010-12-26 22:49:40 +01:00
|
|
|
|
|
2011-06-13 22:36:46 +02:00
|
|
|
|
import akka.actor._
|
2010-12-26 22:49:40 +01:00
|
|
|
|
import Actor._
|
2011-03-05 14:36:08 +01:00
|
|
|
|
import akka.util.Duration
|
|
|
|
|
|
import akka.util.duration._
|
2011-06-05 10:45:27 +02:00
|
|
|
|
import java.util.concurrent.{ BlockingDeque, LinkedBlockingDeque, TimeUnit, atomic }
|
|
|
|
|
|
import atomic.AtomicInteger
|
2010-12-30 22:43:24 +01:00
|
|
|
|
import scala.annotation.tailrec
|
2011-11-10 20:08:00 +01:00
|
|
|
|
import akka.actor.ActorSystem
|
2012-02-10 16:02:37 +01:00
|
|
|
|
import akka.util.Timeout
|
2012-05-15 21:12:46 +02:00
|
|
|
|
import akka.util.BoxedType
|
2012-06-25 19:30:13 +02:00
|
|
|
|
import scala.annotation.varargs
|
2010-12-26 22:49:40 +01:00
|
|
|
|
|
2010-12-28 23:51:41 +01:00
|
|
|
|
object TestActor {
|
2011-01-04 13:50:50 +01:00
|
|
|
|
type Ignore = Option[PartialFunction[AnyRef, Boolean]]
|
2010-12-29 21:47:05 +01:00
|
|
|
|
|
2012-02-19 00:09:04 +01:00
|
|
|
|
trait AutoPilot {
|
|
|
|
|
|
def run(sender: ActorRef, msg: Any): Option[AutoPilot]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2011-05-18 17:25:30 +02:00
|
|
|
|
case class SetIgnore(i: Ignore)
|
2011-12-13 15:41:00 +01:00
|
|
|
|
case class Watch(ref: ActorRef)
|
|
|
|
|
|
case class UnWatch(ref: ActorRef)
|
2012-02-19 00:09:04 +01:00
|
|
|
|
case class SetAutoPilot(ap: AutoPilot)
|
2011-06-13 22:36:46 +02:00
|
|
|
|
|
|
|
|
|
|
trait Message {
|
|
|
|
|
|
def msg: AnyRef
|
2011-10-22 16:06:20 +02:00
|
|
|
|
def sender: ActorRef
|
2011-06-13 22:36:46 +02:00
|
|
|
|
}
|
2011-10-22 16:06:20 +02:00
|
|
|
|
case class RealMessage(msg: AnyRef, sender: ActorRef) extends Message
|
2011-06-13 22:36:46 +02:00
|
|
|
|
case object NullMessage extends Message {
|
|
|
|
|
|
override def msg: AnyRef = throw new IllegalActorStateException("last receive did not dequeue a message")
|
2011-10-22 16:06:20 +02:00
|
|
|
|
override def sender: ActorRef = throw new IllegalActorStateException("last receive did not dequeue a message")
|
2011-06-13 22:36:46 +02:00
|
|
|
|
}
|
2010-12-28 23:51:41 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2011-11-13 00:34:30 +01:00
|
|
|
|
class TestActor(queue: BlockingDeque[TestActor.Message]) extends Actor {
|
2011-01-04 13:50:50 +01:00
|
|
|
|
import TestActor._
|
|
|
|
|
|
|
2011-11-13 00:34:30 +01:00
|
|
|
|
var ignore: Ignore = None
|
|
|
|
|
|
|
2012-02-19 00:09:04 +01:00
|
|
|
|
var autopilot: Option[AutoPilot] = None
|
|
|
|
|
|
|
2011-11-13 00:34:30 +01:00
|
|
|
|
def receive = {
|
2012-02-19 00:09:04 +01:00
|
|
|
|
case SetIgnore(ign) ⇒ ignore = ign
|
|
|
|
|
|
case x @ Watch(ref) ⇒ context.watch(ref); queue.offerLast(RealMessage(x, self))
|
|
|
|
|
|
case x @ UnWatch(ref) ⇒ context.unwatch(ref); queue.offerLast(RealMessage(x, self))
|
|
|
|
|
|
case SetAutoPilot(pilot) ⇒ autopilot = Some(pilot)
|
2011-11-13 00:34:30 +01:00
|
|
|
|
case x: AnyRef ⇒
|
2012-02-19 00:09:04 +01:00
|
|
|
|
autopilot = autopilot.flatMap(_.run(sender, x))
|
2011-11-13 00:34:30 +01:00
|
|
|
|
val observe = ignore map (ignoreFunc ⇒ if (ignoreFunc isDefinedAt x) !ignoreFunc(x) else true) getOrElse true
|
|
|
|
|
|
if (observe) queue.offerLast(RealMessage(x, sender))
|
2011-01-04 13:50:50 +01:00
|
|
|
|
}
|
2011-12-05 18:52:32 +01:00
|
|
|
|
|
|
|
|
|
|
override def postStop() = {
|
|
|
|
|
|
import scala.collection.JavaConverters._
|
2011-12-07 07:49:34 +01:00
|
|
|
|
queue.asScala foreach { m ⇒ context.system.deadLetters ! DeadLetter(m.msg, m.sender, self) }
|
2011-12-05 18:52:32 +01:00
|
|
|
|
}
|
2010-12-26 22:49:40 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2010-12-28 23:51:41 +01:00
|
|
|
|
/**
|
2012-06-04 21:21:36 +02:00
|
|
|
|
* Implementation trait behind the [[akka.testkit.TestKit]] class: you may use
|
2012-06-04 19:28:58 +02:00
|
|
|
|
* this if inheriting from a concrete class is not possible.
|
2010-12-28 23:51:41 +01:00
|
|
|
|
*
|
2012-06-04 21:21:36 +02:00
|
|
|
|
* <b>Use of the trait is discouraged because of potential issues with binary
|
2012-06-04 19:28:58 +02:00
|
|
|
|
* backwards compatibility in the future, use at own risk.</b>
|
2010-12-28 23:51:41 +01:00
|
|
|
|
*
|
2012-06-04 21:21:36 +02:00
|
|
|
|
* This trait requires the concrete class mixing it in to provide an
|
2012-06-04 19:28:58 +02:00
|
|
|
|
* [[akka.actor.ActorSystem]] which is available before this traits’s
|
|
|
|
|
|
* constructor is run. The recommended way is this:
|
2012-03-30 15:24:57 +02:00
|
|
|
|
*
|
2012-06-04 19:28:58 +02:00
|
|
|
|
* {{{
|
|
|
|
|
|
* class MyTest extends TestKitBase {
|
|
|
|
|
|
* implicit lazy val system = ActorSystem() // may add arguments here
|
|
|
|
|
|
* ...
|
2010-12-28 23:51:41 +01:00
|
|
|
|
* }
|
2012-06-04 19:28:58 +02:00
|
|
|
|
* }}}
|
2010-12-28 23:51:41 +01:00
|
|
|
|
*/
|
2012-06-04 10:03:41 +02:00
|
|
|
|
trait TestKitBase {
|
2010-12-26 22:49:40 +01:00
|
|
|
|
|
2011-06-13 22:36:46 +02:00
|
|
|
|
import TestActor.{ Message, RealMessage, NullMessage }
|
2011-10-07 15:22:36 +02:00
|
|
|
|
|
2012-06-04 10:03:41 +02:00
|
|
|
|
implicit val system: ActorSystem
|
2011-11-24 18:53:18 +01:00
|
|
|
|
val testKitSettings = TestKitExtension(system)
|
2011-06-13 22:36:46 +02:00
|
|
|
|
|
|
|
|
|
|
private val queue = new LinkedBlockingDeque[Message]()
|
|
|
|
|
|
private[akka] var lastMessage: Message = NullMessage
|
2011-05-18 17:25:30 +02:00
|
|
|
|
|
2011-11-12 22:37:12 +01:00
|
|
|
|
def lastSender = lastMessage.sender
|
|
|
|
|
|
|
2011-01-04 13:50:50 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* ActorRef of the test actor. Access is provided to enable e.g.
|
|
|
|
|
|
* registration as message target.
|
|
|
|
|
|
*/
|
2011-11-30 15:16:20 +01:00
|
|
|
|
lazy val testActor: ActorRef = {
|
2012-01-16 20:18:08 +01:00
|
|
|
|
val impl = system.asInstanceOf[ActorSystemImpl] //TODO ticket #1559
|
2011-11-16 17:18:36 +01:00
|
|
|
|
impl.systemActorOf(Props(new TestActor(queue))
|
2011-12-21 19:02:06 +01:00
|
|
|
|
.withDispatcher(CallingThreadDispatcher.Id),
|
2011-11-16 17:18:36 +01:00
|
|
|
|
"testActor" + TestKit.testActorId.incrementAndGet)
|
|
|
|
|
|
}
|
2011-01-04 13:50:50 +01:00
|
|
|
|
|
2011-11-11 16:50:39 +01:00
|
|
|
|
private var end: Duration = Duration.Undefined
|
2011-06-26 17:40:30 +02:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* if last assertion was expectNoMsg, disable timing failure upon within()
|
|
|
|
|
|
* block end.
|
2011-01-04 13:50:50 +01:00
|
|
|
|
*/
|
2011-06-26 17:40:30 +02:00
|
|
|
|
private var lastWasNoMsg = false
|
2011-01-04 13:50:50 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Ignore all messages in the test actor for which the given partial
|
|
|
|
|
|
* function returns true.
|
|
|
|
|
|
*/
|
2011-05-18 17:25:30 +02:00
|
|
|
|
def ignoreMsg(f: PartialFunction[AnyRef, Boolean]) { testActor ! TestActor.SetIgnore(Some(f)) }
|
2011-01-04 13:50:50 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Stop ignoring messages in the test actor.
|
|
|
|
|
|
*/
|
2011-09-28 12:57:33 +02:00
|
|
|
|
def ignoreNoMsg() { testActor ! TestActor.SetIgnore(None) }
|
2011-01-04 13:50:50 +01:00
|
|
|
|
|
2011-12-13 15:41:00 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Have the testActor watch someone (i.e. `context.watch(...)`). Waits until
|
|
|
|
|
|
* the Watch message is received back using expectMsg.
|
|
|
|
|
|
*/
|
|
|
|
|
|
def watch(ref: ActorRef) {
|
|
|
|
|
|
val msg = TestActor.Watch(ref)
|
|
|
|
|
|
testActor ! msg
|
|
|
|
|
|
expectMsg(msg)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Have the testActor stop watching someone (i.e. `context.unwatch(...)`). Waits until
|
|
|
|
|
|
* the Watch message is received back using expectMsg.
|
|
|
|
|
|
*/
|
|
|
|
|
|
def unwatch(ref: ActorRef) {
|
|
|
|
|
|
val msg = TestActor.UnWatch(ref)
|
|
|
|
|
|
testActor ! msg
|
|
|
|
|
|
expectMsg(msg)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2012-02-19 00:09:04 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Install an AutoPilot to drive the testActor: the AutoPilot will be run
|
|
|
|
|
|
* for each received message and can be used to send or forward messages,
|
|
|
|
|
|
* etc. Each invocation must return the AutoPilot for the next round.
|
|
|
|
|
|
*/
|
|
|
|
|
|
def setAutoPilot(pilot: TestActor.AutoPilot): Unit = testActor ! TestActor.SetAutoPilot(pilot)
|
|
|
|
|
|
|
2011-01-04 13:50:50 +01:00
|
|
|
|
/**
|
2011-05-29 20:18:07 +02:00
|
|
|
|
* Obtain current time (`System.nanoTime`) as Duration.
|
2011-01-04 13:50:50 +01:00
|
|
|
|
*/
|
2011-05-18 17:25:30 +02:00
|
|
|
|
def now: Duration = System.nanoTime.nanos
|
2011-01-04 13:50:50 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
2011-11-11 16:50:39 +01:00
|
|
|
|
* Obtain time remaining for execution of the innermost enclosing `within`
|
|
|
|
|
|
* block or missing that it returns the properly dilated default for this
|
2011-11-17 11:51:14 +01:00
|
|
|
|
* case from settings (key "akka.test.single-expect-default").
|
2011-01-04 13:50:50 +01:00
|
|
|
|
*/
|
2012-06-13 13:52:58 +02:00
|
|
|
|
def remaining: Duration = remainingOr(testKitSettings.SingleExpectDefaultTimeout.dilated)
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Obtain time remaining for execution of the innermost enclosing `within`
|
|
|
|
|
|
* block or missing that it returns the given duration.
|
|
|
|
|
|
*/
|
|
|
|
|
|
def remainingOr(duration: Duration): Duration = if (end == Duration.Undefined) duration else end - now
|
2011-01-04 13:50:50 +01:00
|
|
|
|
|
2011-06-02 22:54:38 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Query queue status.
|
|
|
|
|
|
*/
|
|
|
|
|
|
def msgAvailable = !queue.isEmpty
|
|
|
|
|
|
|
2011-06-17 22:19:17 +02:00
|
|
|
|
/**
|
2011-12-12 22:50:08 +01:00
|
|
|
|
* Await until the given condition evaluates to `true` or the timeout
|
2011-06-17 22:19:17 +02:00
|
|
|
|
* expires, whichever comes first.
|
|
|
|
|
|
*
|
|
|
|
|
|
* If no timeout is given, take it from the innermost enclosing `within`
|
|
|
|
|
|
* block.
|
|
|
|
|
|
*
|
2011-11-22 13:04:10 +01:00
|
|
|
|
* Note that the timeout is scaled using Duration.dilated,
|
|
|
|
|
|
* which uses the configuration entry "akka.test.timefactor".
|
2011-06-17 22:19:17 +02:00
|
|
|
|
*/
|
2011-11-11 16:50:39 +01:00
|
|
|
|
def awaitCond(p: ⇒ Boolean, max: Duration = Duration.Undefined, interval: Duration = 100.millis) {
|
|
|
|
|
|
val _max = if (max eq Duration.Undefined) remaining else max.dilated
|
2011-06-17 22:19:17 +02:00
|
|
|
|
val stop = now + _max
|
|
|
|
|
|
|
|
|
|
|
|
@tailrec
|
|
|
|
|
|
def poll(t: Duration) {
|
|
|
|
|
|
if (!p) {
|
|
|
|
|
|
assert(now < stop, "timeout " + _max + " expired")
|
|
|
|
|
|
Thread.sleep(t.toMillis)
|
|
|
|
|
|
poll((stop - now) min interval)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
poll(_max min interval)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2011-01-04 13:50:50 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Execute code block while bounding its execution time between `min` and
|
|
|
|
|
|
* `max`. `within` blocks may be nested. All methods in this trait which
|
|
|
|
|
|
* take maximum wait times are available in a version which implicitly uses
|
|
|
|
|
|
* the remaining time governed by the innermost enclosing `within` block.
|
|
|
|
|
|
*
|
2011-11-22 13:04:10 +01:00
|
|
|
|
* Note that the timeout is scaled using Duration.dilated, which uses the
|
|
|
|
|
|
* configuration entry "akka.test.timefactor", while the min Duration is not.
|
2011-06-17 22:19:17 +02:00
|
|
|
|
*
|
2011-01-04 13:50:50 +01:00
|
|
|
|
* <pre>
|
|
|
|
|
|
* val ret = within(50 millis) {
|
|
|
|
|
|
* test ! "ping"
|
|
|
|
|
|
* expectMsgClass(classOf[String])
|
|
|
|
|
|
* }
|
|
|
|
|
|
* </pre>
|
|
|
|
|
|
*/
|
2011-05-18 17:25:30 +02:00
|
|
|
|
def within[T](min: Duration, max: Duration)(f: ⇒ T): T = {
|
2011-06-17 22:19:17 +02:00
|
|
|
|
val _max = max.dilated
|
2011-01-04 13:50:50 +01:00
|
|
|
|
val start = now
|
2011-11-11 16:50:39 +01:00
|
|
|
|
val rem = if (end == Duration.Undefined) Duration.Inf else end - start
|
2011-05-18 17:25:30 +02:00
|
|
|
|
assert(rem >= min, "required min time " + min + " not possible, only " + format(min.unit, rem) + " left")
|
2011-01-04 13:50:50 +01:00
|
|
|
|
|
2011-06-26 17:40:30 +02:00
|
|
|
|
lastWasNoMsg = false
|
|
|
|
|
|
|
2011-06-17 22:19:17 +02:00
|
|
|
|
val max_diff = _max min rem
|
2011-01-04 13:50:50 +01:00
|
|
|
|
val prev_end = end
|
|
|
|
|
|
end = start + max_diff
|
|
|
|
|
|
|
2011-03-27 18:15:40 +02:00
|
|
|
|
val ret = try f finally end = prev_end
|
2011-01-04 13:50:50 +01:00
|
|
|
|
|
|
|
|
|
|
val diff = now - start
|
2011-05-18 17:25:30 +02:00
|
|
|
|
assert(min <= diff, "block took " + format(min.unit, diff) + ", should at least have been " + min)
|
2011-06-26 17:40:30 +02:00
|
|
|
|
if (!lastWasNoMsg) {
|
2011-06-17 22:19:17 +02:00
|
|
|
|
assert(diff <= max_diff, "block took " + format(_max.unit, diff) + ", exceeding " + format(_max.unit, max_diff))
|
2010-12-26 22:49:40 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2011-01-04 13:50:50 +01:00
|
|
|
|
ret
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Same as calling `within(0 seconds, max)(f)`.
|
|
|
|
|
|
*/
|
2011-05-18 17:25:30 +02:00
|
|
|
|
def within[T](max: Duration)(f: ⇒ T): T = within(0 seconds, max)(f)
|
2011-01-04 13:50:50 +01:00
|
|
|
|
|
2012-06-25 19:30:13 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Java API for within():
|
|
|
|
|
|
*
|
|
|
|
|
|
* {{{
|
|
|
|
|
|
* new Within(Duration.parse("3 seconds")) {
|
|
|
|
|
|
* public void run() {
|
|
|
|
|
|
* // your test code here
|
|
|
|
|
|
* }
|
|
|
|
|
|
* }
|
|
|
|
|
|
* }}}
|
|
|
|
|
|
*/
|
|
|
|
|
|
abstract class Within(max: Duration) {
|
|
|
|
|
|
def run(): Unit
|
|
|
|
|
|
within(max)(run())
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2011-01-04 13:50:50 +01:00
|
|
|
|
/**
|
2011-06-17 22:19:17 +02:00
|
|
|
|
* Same as `expectMsg(remaining, obj)`, but correctly treating the timeFactor.
|
2011-01-04 13:50:50 +01:00
|
|
|
|
*/
|
2011-05-29 20:18:07 +02:00
|
|
|
|
def expectMsg[T](obj: T): T = expectMsg_internal(remaining, obj)
|
2011-01-04 13:50:50 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Receive one message from the test actor and assert that it equals the
|
|
|
|
|
|
* given object. Wait time is bounded by the given duration, with an
|
|
|
|
|
|
* AssertionFailure being thrown in case of timeout.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @return the received object
|
|
|
|
|
|
*/
|
2011-05-29 20:18:07 +02:00
|
|
|
|
def expectMsg[T](max: Duration, obj: T): T = expectMsg_internal(max.dilated, obj)
|
2011-06-17 22:19:17 +02:00
|
|
|
|
|
2011-05-29 20:18:07 +02:00
|
|
|
|
private def expectMsg_internal[T](max: Duration, obj: T): T = {
|
2011-01-04 13:50:50 +01:00
|
|
|
|
val o = receiveOne(max)
|
2011-11-11 16:50:39 +01:00
|
|
|
|
assert(o ne null, "timeout (" + max + ") during expectMsg while waiting for " + obj)
|
2011-05-18 17:25:30 +02:00
|
|
|
|
assert(obj == o, "expected " + obj + ", found " + o)
|
2011-05-29 20:18:07 +02:00
|
|
|
|
o.asInstanceOf[T]
|
2011-01-04 13:50:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Receive one message from the test actor and assert that the given
|
|
|
|
|
|
* partial function accepts it. Wait time is bounded by the given duration,
|
|
|
|
|
|
* with an AssertionFailure being thrown in case of timeout.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Use this variant to implement more complicated or conditional
|
|
|
|
|
|
* processing.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @return the received object as transformed by the partial function
|
|
|
|
|
|
*/
|
2011-11-11 16:50:39 +01:00
|
|
|
|
def expectMsgPF[T](max: Duration = Duration.Undefined, hint: String = "")(f: PartialFunction[Any, T]): T = {
|
|
|
|
|
|
val _max = if (max eq Duration.Undefined) remaining else max.dilated
|
2011-05-25 00:17:15 +02:00
|
|
|
|
val o = receiveOne(_max)
|
2011-11-11 18:41:43 +01:00
|
|
|
|
assert(o ne null, "timeout (" + _max + ") during expectMsg: " + hint)
|
2011-10-18 12:40:44 +02:00
|
|
|
|
assert(f.isDefinedAt(o), "expected: " + hint + " but got unexpected message " + o)
|
2011-01-04 13:50:50 +01:00
|
|
|
|
f(o)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2011-10-20 13:43:19 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Hybrid of expectMsgPF and receiveWhile: receive messages while the
|
|
|
|
|
|
* partial function matches and returns false. Use it to ignore certain
|
|
|
|
|
|
* messages while waiting for a specific message.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @return the last received messsage, i.e. the first one for which the
|
|
|
|
|
|
* partial function returned true
|
|
|
|
|
|
*/
|
2011-11-11 16:50:39 +01:00
|
|
|
|
def fishForMessage(max: Duration = Duration.Undefined, hint: String = "")(f: PartialFunction[Any, Boolean]): Any = {
|
|
|
|
|
|
val _max = if (max eq Duration.Undefined) remaining else max.dilated
|
2011-10-20 13:43:19 +02:00
|
|
|
|
val end = now + _max
|
|
|
|
|
|
@tailrec
|
|
|
|
|
|
def recv: Any = {
|
|
|
|
|
|
val o = receiveOne(end - now)
|
2011-11-11 18:41:43 +01:00
|
|
|
|
assert(o ne null, "timeout (" + _max + ") during fishForMessage, hint: " + hint)
|
2011-10-20 13:43:19 +02:00
|
|
|
|
assert(f.isDefinedAt(o), "fishForMessage(" + hint + ") found unexpected message " + o)
|
|
|
|
|
|
if (f(o)) o else recv
|
|
|
|
|
|
}
|
|
|
|
|
|
recv
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2011-06-26 17:40:30 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Same as `expectMsgType[T](remaining)`, but correctly treating the timeFactor.
|
|
|
|
|
|
*/
|
|
|
|
|
|
def expectMsgType[T](implicit m: Manifest[T]): T = expectMsgClass_internal(remaining, m.erasure.asInstanceOf[Class[T]])
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Receive one message from the test actor and assert that it conforms to the
|
|
|
|
|
|
* given type (after erasure). Wait time is bounded by the given duration,
|
|
|
|
|
|
* with an AssertionFailure being thrown in case of timeout.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @return the received object
|
|
|
|
|
|
*/
|
|
|
|
|
|
def expectMsgType[T](max: Duration)(implicit m: Manifest[T]): T = expectMsgClass_internal(max.dilated, m.erasure.asInstanceOf[Class[T]])
|
|
|
|
|
|
|
2011-01-04 13:50:50 +01:00
|
|
|
|
/**
|
2011-06-17 22:19:17 +02:00
|
|
|
|
* Same as `expectMsgClass(remaining, c)`, but correctly treating the timeFactor.
|
2011-01-04 13:50:50 +01:00
|
|
|
|
*/
|
2011-06-17 22:19:17 +02:00
|
|
|
|
def expectMsgClass[C](c: Class[C]): C = expectMsgClass_internal(remaining, c)
|
2011-01-04 13:50:50 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Receive one message from the test actor and assert that it conforms to
|
|
|
|
|
|
* the given class. Wait time is bounded by the given duration, with an
|
|
|
|
|
|
* AssertionFailure being thrown in case of timeout.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @return the received object
|
|
|
|
|
|
*/
|
2011-06-17 22:19:17 +02:00
|
|
|
|
def expectMsgClass[C](max: Duration, c: Class[C]): C = expectMsgClass_internal(max.dilated, c)
|
|
|
|
|
|
|
|
|
|
|
|
private def expectMsgClass_internal[C](max: Duration, c: Class[C]): C = {
|
2011-01-04 13:50:50 +01:00
|
|
|
|
val o = receiveOne(max)
|
2011-11-11 16:50:39 +01:00
|
|
|
|
assert(o ne null, "timeout (" + max + ") during expectMsgClass waiting for " + c)
|
2012-05-15 21:12:46 +02:00
|
|
|
|
assert(BoxedType(c) isInstance o, "expected " + c + ", found " + o.getClass)
|
2011-01-04 13:50:50 +01:00
|
|
|
|
o.asInstanceOf[C]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2011-06-17 22:19:17 +02:00
|
|
|
|
* Same as `expectMsgAnyOf(remaining, obj...)`, but correctly treating the timeFactor.
|
2011-01-04 13:50:50 +01:00
|
|
|
|
*/
|
2011-05-29 20:18:07 +02:00
|
|
|
|
def expectMsgAnyOf[T](obj: T*): T = expectMsgAnyOf_internal(remaining, obj: _*)
|
2011-01-04 13:50:50 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Receive one message from the test actor and assert that it equals one of
|
|
|
|
|
|
* the given objects. Wait time is bounded by the given duration, with an
|
|
|
|
|
|
* AssertionFailure being thrown in case of timeout.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @return the received object
|
|
|
|
|
|
*/
|
2011-05-29 20:18:07 +02:00
|
|
|
|
def expectMsgAnyOf[T](max: Duration, obj: T*): T = expectMsgAnyOf_internal(max.dilated, obj: _*)
|
2011-06-17 22:19:17 +02:00
|
|
|
|
|
2011-05-29 20:18:07 +02:00
|
|
|
|
private def expectMsgAnyOf_internal[T](max: Duration, obj: T*): T = {
|
2011-01-04 13:50:50 +01:00
|
|
|
|
val o = receiveOne(max)
|
2011-11-11 16:50:39 +01:00
|
|
|
|
assert(o ne null, "timeout (" + max + ") during expectMsgAnyOf waiting for " + obj.mkString("(", ", ", ")"))
|
2011-05-18 17:25:30 +02:00
|
|
|
|
assert(obj exists (_ == o), "found unexpected " + o)
|
2011-05-29 20:18:07 +02:00
|
|
|
|
o.asInstanceOf[T]
|
2011-01-04 13:50:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2011-06-17 22:19:17 +02:00
|
|
|
|
* Same as `expectMsgAnyClassOf(remaining, obj...)`, but correctly treating the timeFactor.
|
2011-01-04 13:50:50 +01:00
|
|
|
|
*/
|
2011-05-29 20:18:07 +02:00
|
|
|
|
def expectMsgAnyClassOf[C](obj: Class[_ <: C]*): C = expectMsgAnyClassOf_internal(remaining, obj: _*)
|
2011-01-04 13:50:50 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Receive one message from the test actor and assert that it conforms to
|
|
|
|
|
|
* one of the given classes. Wait time is bounded by the given duration,
|
|
|
|
|
|
* with an AssertionFailure being thrown in case of timeout.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @return the received object
|
|
|
|
|
|
*/
|
2011-05-29 20:18:07 +02:00
|
|
|
|
def expectMsgAnyClassOf[C](max: Duration, obj: Class[_ <: C]*): C = expectMsgAnyClassOf_internal(max.dilated, obj: _*)
|
2011-06-17 22:19:17 +02:00
|
|
|
|
|
2011-05-29 20:18:07 +02:00
|
|
|
|
private def expectMsgAnyClassOf_internal[C](max: Duration, obj: Class[_ <: C]*): C = {
|
2011-01-04 13:50:50 +01:00
|
|
|
|
val o = receiveOne(max)
|
2011-11-11 16:50:39 +01:00
|
|
|
|
assert(o ne null, "timeout (" + max + ") during expectMsgAnyClassOf waiting for " + obj.mkString("(", ", ", ")"))
|
2012-05-15 21:12:46 +02:00
|
|
|
|
assert(obj exists (c ⇒ BoxedType(c) isInstance o), "found unexpected " + o)
|
2011-05-29 20:18:07 +02:00
|
|
|
|
o.asInstanceOf[C]
|
2011-01-04 13:50:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2011-06-17 22:19:17 +02:00
|
|
|
|
* Same as `expectMsgAllOf(remaining, obj...)`, but correctly treating the timeFactor.
|
2011-01-04 13:50:50 +01:00
|
|
|
|
*/
|
2011-05-29 20:18:07 +02:00
|
|
|
|
def expectMsgAllOf[T](obj: T*): Seq[T] = expectMsgAllOf_internal(remaining, obj: _*)
|
2011-01-04 13:50:50 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 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
|
2011-05-29 20:18:07 +02:00
|
|
|
|
* 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.
|
2011-01-04 13:50:50 +01:00
|
|
|
|
*
|
|
|
|
|
|
* <pre>
|
|
|
|
|
|
* dispatcher ! SomeWork1()
|
|
|
|
|
|
* dispatcher ! SomeWork2()
|
2011-07-16 21:30:08 -04:00
|
|
|
|
* expectMsgAllOf(1 second, Result1(), Result2())
|
2011-01-04 13:50:50 +01:00
|
|
|
|
* </pre>
|
|
|
|
|
|
*/
|
2011-05-29 20:18:07 +02:00
|
|
|
|
def expectMsgAllOf[T](max: Duration, obj: T*): Seq[T] = expectMsgAllOf_internal(max.dilated, obj: _*)
|
2011-06-17 22:19:17 +02:00
|
|
|
|
|
2011-05-29 20:18:07 +02:00
|
|
|
|
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]]
|
2011-01-04 13:50:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2011-06-17 22:19:17 +02:00
|
|
|
|
* Same as `expectMsgAllClassOf(remaining, obj...)`, but correctly treating the timeFactor.
|
2011-01-04 13:50:50 +01:00
|
|
|
|
*/
|
2011-05-29 20:18:07 +02:00
|
|
|
|
def expectMsgAllClassOf[T](obj: Class[_ <: T]*): Seq[T] = expectMsgAllClassOf_internal(remaining, obj: _*)
|
2011-01-04 13:50:50 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 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 is of that class (equality, not conformance). 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.
|
|
|
|
|
|
*/
|
2011-05-29 20:18:07 +02:00
|
|
|
|
def expectMsgAllClassOf[T](max: Duration, obj: Class[_ <: T]*): Seq[T] = expectMsgAllClassOf_internal(max.dilated, obj: _*)
|
2011-06-17 22:19:17 +02:00
|
|
|
|
|
2011-05-29 20:18:07 +02:00
|
|
|
|
private def expectMsgAllClassOf_internal[T](max: Duration, obj: Class[_ <: T]*): Seq[T] = {
|
|
|
|
|
|
val recv = receiveN_internal(obj.size, max)
|
2012-05-15 21:12:46 +02:00
|
|
|
|
obj foreach (x ⇒ assert(recv exists (_.getClass eq BoxedType(x)), "not found " + x))
|
|
|
|
|
|
recv foreach (x ⇒ assert(obj exists (c ⇒ BoxedType(c) eq x.getClass), "found non-matching object " + x))
|
2011-05-29 20:18:07 +02:00
|
|
|
|
recv.asInstanceOf[Seq[T]]
|
2011-01-04 13:50:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2011-06-17 22:19:17 +02:00
|
|
|
|
* Same as `expectMsgAllConformingOf(remaining, obj...)`, but correctly treating the timeFactor.
|
2011-01-04 13:50:50 +01:00
|
|
|
|
*/
|
2011-05-29 20:18:07 +02:00
|
|
|
|
def expectMsgAllConformingOf[T](obj: Class[_ <: T]*): Seq[T] = expectMsgAllClassOf_internal(remaining, obj: _*)
|
2011-01-04 13:50:50 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 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
|
2011-05-29 20:18:07 +02:00
|
|
|
|
* 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.
|
2011-01-04 13:50:50 +01:00
|
|
|
|
*
|
|
|
|
|
|
* Beware that one object may satisfy all given class constraints, which
|
|
|
|
|
|
* may be counter-intuitive.
|
|
|
|
|
|
*/
|
2011-05-29 20:18:07 +02:00
|
|
|
|
def expectMsgAllConformingOf[T](max: Duration, obj: Class[_ <: T]*): Seq[T] = expectMsgAllConformingOf(max.dilated, obj: _*)
|
2011-06-17 22:19:17 +02:00
|
|
|
|
|
2011-05-29 20:18:07 +02:00
|
|
|
|
private def expectMsgAllConformingOf_internal[T](max: Duration, obj: Class[_ <: T]*): Seq[T] = {
|
|
|
|
|
|
val recv = receiveN_internal(obj.size, max)
|
2012-05-15 21:12:46 +02:00
|
|
|
|
obj foreach (x ⇒ assert(recv exists (BoxedType(x) isInstance _), "not found " + x))
|
|
|
|
|
|
recv foreach (x ⇒ assert(obj exists (c ⇒ BoxedType(c) isInstance x), "found non-matching object " + x))
|
2011-05-29 20:18:07 +02:00
|
|
|
|
recv.asInstanceOf[Seq[T]]
|
2011-01-04 13:50:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2011-06-17 22:19:17 +02:00
|
|
|
|
* Same as `expectNoMsg(remaining)`, but correctly treating the timeFactor.
|
2011-01-04 13:50:50 +01:00
|
|
|
|
*/
|
2011-09-28 12:57:33 +02:00
|
|
|
|
def expectNoMsg() { expectNoMsg_internal(remaining) }
|
2011-01-04 13:50:50 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Assert that no message is received for the specified time.
|
|
|
|
|
|
*/
|
2011-06-17 22:19:17 +02:00
|
|
|
|
def expectNoMsg(max: Duration) { expectNoMsg_internal(max.dilated) }
|
|
|
|
|
|
|
|
|
|
|
|
private def expectNoMsg_internal(max: Duration) {
|
2011-01-04 13:50:50 +01:00
|
|
|
|
val o = receiveOne(max)
|
2011-05-18 17:25:30 +02:00
|
|
|
|
assert(o eq null, "received unexpected message " + o)
|
2011-06-26 17:40:30 +02:00
|
|
|
|
lastWasNoMsg = true
|
2011-01-04 13:50:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2011-05-29 20:18:07 +02:00
|
|
|
|
* 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.
|
2011-01-04 13:50:50 +01:00
|
|
|
|
*
|
2011-06-17 22:19:17 +02:00
|
|
|
|
* Note that it is not an error to hit the `max` duration in this case.
|
2011-01-04 13:50:50 +01:00
|
|
|
|
*
|
|
|
|
|
|
* One possible use of this method is for testing whether messages of
|
|
|
|
|
|
* certain characteristics are generated at a certain rate:
|
|
|
|
|
|
*
|
|
|
|
|
|
* <pre>
|
|
|
|
|
|
* test ! ScheduleTicks(100 millis)
|
|
|
|
|
|
* val series = receiveWhile(750 millis) {
|
|
|
|
|
|
* case Tick(count) => count
|
|
|
|
|
|
* }
|
|
|
|
|
|
* assert(series == (1 to 7).toList)
|
|
|
|
|
|
* </pre>
|
|
|
|
|
|
*/
|
2011-11-11 16:50:39 +01:00
|
|
|
|
def receiveWhile[T](max: Duration = Duration.Undefined, idle: Duration = Duration.Inf, messages: Int = Int.MaxValue)(f: PartialFunction[AnyRef, T]): Seq[T] = {
|
|
|
|
|
|
val stop = now + (if (max eq Duration.Undefined) remaining else max.dilated)
|
2011-06-13 22:36:46 +02:00
|
|
|
|
var msg: Message = NullMessage
|
2011-01-04 13:50:50 +01:00
|
|
|
|
|
2011-05-18 17:25:30 +02:00
|
|
|
|
@tailrec
|
2011-10-21 15:11:43 +02:00
|
|
|
|
def doit(acc: List[T], count: Int): List[T] = {
|
2012-06-13 11:53:27 +02:00
|
|
|
|
if (count >= messages) acc.reverse
|
|
|
|
|
|
else {
|
|
|
|
|
|
receiveOne((stop - now) min idle)
|
|
|
|
|
|
lastMessage match {
|
|
|
|
|
|
case NullMessage ⇒
|
|
|
|
|
|
lastMessage = msg
|
|
|
|
|
|
acc.reverse
|
|
|
|
|
|
case RealMessage(o, _) if (f isDefinedAt o) ⇒
|
|
|
|
|
|
msg = lastMessage
|
|
|
|
|
|
doit(f(o) :: acc, count + 1)
|
|
|
|
|
|
case RealMessage(o, _) ⇒
|
|
|
|
|
|
queue.offerFirst(lastMessage)
|
|
|
|
|
|
lastMessage = msg
|
|
|
|
|
|
acc.reverse
|
|
|
|
|
|
}
|
2011-01-04 13:50:50 +01:00
|
|
|
|
}
|
2010-12-26 22:49:40 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2011-10-21 15:11:43 +02:00
|
|
|
|
val ret = doit(Nil, 0)
|
2011-06-26 17:40:30 +02:00
|
|
|
|
lastWasNoMsg = true
|
2011-01-04 13:50:50 +01:00
|
|
|
|
ret
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2011-05-29 20:18:07 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Same as `receiveN(n, remaining)` but correctly taking into account
|
|
|
|
|
|
* Duration.timeFactor.
|
|
|
|
|
|
*/
|
|
|
|
|
|
def receiveN(n: Int): Seq[AnyRef] = receiveN_internal(n, remaining)
|
|
|
|
|
|
|
2011-06-13 22:36:46 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Receive N messages in a row before the given deadline.
|
|
|
|
|
|
*/
|
2011-05-29 20:18:07 +02:00
|
|
|
|
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
|
2011-05-18 17:25:30 +02:00
|
|
|
|
for { x ← 1 to n } yield {
|
2011-01-04 13:50:50 +01:00
|
|
|
|
val timeout = stop - now
|
|
|
|
|
|
val o = receiveOne(timeout)
|
2011-11-11 16:50:39 +01:00
|
|
|
|
assert(o ne null, "timeout (" + max + ") while expecting " + n + " messages")
|
2011-01-04 13:50:50 +01:00
|
|
|
|
o
|
2010-12-28 23:51:41 +01:00
|
|
|
|
}
|
2011-01-04 13:50:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2011-06-13 22:36:46 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Receive one message from the internal queue of the TestActor. If the given
|
|
|
|
|
|
* duration is zero, the queue is polled (non-blocking).
|
2011-06-17 22:19:17 +02:00
|
|
|
|
*
|
|
|
|
|
|
* This method does NOT automatically scale its Duration parameter!
|
2011-06-13 22:36:46 +02:00
|
|
|
|
*/
|
|
|
|
|
|
def receiveOne(max: Duration): AnyRef = {
|
|
|
|
|
|
val message =
|
|
|
|
|
|
if (max == 0.seconds) {
|
|
|
|
|
|
queue.pollFirst
|
|
|
|
|
|
} else if (max.finite_?) {
|
|
|
|
|
|
queue.pollFirst(max.length, max.unit)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
queue.takeFirst
|
|
|
|
|
|
}
|
2011-06-26 17:40:30 +02:00
|
|
|
|
lastWasNoMsg = false
|
2011-06-13 22:36:46 +02:00
|
|
|
|
message match {
|
|
|
|
|
|
case null ⇒
|
|
|
|
|
|
lastMessage = NullMessage
|
|
|
|
|
|
null
|
|
|
|
|
|
case RealMessage(msg, _) ⇒
|
|
|
|
|
|
lastMessage = message
|
|
|
|
|
|
msg
|
2010-12-28 23:51:41 +01:00
|
|
|
|
}
|
2011-01-04 13:50:50 +01:00
|
|
|
|
}
|
2010-12-31 17:57:08 +01:00
|
|
|
|
|
2011-05-18 17:25:30 +02:00
|
|
|
|
private def format(u: TimeUnit, d: Duration) = "%.3f %s".format(d.toUnit(u), u.toString.toLowerCase)
|
2010-12-26 22:49:40 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2012-06-04 19:28:58 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* 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 `expectMsg...` methods. Assertions and bounds concerning
|
|
|
|
|
|
* timing are available in the form of `within` blocks.
|
|
|
|
|
|
*
|
|
|
|
|
|
* <pre>
|
|
|
|
|
|
* class Test extends TestKit(ActorSystem()) {
|
|
|
|
|
|
* try {
|
|
|
|
|
|
*
|
|
|
|
|
|
* val test = system.actorOf(Props[SomeActor]
|
|
|
|
|
|
*
|
|
|
|
|
|
* within (1 second) {
|
|
|
|
|
|
* test ! SomeWork
|
|
|
|
|
|
* expectMsg(Result1) // bounded to 1 second
|
|
|
|
|
|
* expectMsg(Result2) // bounded to the remainder of the 1 second
|
|
|
|
|
|
* }
|
|
|
|
|
|
*
|
|
|
|
|
|
* } finally {
|
|
|
|
|
|
* system.shutdown()
|
|
|
|
|
|
* }
|
|
|
|
|
|
* }
|
|
|
|
|
|
* </pre>
|
|
|
|
|
|
*
|
|
|
|
|
|
* Beware of two points:
|
|
|
|
|
|
*
|
|
|
|
|
|
* - the ActorSystem passed into the constructor needs to be shutdown,
|
|
|
|
|
|
* otherwise thread pools and memory will be leaked
|
|
|
|
|
|
* - 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 instance in parallel.
|
|
|
|
|
|
*
|
|
|
|
|
|
* It should be noted that for CI servers and the like all maximum Durations
|
|
|
|
|
|
* are scaled using their Duration.dilated method, which uses the
|
|
|
|
|
|
* TestKitExtension.Settings.TestTimeFactor settable via akka.conf entry "akka.test.timefactor".
|
|
|
|
|
|
*
|
|
|
|
|
|
* @author Roland Kuhn
|
|
|
|
|
|
* @since 1.1
|
|
|
|
|
|
*/
|
2012-06-04 10:03:41 +02:00
|
|
|
|
class TestKit(_system: ActorSystem) extends { implicit val system = _system } with TestKitBase
|
|
|
|
|
|
|
2011-06-05 10:45:27 +02:00
|
|
|
|
object TestKit {
|
|
|
|
|
|
private[testkit] val testActorId = new AtomicInteger(0)
|
2011-11-06 11:55:45 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
2011-12-12 22:50:08 +01:00
|
|
|
|
* Await until the given condition evaluates to `true` or the timeout
|
2011-11-06 11:55:45 +01:00
|
|
|
|
* expires, whichever comes first.
|
|
|
|
|
|
*/
|
2011-11-09 11:04:39 +01:00
|
|
|
|
def awaitCond(p: ⇒ Boolean, max: Duration, interval: Duration = 100.millis, noThrow: Boolean = false): Boolean = {
|
2011-11-06 11:55:45 +01:00
|
|
|
|
val stop = now + max
|
|
|
|
|
|
|
|
|
|
|
|
@tailrec
|
2011-11-09 11:04:39 +01:00
|
|
|
|
def poll(): Boolean = {
|
2011-11-06 11:55:45 +01:00
|
|
|
|
if (!p) {
|
2011-11-09 11:04:39 +01:00
|
|
|
|
val toSleep = stop - now
|
|
|
|
|
|
if (toSleep <= Duration.Zero) {
|
|
|
|
|
|
if (noThrow) false
|
|
|
|
|
|
else throw new AssertionError("timeout " + max + " expired")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Thread.sleep((toSleep min interval).toMillis)
|
|
|
|
|
|
poll()
|
|
|
|
|
|
}
|
|
|
|
|
|
} else true
|
2011-11-06 11:55:45 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2011-11-09 11:04:39 +01:00
|
|
|
|
poll()
|
2011-11-06 11:55:45 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Obtain current timestamp as Duration for relative measurements (using System.nanoTime).
|
|
|
|
|
|
*/
|
|
|
|
|
|
def now: Duration = System.nanoTime().nanos
|
|
|
|
|
|
|
2011-11-22 13:04:10 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Java API. Scale timeouts (durations) during tests with the configured
|
|
|
|
|
|
* 'akka.test.timefactor'.
|
|
|
|
|
|
*/
|
2011-11-24 18:53:18 +01:00
|
|
|
|
def dilated(duration: Duration, system: ActorSystem): Duration =
|
|
|
|
|
|
duration * TestKitExtension(system).TestTimeFactor
|
2011-06-05 10:45:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2011-06-13 22:36:46 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* TestKit-based probe which allows sending, reception and reply.
|
|
|
|
|
|
*/
|
2011-11-10 20:08:00 +01:00
|
|
|
|
class TestProbe(_application: ActorSystem) extends TestKit(_application) {
|
2011-06-13 22:36:46 +02:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Shorthand to get the testActor.
|
|
|
|
|
|
*/
|
|
|
|
|
|
def ref = testActor
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Send message to an actor while using the probe's TestActor as the sender.
|
|
|
|
|
|
* Replies will be available for inspection with all of TestKit's assertion
|
|
|
|
|
|
* methods.
|
|
|
|
|
|
*/
|
2012-06-04 11:29:56 +02:00
|
|
|
|
def send(actor: ActorRef, msg: Any): Unit = actor.!(msg)(testActor)
|
2011-06-13 22:36:46 +02:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Forward this message as if in the TestActor's receive method with self.forward.
|
|
|
|
|
|
*/
|
2012-06-04 11:29:56 +02:00
|
|
|
|
def forward(actor: ActorRef, msg: Any = lastMessage.msg): Unit = actor.!(msg)(lastMessage.sender)
|
2011-06-13 22:36:46 +02:00
|
|
|
|
|
|
|
|
|
|
/**
|
2011-10-22 16:06:20 +02:00
|
|
|
|
* Get sender of last received message.
|
2011-06-13 22:36:46 +02:00
|
|
|
|
*/
|
2011-10-22 16:06:20 +02:00
|
|
|
|
def sender = lastMessage.sender
|
2011-06-13 22:36:46 +02:00
|
|
|
|
|
2012-06-04 11:29:56 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Send message to the sender of the last dequeued message.
|
|
|
|
|
|
*/
|
|
|
|
|
|
def reply(msg: Any): Unit = sender.!(msg)(ref)
|
|
|
|
|
|
|
2011-06-13 22:36:46 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
object TestProbe {
|
2011-11-17 12:36:35 +01:00
|
|
|
|
def apply()(implicit system: ActorSystem) = new TestProbe(system)
|
2011-06-13 22:36:46 +02:00
|
|
|
|
}
|
2011-10-11 16:05:48 +02:00
|
|
|
|
|
|
|
|
|
|
trait ImplicitSender { this: TestKit ⇒
|
2011-11-18 11:59:43 +01:00
|
|
|
|
implicit def self = testActor
|
2011-10-11 16:05:48 +02:00
|
|
|
|
}
|
2011-12-05 20:01:42 +01:00
|
|
|
|
|
|
|
|
|
|
trait DefaultTimeout { this: TestKit ⇒
|
2012-02-10 16:02:37 +01:00
|
|
|
|
implicit val timeout: Timeout = testKitSettings.DefaultTimeout
|
2011-12-05 20:01:42 +01:00
|
|
|
|
}
|