All Downloads are FREE. Search and download functionalities are using the official Maven repository.

akka.testkit.JavaTestKit Maven / Gradle / Ivy

The newest version!
/**
 * Copyright (C) 2009-2014 Typesafe Inc. 
 */
package akka.testkit;

import akka.actor.Terminated;
import scala.runtime.AbstractFunction0;
import akka.actor.ActorRef;
import akka.actor.ActorSystem;
import akka.event.Logging;
import akka.event.Logging.LogEvent;
import akka.japi.JavaPartialFunction;
import akka.japi.Util;
import scala.concurrent.duration.Duration;
import scala.concurrent.duration.FiniteDuration;

import java.util.concurrent.TimeUnit;

/**
 * Java API: Test kit for testing actors. Inheriting from this class 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.
 * 

* * 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); 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 the dilated method, which uses the * TestKitExtension.Settings.TestTimeFactor settable via akka.conf entry * "akka.test.timefactor".
  • *
* * */ public class JavaTestKit { /** * Shut down an actor system and wait for termination. * On failure debug output will be logged about the remaining actors in the system. *

* * If verifySystemShutdown is true, then an exception will be thrown on failure. */ public static void shutdownActorSystem(ActorSystem actorSystem, Duration duration, Boolean verifySystemShutdown) { boolean vss = verifySystemShutdown != null ? verifySystemShutdown : false; Duration dur = duration != null ? duration : FiniteDuration.create(10, TimeUnit.SECONDS); TestKit.shutdownActorSystem(actorSystem, dur, vss); } public static void shutdownActorSystem(ActorSystem actorSystem) { shutdownActorSystem(actorSystem, null, null); } public void shutdownActorSystem(ActorSystem actorSystem, Duration duration) { shutdownActorSystem(actorSystem, duration, null); } public void shutdownActorSystem(ActorSystem actorSystem, Boolean verifySystemShutdown) { shutdownActorSystem(actorSystem, null, verifySystemShutdown); } private final TestProbe p; public JavaTestKit(ActorSystem system) { p = new TestProbe(system); } /** * ActorRef of the test actor. Access is provided to enable e.g. registration * as message target. */ public ActorRef getTestActor() { return p.testActor(); } /** * Shorthand to get the testActor. */ public ActorRef getRef() { return getTestActor(); } public ActorSystem getSystem() { return p.system(); } static public FiniteDuration duration(String s) { final Duration ret = Duration.apply(s); if (ret instanceof FiniteDuration) return (FiniteDuration) ret; else throw new IllegalArgumentException("duration() is only for finite durations, use Duration.Inf() and friends"); } public Duration dilated(Duration d) { return d.mul(TestKitExtension.get(getSystem()).TestTimeFactor()); } /** * Query queue status. */ public boolean msgAvailable() { return p.msgAvailable(); } public ActorRef getLastSender() { return p.lastMessage().sender(); } public void send(ActorRef actor, Object msg) { actor.tell(msg, p.ref()); } public void forward(ActorRef actor) { actor.tell(p.lastMessage().msg(), p.lastMessage().sender()); } public void reply(Object msg) { p.lastMessage().sender().tell(msg, p.ref()); } public FiniteDuration getRemainingTime() { return p.remaining(); } public FiniteDuration getRemainingTimeOr(FiniteDuration def) { return p.remainingOr(def); } /** * Have the testActor watch someone (i.e. * getContext().getWatch(...) ). */ public ActorRef watch(ActorRef ref) { return p.watch(ref); } /** * Have the testActor stop watching someone (i.e. * getContext.unwatch(...)). */ public ActorRef unwatch(ActorRef ref) { return p.unwatch(ref); } /** * Ignore all messages in the test actor for which the given function returns * true. */ public abstract class IgnoreMsg { abstract protected boolean ignore(Object msg); public IgnoreMsg() { p.ignoreMsg(new JavaPartialFunction() { public Boolean apply(Object in, boolean isCheck) { return ignore(in); } }); } } /** * Stop ignoring messages in the test actor. */ public void ignoreNoMsg() { p.ignoreNoMsg(); } /** * 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. */ public void setAutoPilot(TestActor.AutoPilot pilot) { p.setAutoPilot(pilot); } /** * Obtain time remaining for execution of the innermost enclosing * Within block or missing that it returns the properly dilated * default for this case from settings (key * "akka.test.single-expect-default"). */ public FiniteDuration remaining() { return p.remaining(); } /** * Obtain time remaining for execution of the innermost enclosing * Within block or missing that it returns the given duration. */ public FiniteDuration remainingOr(FiniteDuration duration) { return p.remainingOr(duration); } /** * 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. *

* * Note that the timeout is scaled using dilated, which uses the * configuration entry "akka.test.timefactor", while the min Duration is not. *

* *

   * 
   * // the run() method needs to finish within 3 seconds
   * new Within(duration("3 seconds")) {
   *   protected void run() {
   *     // ...
   *   }
   * }
   * 
   * 
*/ public abstract class Within { protected abstract void run(); public Within(FiniteDuration max) { p.within(max, new AbstractFunction0() { public Object apply() { run(); return null; } }); } public Within(FiniteDuration min, FiniteDuration max) { p.within(min, max, new AbstractFunction0() { public Object apply() { run(); return null; } }); } } /** * Await until the given condition evaluates to true or the * timeout expires, whichever comes first. *

* * If no timeout is given, take it from the innermost enclosing * Within block. *

* * Note that the timeout is scaled using Duration.dilated, which uses the * configuration entry "akka.test.timefactor". */ public abstract class AwaitCond { protected abstract boolean cond(); public AwaitCond() { this(Duration.Undefined(), p.awaitCond$default$3()); } public AwaitCond(Duration max) { this(max, p.awaitCond$default$3()); } public AwaitCond(Duration max, Duration interval) { p.awaitCond(new AbstractFunction0() { public Object apply() { return cond(); } }, max, interval, p.awaitCond$default$4()); } public AwaitCond(Duration max, Duration interval, String message) { p.awaitCond(new AbstractFunction0() { public Object apply() { return cond(); } }, max, interval, message); } } /** * Await until the given assert does not throw an exception or the timeout * expires, whichever comes first. If the timeout expires the last exception * is thrown. *

* * If no timeout is given, take it from the innermost enclosing * Within block. *

* * Note that the timeout is scaled using Duration.dilated, which uses the * configuration entry "akka.test.timefactor". */ public abstract class AwaitAssert { protected abstract void check(); public AwaitAssert() { this(Duration.Undefined(), p.awaitAssert$default$3()); } public AwaitAssert(Duration max) { this(max, p.awaitAssert$default$3()); } public AwaitAssert(Duration max, Duration interval) { p.awaitAssert(new AbstractFunction0() { public Object apply() { check(); return null; } }, max, interval); } } /** * Receive one message from the test actor and assert that the given matching * function accepts it. Wait time is bounded by the given duration, with an * AssertionFailure being thrown in case of timeout. *

* The received object as transformed by the matching function can be * retrieved with the get method. * * Use this variant to implement more complicated or conditional processing. *

* *

   * 
   * final String out = new ExpectMsg("match hint") {
   *   protected String match(Object in) {
   *     if (in instanceof Integer)
   *       return "match";
   *     else
   *       throw noMatch();
   *   }
   * }.get(); // this extracts the received message
   * 
   * 
*/ public abstract class ExpectMsg { private final T result; public ExpectMsg(String hint) { this(Duration.Undefined(), hint); } public ExpectMsg(Duration max, String hint) { final Object received = p.receiveOne(max); try { result = match(received); } catch (JavaPartialFunction.NoMatchException ex) { throw new AssertionError("while expecting '" + hint + "' received unexpected: " + received); } } abstract protected T match(Object msg); protected RuntimeException noMatch() { throw JavaPartialFunction.noMatch(); } public T get() { return result; } } /** * Same as expectMsgEquals(remaining(), obj), but correctly * treating the timeFactor. */ public T expectMsgEquals(T msg) { return p.expectMsg(msg); } /** * 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 */ public T expectMsgEquals(FiniteDuration max, T msg) { return p.expectMsg(max, msg); } /** * Same as expectMsgClass(remaining(), clazz), but correctly * treating the timeFactor. */ public T expectMsgClass(Class clazz) { return p.expectMsgClass(clazz); } /** * 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 */ public T expectMsgClass(FiniteDuration max, Class clazz) { return p.expectMsgClass(max, clazz); } /** * Same as expectMsgAnyOf(remaining(), obj...), but correctly * treating the timeFactor. */ public Object expectMsgAnyOf(Object... msgs) { return p.expectMsgAnyOf(Util.immutableSeq(msgs)); } /** * 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 */ public Object expectMsgAnyOf(FiniteDuration max, Object... msgs) { return p.expectMsgAnyOf(max, Util.immutableSeq(msgs)); } /** * Same as expectMsgAllOf(remaining(), obj...), but correctly * treating the timeFactor. */ public Object[] expectMsgAllOf(Object... msgs) { return (Object[]) p.expectMsgAllOf(Util.immutableSeq(msgs)).toArray(Util.classTag(Object.class)); } /** * 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 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. */ public Object[] expectMsgAllOf(FiniteDuration max, Object... msgs) { return (Object[]) p.expectMsgAllOf(max, Util.immutableSeq(msgs)).toArray(Util.classTag(Object.class)); } /** * Same as expectMsgAnyClassOf(remaining(), obj...), but * correctly treating the timeFactor. */ @SuppressWarnings("unchecked") public T expectMsgAnyClassOf(Class... classes) { final Object result = p.expectMsgAnyClassOf(Util.immutableSeq(classes)); return (T) result; } /** * 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 */ public Object expectMsgAnyClassOf(FiniteDuration max, Class... classes) { return p.expectMsgAnyClassOf(max, Util.immutableSeq(classes)); } /** * Same as expectNoMsg(remaining()), but correctly treating the * timeFactor. */ public void expectNoMsg() { p.expectNoMsg(); } /** * Assert that no message is received for the specified time. */ public void expectNoMsg(FiniteDuration max) { p.expectNoMsg(max); } /** * Assert that the given ActorRef is Terminated within the specified time. * Don't forget to 'watch' it first! */ public Terminated expectTerminated(Duration max, ActorRef target) { return p.expectTerminated(target, max); } /** * Same as expectTerminated(remaining(), target), * but correctly treating the timeFactor. * Don't forget to 'watch' it first! */ public Terminated expectTerminated(ActorRef target) { return expectTerminated(Duration.Undefined(), target); } /** * Same as receiveN(n, remaining()), but correctly treating the * timeFactor. */ public Object[] receiveN(int n) { return (Object[]) p.receiveN(n).toArray(Util.classTag(Object.class)); } /** * Receive N messages in a row before the given deadline. */ public Object[] receiveN(int n, FiniteDuration max) { return (Object[]) p.receiveN(n, max).toArray(Util.classTag(Object.class)); } /** * Receive one message from the internal queue of the TestActor. If the given * duration is zero, the queue is polled (non-blocking). *

* * This method does NOT automatically scale its Duration parameter! */ public Object receiveOne(Duration max) { return p.receiveOne(max); } /** * Receive a series of messages until one does not match the given * match 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. *

* * One possible use of this method is for testing whether messages of certain * characteristics are generated at a certain rate. */ public abstract class ReceiveWhile { abstract protected T match(Object msg) throws Exception; private Object results; public ReceiveWhile(Class clazz) { this(clazz, Duration.Undefined()); } public ReceiveWhile(Class clazz, Duration max) { this(clazz, max, Duration.Inf(), Integer.MAX_VALUE); } public ReceiveWhile(Class clazz, Duration max, int messages) { this(clazz, max, Duration.Inf(), messages); } @SuppressWarnings("unchecked") public ReceiveWhile(Class clazz, Duration max, Duration idle, int messages) { results = p.receiveWhile(max, idle, messages, new CachingPartialFunction() { public T match(Object msg) throws Exception { return ReceiveWhile.this.match(msg); } }).toArray(Util.classTag(clazz)); } protected RuntimeException noMatch() { throw JavaPartialFunction.noMatch(); } @SuppressWarnings("unchecked") public T[] get() { return (T[]) results; } } /** * Facilities for selectively filtering out expected events from logging so * that you can keep your test run’s console output clean and do not miss real * error messages. *

* * If the occurrences is set to Integer.MAX_VALUE, * no tracking is done. */ public abstract class EventFilter { abstract protected T run(); private final Class clazz; private String source = null; private String message = null; private boolean pattern = false; private boolean complete = false; private int occurrences = Integer.MAX_VALUE; private Class exceptionType = null; @SuppressWarnings("unchecked") public EventFilter(Class clazz) { if (Throwable.class.isAssignableFrom(clazz)) { this.clazz = Logging.Error.class; exceptionType = (Class) clazz; } else if (Logging.LogEvent.class.isAssignableFrom(clazz)) { this.clazz = (Class) clazz; } else throw new IllegalArgumentException("supplied class must either be LogEvent or Throwable"); } public T exec() { akka.testkit.EventFilter filter; if (clazz == Logging.Error.class) { if (exceptionType == null) exceptionType = Logging.noCause().getClass(); filter = new ErrorFilter(exceptionType, source, message, pattern, complete, occurrences); } else if (clazz == Logging.Warning.class) { filter = new WarningFilter(source, message, pattern, complete, occurrences); } else if (clazz == Logging.Info.class) { filter = new InfoFilter(source, message, pattern, complete, occurrences); } else if (clazz == Logging.Debug.class) { filter = new DebugFilter(source, message, pattern, complete, occurrences); } else throw new IllegalArgumentException("unknown LogLevel " + clazz); return filter.intercept(new AbstractFunction0() { public T apply() { return run(); } }, p.system()); } public EventFilter message(String msg) { message = msg; pattern = false; complete = true; return this; } public EventFilter startsWith(String msg) { message = msg; pattern = false; complete = false; return this; } public EventFilter matches(String regex) { message = regex; pattern = true; return this; } public EventFilter from(String source) { this.source = source; return this; } public EventFilter occurrences(int number) { occurrences = number; return this; } } /** * Shut down an actor system and wait for termination. * On failure debug output will be logged about the remaining actors in the system. *

* * If verifySystemShutdown is true, then an exception will be thrown on failure. */ public void shutdown(ActorSystem actorSystem, Duration duration, Boolean verifySystemShutdown) { boolean vss = verifySystemShutdown != null ? verifySystemShutdown : false; Duration dur = duration != null ? duration : dilated(FiniteDuration.create(5, TimeUnit.SECONDS)).min(FiniteDuration.create(10, TimeUnit.SECONDS)); JavaTestKit.shutdownActorSystem(actorSystem, dur, vss); } public void shutdown(ActorSystem actorSystem) { shutdown(actorSystem, null, null); } public void shutdown(ActorSystem actorSystem, Duration duration) { shutdown(actorSystem, duration, null); } public void shutdown(ActorSystem actorSystem, Boolean verifySystemShutdown) { shutdown(actorSystem, null, verifySystemShutdown); } }