*/
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 extends T>... 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 extends Logging.LogEvent> 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 extends Throwable> exceptionType = null;
@SuppressWarnings("unchecked")
public EventFilter(Class> clazz) {
if (Throwable.class.isAssignableFrom(clazz)) {
this.clazz = Logging.Error.class;
exceptionType = (Class extends Throwable>) clazz;
} else if (Logging.LogEvent.class.isAssignableFrom(clazz)) {
this.clazz = (Class extends LogEvent>) 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);
}
}