Initial pass of typed testkits + documenation (#24187)
* Initial pass of typed testkits Not expecting this to be the final API just want to get some examples documented and internals hidden Refs #23667 and #22764
This commit is contained in:
parent
2aab0762dd
commit
7aa831bc2f
42 changed files with 1025 additions and 402 deletions
|
|
@ -0,0 +1,62 @@
|
|||
/**
|
||||
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package jdocs.akka.typed.testing.async;
|
||||
|
||||
import akka.actor.typed.ActorRef;
|
||||
import akka.actor.typed.ActorSystem;
|
||||
import akka.actor.typed.Behavior;
|
||||
import akka.actor.typed.javadsl.Actor;
|
||||
import akka.testkit.typed.javadsl.TestProbe;
|
||||
import akka.testkit.typed.TestKit;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Test;
|
||||
|
||||
//#test-header
|
||||
public class BasicAsyncTestingTest extends TestKit {
|
||||
public BasicAsyncTestingTest() {
|
||||
super(ActorSystem.create(Actor.empty(), "BasicAsyncTestingTest"));
|
||||
}
|
||||
//#test-header
|
||||
|
||||
//#under-test
|
||||
public static class Ping {
|
||||
private String msg;
|
||||
private ActorRef<Pong> replyTo;
|
||||
|
||||
public Ping(String msg, ActorRef<Pong> replyTo) {
|
||||
this.msg = msg;
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
}
|
||||
public static class Pong {
|
||||
private String msg;
|
||||
|
||||
public Pong(String msg) {
|
||||
this.msg = msg;
|
||||
}
|
||||
}
|
||||
|
||||
Behavior<Ping> echoActor = Actor.immutable((ctx, ping) -> {
|
||||
ping.replyTo.tell(new Pong(ping.msg));
|
||||
return Actor.same();
|
||||
});
|
||||
//#under-test
|
||||
|
||||
//#test-shutdown
|
||||
@AfterClass
|
||||
public void cleanup() {
|
||||
this.shutdown();
|
||||
}
|
||||
//#test-shutdown
|
||||
|
||||
@Test
|
||||
public void testVerifyingAResponse() {
|
||||
//#test
|
||||
TestProbe<Pong> probe = new TestProbe<>(system(), testkitSettings());
|
||||
ActorRef<Ping> pinger = actorOf(echoActor, "ping");
|
||||
pinger.tell(new Ping("hello", probe.ref()));
|
||||
probe.expectMsg(new Pong("hello"));
|
||||
//#test
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
package jdocs.akka.typed.testing.sync;
|
||||
|
||||
//#imports
|
||||
import akka.actor.typed.*;
|
||||
import akka.actor.typed.javadsl.*;
|
||||
import akka.testkit.typed.*;
|
||||
//#imports
|
||||
import org.junit.Test;
|
||||
import org.scalatest.junit.JUnitSuite;
|
||||
|
||||
public class BasicSyncTestingTest extends JUnitSuite {
|
||||
|
||||
//#child
|
||||
public static Behavior<String> childActor = Actor.immutable((ctx, msg) -> Actor.same());
|
||||
//#child
|
||||
|
||||
//#under-test
|
||||
interface Command { }
|
||||
public static class CreateAChild implements Command {
|
||||
private final String childName;
|
||||
public CreateAChild(String childName) {
|
||||
this.childName = childName;
|
||||
}
|
||||
}
|
||||
public static class CreateAnAnonymousChild implements Command { }
|
||||
public static class SayHelloToChild implements Command {
|
||||
private final String childName;
|
||||
public SayHelloToChild(String childName) {
|
||||
this.childName = childName;
|
||||
}
|
||||
}
|
||||
public static class SayHelloToAnonymousChild implements Command { }
|
||||
public static class SayHello implements Command {
|
||||
private final ActorRef<String> who;
|
||||
|
||||
public SayHello(ActorRef<String> who) {
|
||||
this.who = who;
|
||||
}
|
||||
}
|
||||
|
||||
public static Behavior<Command> myBehaviour = Actor.immutable(Command.class)
|
||||
.onMessage(CreateAChild.class, (ctx, msg) -> {
|
||||
ctx.spawn(childActor, msg.childName);
|
||||
return Actor.same();
|
||||
})
|
||||
.onMessage(CreateAnAnonymousChild.class, (ctx, msg) -> {
|
||||
ctx.spawnAnonymous(childActor);
|
||||
return Actor.same();
|
||||
})
|
||||
.onMessage(SayHelloToChild.class, (ctx, msg) -> {
|
||||
ActorRef<String> child = ctx.spawn(childActor, msg.childName);
|
||||
child.tell("hello");
|
||||
return Actor.same();
|
||||
})
|
||||
.onMessage(SayHelloToAnonymousChild.class, (ctx, msg) -> {
|
||||
ActorRef<String> child = ctx.spawnAnonymous(childActor);
|
||||
child.tell("hello stranger");
|
||||
return Actor.same();
|
||||
}).onMessage(SayHello.class, (ctx, msg) -> {
|
||||
msg.who.tell("hello");
|
||||
return Actor.same();
|
||||
}).build();
|
||||
//#under-test
|
||||
|
||||
|
||||
@Test
|
||||
public void testSpawning() {
|
||||
//#test-child
|
||||
BehaviorTestkit<Command> test = BehaviorTestkit.create(myBehaviour);
|
||||
test.run(new CreateAChild("child"));
|
||||
test.expectEffect(new Effect.Spawned(childActor, "child", Props.empty()));
|
||||
//#test-child
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpawningAnonymous() {
|
||||
//#test-anonymous-child
|
||||
BehaviorTestkit<Command> test = BehaviorTestkit.create(myBehaviour);
|
||||
test.run(new CreateAnAnonymousChild());
|
||||
test.expectEffect(new Effect.SpawnedAnonymous(childActor, Props.empty()));
|
||||
//#test-anonymous-child
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRecodingMessageSend() {
|
||||
//#test-message
|
||||
BehaviorTestkit<Command> test = BehaviorTestkit.create(myBehaviour);
|
||||
TestInbox<String> inbox = new TestInbox<String>();
|
||||
test.run(new SayHello(inbox.ref()));
|
||||
inbox.expectMsg("hello");
|
||||
//#test-message
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMessageToChild() {
|
||||
//#test-child-message
|
||||
BehaviorTestkit<Command> testKit = BehaviorTestkit.create(myBehaviour);
|
||||
testKit.run(new SayHelloToChild("child"));
|
||||
TestInbox<String> childInbox = testKit.childInbox("child");
|
||||
childInbox.expectMsg("hello");
|
||||
//#test-child-message
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMessageToAnonymousChild() {
|
||||
//#test-child-message-anonymous
|
||||
BehaviorTestkit<Command> testKit = BehaviorTestkit.create(myBehaviour);
|
||||
testKit.run(new SayHelloToAnonymousChild());
|
||||
// Anonymous actors are created as: $a $b etc
|
||||
TestInbox<String> childInbox = testKit.childInbox("$a");
|
||||
childInbox.expectMsg("hello stranger");
|
||||
//#test-child-message-anonymous
|
||||
}
|
||||
}
|
||||
|
|
@ -266,7 +266,7 @@ abstract class ActorContextSpec extends TypedSpec(ConfigFactory.parseString(
|
|||
| lifecycle = off
|
||||
| autoreceive = off
|
||||
| }
|
||||
| typed.loggers = ["akka.typed.testkit.TestEventListener"]
|
||||
| typed.loggers = ["akka.testkit.typed.TestEventListener"]
|
||||
|}""".stripMargin)) {
|
||||
|
||||
import ActorContextSpec._
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import akka.japi.pf.{ FI, PFBuilder }
|
|||
import java.util.function.{ Function ⇒ F1 }
|
||||
|
||||
import akka.Done
|
||||
import akka.typed.testkit.{ EffectfulActorContext, Inbox }
|
||||
import akka.testkit.typed.{ BehaviorTestkit, TestInbox }
|
||||
|
||||
object BehaviorSpec {
|
||||
sealed trait Command {
|
||||
|
|
@ -68,23 +68,23 @@ object BehaviorSpec {
|
|||
def checkAux(signal: Signal, aux: Aux): Unit = ()
|
||||
def checkAux(command: Command, aux: Aux): Unit = ()
|
||||
|
||||
case class Init(behv: Behavior[Command], inbox: Inbox[Event], aux: Aux) {
|
||||
case class Init(behv: Behavior[Command], inbox: TestInbox[Event], aux: Aux) {
|
||||
def mkCtx(): Setup = {
|
||||
val ctx = new EffectfulActorContext("ctx", behv, 1000, system)
|
||||
val msgs = inbox.receiveAll()
|
||||
Setup(ctx, inbox, aux)
|
||||
val testkit = BehaviorTestkit(behv)
|
||||
inbox.receiveAll()
|
||||
Setup(testkit, inbox, aux)
|
||||
}
|
||||
}
|
||||
case class Setup(ctx: EffectfulActorContext[Command], inbox: Inbox[Event], aux: Aux)
|
||||
case class Setup(testKit: BehaviorTestkit[Command], inbox: TestInbox[Event], aux: Aux)
|
||||
|
||||
def init(): Init = {
|
||||
val inbox = Inbox[Event]("evt")
|
||||
val inbox = TestInbox[Event]("evt")
|
||||
val (behv, aux) = behavior(inbox.ref)
|
||||
Init(behv, inbox, aux)
|
||||
}
|
||||
|
||||
def init(factory: ActorRef[Event] ⇒ (Behavior[Command], Aux)): Init = {
|
||||
val inbox = Inbox[Event]("evt")
|
||||
val inbox = TestInbox[Event]("evt")
|
||||
val (behv, aux) = factory(inbox.ref)
|
||||
Init(behv, inbox, aux)
|
||||
}
|
||||
|
|
@ -94,20 +94,20 @@ object BehaviorSpec {
|
|||
|
||||
implicit class Check(val setup: Setup) {
|
||||
def check(signal: Signal): Setup = {
|
||||
setup.ctx.signal(signal)
|
||||
setup.testKit.signal(signal)
|
||||
setup.inbox.receiveAll() should ===(GotSignal(signal) :: Nil)
|
||||
checkAux(signal, setup.aux)
|
||||
setup
|
||||
}
|
||||
def check(command: Command): Setup = {
|
||||
setup.ctx.run(command)
|
||||
setup.inbox.receiveAll() should ===(command.expectedResponse(setup.ctx))
|
||||
setup.testKit.run(command)
|
||||
setup.inbox.receiveAll() should ===(command.expectedResponse(setup.testKit.ctx))
|
||||
checkAux(command, setup.aux)
|
||||
setup
|
||||
}
|
||||
def check2(command: Command): Setup = {
|
||||
setup.ctx.run(command)
|
||||
val expected = command.expectedResponse(setup.ctx)
|
||||
setup.testKit.run(command)
|
||||
val expected = command.expectedResponse(setup.testKit.ctx)
|
||||
setup.inbox.receiveAll() should ===(expected ++ expected)
|
||||
checkAux(command, setup.aux)
|
||||
setup
|
||||
|
|
@ -118,7 +118,7 @@ object BehaviorSpec {
|
|||
}
|
||||
|
||||
trait Siphon extends Common {
|
||||
override type Aux = Inbox[Command]
|
||||
override type Aux = TestInbox[Command]
|
||||
|
||||
override def checkAux(command: Command, aux: Aux): Unit = {
|
||||
aux.receiveAll() should ===(command :: Nil)
|
||||
|
|
@ -126,7 +126,7 @@ object BehaviorSpec {
|
|||
}
|
||||
|
||||
trait SignalSiphon extends Common {
|
||||
override type Aux = Inbox[Either[Signal, Command]]
|
||||
override type Aux = TestInbox[Either[Signal, Command]]
|
||||
|
||||
override def checkAux(command: Command, aux: Aux): Unit = {
|
||||
aux.receiveAll() should ===(Right(command) :: Nil)
|
||||
|
|
@ -142,25 +142,25 @@ object BehaviorSpec {
|
|||
case (ctx, GetSelf) ⇒
|
||||
monitor ! Self(ctx.self)
|
||||
SActor.same
|
||||
case (ctx, Miss) ⇒
|
||||
case (_, Miss) ⇒
|
||||
monitor ! Missed
|
||||
SActor.unhandled
|
||||
case (ctx, Ignore) ⇒
|
||||
case (_, Ignore) ⇒
|
||||
monitor ! Ignored
|
||||
SActor.same
|
||||
case (ctx, Ping) ⇒
|
||||
case (_, Ping) ⇒
|
||||
monitor ! Pong
|
||||
mkFull(monitor, state)
|
||||
case (ctx, Swap) ⇒
|
||||
case (_, Swap) ⇒
|
||||
monitor ! Swapped
|
||||
mkFull(monitor, state.next)
|
||||
case (ctx, GetState()) ⇒
|
||||
case (_, GetState()) ⇒
|
||||
monitor ! state
|
||||
SActor.same
|
||||
case (ctx, Stop) ⇒ SActor.stopped
|
||||
case (_, _) ⇒ SActor.unhandled
|
||||
case (_, Stop) ⇒ SActor.stopped
|
||||
case (_, _) ⇒ SActor.unhandled
|
||||
} onSignal {
|
||||
case (ctx, signal) ⇒
|
||||
case (_, signal) ⇒
|
||||
monitor ! GotSignal(signal)
|
||||
SActor.same
|
||||
}
|
||||
|
|
@ -220,15 +220,15 @@ object BehaviorSpec {
|
|||
}
|
||||
|
||||
"must react to Terminated" in {
|
||||
mkCtx().check(Terminated(Inbox("x").ref)(null))
|
||||
mkCtx().check(Terminated(TestInbox("x").ref)(null))
|
||||
}
|
||||
|
||||
"must react to Terminated after a message" in {
|
||||
mkCtx().check(GetSelf).check(Terminated(Inbox("x").ref)(null))
|
||||
mkCtx().check(GetSelf).check(Terminated(TestInbox("x").ref)(null))
|
||||
}
|
||||
|
||||
"must react to a message after Terminated" in {
|
||||
mkCtx().check(Terminated(Inbox("x").ref)(null)).check(GetSelf)
|
||||
mkCtx().check(Terminated(TestInbox("x").ref)(null)).check(GetSelf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -250,10 +250,10 @@ object BehaviorSpec {
|
|||
}
|
||||
|
||||
trait Unhandled extends Common {
|
||||
"Unahndled" must {
|
||||
"Unhandled" must {
|
||||
"must return Unhandled" in {
|
||||
val Setup(ctx, inbox, aux) = mkCtx()
|
||||
Behavior.interpretMessage(ctx.currentBehavior, ctx, Miss) should be(Behavior.UnhandledBehavior)
|
||||
val Setup(testKit, inbox, aux) = mkCtx()
|
||||
Behavior.interpretMessage(testKit.currentBehavior, testKit.ctx, Miss) should be(Behavior.UnhandledBehavior)
|
||||
inbox.receiveAll() should ===(Missed :: Nil)
|
||||
checkAux(Miss, aux)
|
||||
}
|
||||
|
|
@ -263,16 +263,16 @@ object BehaviorSpec {
|
|||
trait Stoppable extends Common {
|
||||
"Stopping" must {
|
||||
"must stop" in {
|
||||
val Setup(ctx, inbox, aux) = mkCtx()
|
||||
ctx.run(Stop)
|
||||
ctx.currentBehavior should be(Behavior.StoppedBehavior)
|
||||
val Setup(testkit, _, aux) = mkCtx()
|
||||
testkit.run(Stop)
|
||||
testkit.currentBehavior should be(Behavior.StoppedBehavior)
|
||||
checkAux(Stop, aux)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait Become extends Common with Unhandled {
|
||||
private implicit val inbox = Inbox[State]("state")
|
||||
private implicit val inbox = TestInbox[State]("state")
|
||||
|
||||
"Becoming" must {
|
||||
"must be in state A" in {
|
||||
|
|
@ -308,15 +308,15 @@ object BehaviorSpec {
|
|||
}
|
||||
|
||||
"react to Terminated after swap" in {
|
||||
mkCtx().check(Swap).check(Terminated(Inbox("x").ref)(null))
|
||||
mkCtx().check(Swap).check(Terminated(TestInbox("x").ref)(null))
|
||||
}
|
||||
|
||||
"react to Terminated after a message after swap" in {
|
||||
mkCtx().check(Swap).check(GetSelf).check(Terminated(Inbox("x").ref)(null))
|
||||
mkCtx().check(Swap).check(GetSelf).check(Terminated(TestInbox("x").ref)(null))
|
||||
}
|
||||
|
||||
"react to a message after Terminated after swap" in {
|
||||
mkCtx().check(Swap).check(Terminated(Inbox("x").ref)(null)).check(GetSelf)
|
||||
mkCtx().check(Swap).check(Terminated(TestInbox("x").ref)(null)).check(GetSelf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -368,7 +368,7 @@ class ImmutableBehaviorSpec extends TypedSpec with Messages with BecomeWithLifec
|
|||
case (_, Stop) ⇒ SActor.stopped
|
||||
case (_, _: AuxPing) ⇒ SActor.unhandled
|
||||
} onSignal {
|
||||
case (ctx, signal) ⇒
|
||||
case (_, signal) ⇒
|
||||
monitor ! GotSignal(signal)
|
||||
SActor.same
|
||||
}
|
||||
|
|
@ -405,7 +405,7 @@ class ImmutableWithSignalScalaBehaviorSpec extends TypedSpec with Messages with
|
|||
case _: AuxPing ⇒ SActor.unhandled
|
||||
}
|
||||
} onSignal {
|
||||
case (ctx, sig) ⇒
|
||||
case (_, sig) ⇒
|
||||
monitor ! GotSignal(sig)
|
||||
SActor.same
|
||||
}
|
||||
|
|
@ -485,17 +485,17 @@ class WidenedScalaBehaviorSpec extends ImmutableWithSignalScalaBehaviorSpec with
|
|||
import SActor.BehaviorDecorators
|
||||
|
||||
override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = {
|
||||
val inbox = Inbox[Command]("widenedListener")
|
||||
val inbox = TestInbox[Command]("widenedListener")
|
||||
super.behavior(monitor)._1.widen[Command] { case c ⇒ inbox.ref ! c; c } → inbox
|
||||
}
|
||||
}
|
||||
|
||||
class DeferredScalaBehaviorSpec extends ImmutableWithSignalScalaBehaviorSpec {
|
||||
override type Aux = Inbox[Done]
|
||||
override type Aux = TestInbox[Done]
|
||||
|
||||
override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = {
|
||||
val inbox = Inbox[Done]("deferredListener")
|
||||
(SActor.deferred(ctx ⇒ {
|
||||
val inbox = TestInbox[Done]("deferredListener")
|
||||
(SActor.deferred(_ ⇒ {
|
||||
inbox.ref ! Done
|
||||
super.behavior(monitor)._1
|
||||
}), inbox)
|
||||
|
|
@ -507,7 +507,7 @@ class DeferredScalaBehaviorSpec extends ImmutableWithSignalScalaBehaviorSpec {
|
|||
|
||||
class TapScalaBehaviorSpec extends ImmutableWithSignalScalaBehaviorSpec with Reuse with SignalSiphon {
|
||||
override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = {
|
||||
val inbox = Inbox[Either[Signal, Command]]("tapListener")
|
||||
val inbox = TestInbox[Either[Signal, Command]]("tapListener")
|
||||
(SActor.tap((_, msg) ⇒ inbox.ref ! Right(msg), (_, sig) ⇒ inbox.ref ! Left(sig), super.behavior(monitor)._1), inbox)
|
||||
}
|
||||
}
|
||||
|
|
@ -544,7 +544,7 @@ class ImmutableWithSignalJavaBehaviorSpec extends TypedSpec with Messages with B
|
|||
case Stop ⇒ SActor.stopped
|
||||
case _: AuxPing ⇒ SActor.unhandled
|
||||
}),
|
||||
fs((ctx, sig) ⇒ {
|
||||
fs((_, sig) ⇒ {
|
||||
monitor ! GotSignal(sig)
|
||||
SActor.same
|
||||
}))
|
||||
|
|
@ -582,7 +582,7 @@ class ImmutableJavaBehaviorSpec extends TypedSpec with Messages with Become with
|
|||
|
||||
class WidenedJavaBehaviorSpec extends ImmutableWithSignalJavaBehaviorSpec with Reuse with Siphon {
|
||||
override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = {
|
||||
val inbox = Inbox[Command]("widenedListener")
|
||||
val inbox = TestInbox[Command]("widenedListener")
|
||||
JActor.widened(super.behavior(monitor)._1, pf(_.`match`(classOf[Command], fi(x ⇒ {
|
||||
inbox.ref ! x
|
||||
x
|
||||
|
|
@ -591,11 +591,11 @@ class WidenedJavaBehaviorSpec extends ImmutableWithSignalJavaBehaviorSpec with R
|
|||
}
|
||||
|
||||
class DeferredJavaBehaviorSpec extends ImmutableWithSignalJavaBehaviorSpec {
|
||||
override type Aux = Inbox[Done]
|
||||
override type Aux = TestInbox[Done]
|
||||
|
||||
override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = {
|
||||
val inbox = Inbox[Done]("deferredListener")
|
||||
(JActor.deferred(df(ctx ⇒ {
|
||||
val inbox = TestInbox[Done]("deferredListener")
|
||||
(JActor.deferred(df(_ ⇒ {
|
||||
inbox.ref ! Done
|
||||
super.behavior(monitor)._1
|
||||
})), inbox)
|
||||
|
|
@ -607,7 +607,7 @@ class DeferredJavaBehaviorSpec extends ImmutableWithSignalJavaBehaviorSpec {
|
|||
|
||||
class TapJavaBehaviorSpec extends ImmutableWithSignalJavaBehaviorSpec with Reuse with SignalSiphon {
|
||||
override def behavior(monitor: ActorRef[Event]): (Behavior[Command], Aux) = {
|
||||
val inbox = Inbox[Either[Signal, Command]]("tapListener")
|
||||
val inbox = TestInbox[Either[Signal, Command]]("tapListener")
|
||||
(JActor.tap(
|
||||
pc((_, msg) ⇒ inbox.ref ! Right(msg)),
|
||||
ps((_, sig) ⇒ inbox.ref ! Left(sig)),
|
||||
|
|
|
|||
|
|
@ -3,14 +3,12 @@
|
|||
*/
|
||||
package akka.actor.typed
|
||||
|
||||
import scala.concurrent.Await
|
||||
import scala.concurrent.duration._
|
||||
import scala.util.control.NoStackTrace
|
||||
import akka.actor.typed.scaladsl.Actor
|
||||
import akka.actor.typed.scaladsl.Actor.BehaviorDecorators
|
||||
import akka.actor.typed.scaladsl.AskPattern._
|
||||
import akka.typed.testkit.{ EffectfulActorContext, Inbox, TestKitSettings }
|
||||
import akka.typed.testkit.scaladsl._
|
||||
import akka.testkit.typed.{ BehaviorTestkit, TestInbox, TestKitSettings }
|
||||
import akka.testkit.typed.scaladsl._
|
||||
|
||||
object DeferredSpec {
|
||||
sealed trait Command
|
||||
|
|
@ -21,7 +19,7 @@ object DeferredSpec {
|
|||
case object Started extends Event
|
||||
|
||||
def target(monitor: ActorRef[Event]): Behavior[Command] =
|
||||
Actor.immutable((ctx, cmd) ⇒ cmd match {
|
||||
Actor.immutable((_, cmd) ⇒ cmd match {
|
||||
case Ping ⇒
|
||||
monitor ! Pong
|
||||
Actor.same
|
||||
|
|
@ -92,7 +90,7 @@ class DeferredSpec extends TypedSpec with StartSupport {
|
|||
probe.expectMsg(Started)
|
||||
}
|
||||
|
||||
"must undefer underlying when wrapped by widen" in {
|
||||
"must un-defer underlying when wrapped by widen" in {
|
||||
val probe = TestProbe[Event]("evt")
|
||||
val behv = Actor.deferred[Command] { _ ⇒
|
||||
probe.ref ! Started
|
||||
|
|
@ -108,7 +106,7 @@ class DeferredSpec extends TypedSpec with StartSupport {
|
|||
probe.expectMsg(Pong)
|
||||
}
|
||||
|
||||
"must undefer underlying when wrapped by monitor" in {
|
||||
"must un-defer underlying when wrapped by monitor" in {
|
||||
// monitor is implemented with tap, so this is testing both
|
||||
val probe = TestProbe[Event]("evt")
|
||||
val monitorProbe = TestProbe[Command]("monitor")
|
||||
|
|
@ -132,22 +130,22 @@ class DeferredStubbedSpec extends TypedSpec {
|
|||
|
||||
import DeferredSpec._
|
||||
|
||||
def mkCtx(behv: Behavior[Command]): EffectfulActorContext[Command] =
|
||||
new EffectfulActorContext("ctx", behv, 1000, system)
|
||||
def mkCtx(behv: Behavior[Command]): BehaviorTestkit[Command] =
|
||||
BehaviorTestkit(behv, "ctx")
|
||||
|
||||
"must create underlying deferred behavior immediately" in {
|
||||
val inbox = Inbox[Event]("evt")
|
||||
val inbox = TestInbox[Event]("evt")
|
||||
val behv = Actor.deferred[Command] { _ ⇒
|
||||
inbox.ref ! Started
|
||||
target(inbox.ref)
|
||||
}
|
||||
val ctx = mkCtx(behv)
|
||||
mkCtx(behv)
|
||||
// it's supposed to be created immediately (not waiting for first message)
|
||||
inbox.receiveMsg() should ===(Started)
|
||||
}
|
||||
|
||||
"must stop when exception from factory" in {
|
||||
val inbox = Inbox[Event]("evt")
|
||||
val inbox = TestInbox[Event]("evt")
|
||||
val exc = new RuntimeException("simulated exc from factory") with NoStackTrace
|
||||
val behv = Actor.deferred[Command] { _ ⇒
|
||||
inbox.ref ! Started
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ package akka.actor.typed
|
|||
|
||||
import scala.concurrent.duration._
|
||||
import akka.actor.typed.scaladsl.Actor._
|
||||
import akka.typed.testkit.{ EffectfulActorContext, Inbox, TestKitSettings }
|
||||
import akka.testkit.typed.{ BehaviorTestkit, TestInbox, TestKitSettings }
|
||||
|
||||
import scala.util.control.NoStackTrace
|
||||
import akka.typed.testkit.scaladsl._
|
||||
import akka.testkit.typed.scaladsl._
|
||||
|
||||
object RestarterSpec {
|
||||
|
||||
|
|
@ -68,12 +68,12 @@ class RestarterSpec extends TypedSpec {
|
|||
|
||||
import RestarterSpec._
|
||||
|
||||
def mkCtx(behv: Behavior[Command]): EffectfulActorContext[Command] =
|
||||
new EffectfulActorContext("ctx", behv, 1000, system)
|
||||
def mkCtx(behv: Behavior[Command]): BehaviorTestkit[Command] =
|
||||
BehaviorTestkit(behv, "ctx")
|
||||
|
||||
"A restarter" must {
|
||||
"receive message" in {
|
||||
val inbox = Inbox[Event]("evt")
|
||||
val inbox = TestInbox[Event]("evt")
|
||||
val behv = supervise(target(inbox.ref)).onFailure[Throwable](SupervisorStrategy.restart)
|
||||
val ctx = mkCtx(behv)
|
||||
ctx.run(Ping)
|
||||
|
|
@ -81,7 +81,7 @@ class RestarterSpec extends TypedSpec {
|
|||
}
|
||||
|
||||
"stop when no supervise" in {
|
||||
val inbox = Inbox[Event]("evt")
|
||||
val inbox = TestInbox[Event]("evt")
|
||||
val behv = target(inbox.ref)
|
||||
val ctx = mkCtx(behv)
|
||||
intercept[Exc3] {
|
||||
|
|
@ -91,7 +91,7 @@ class RestarterSpec extends TypedSpec {
|
|||
}
|
||||
|
||||
"stop when unhandled exception" in {
|
||||
val inbox = Inbox[Event]("evt")
|
||||
val inbox = TestInbox[Event]("evt")
|
||||
val behv = supervise(target(inbox.ref)).onFailure[Exc1](SupervisorStrategy.restart)
|
||||
val ctx = mkCtx(behv)
|
||||
intercept[Exc3] {
|
||||
|
|
@ -101,7 +101,7 @@ class RestarterSpec extends TypedSpec {
|
|||
}
|
||||
|
||||
"restart when handled exception" in {
|
||||
val inbox = Inbox[Event]("evt")
|
||||
val inbox = TestInbox[Event]("evt")
|
||||
val behv = supervise(target(inbox.ref)).onFailure[Exc1](SupervisorStrategy.restart)
|
||||
val ctx = mkCtx(behv)
|
||||
ctx.run(NextState)
|
||||
|
|
@ -115,7 +115,7 @@ class RestarterSpec extends TypedSpec {
|
|||
}
|
||||
|
||||
"resume when handled exception" in {
|
||||
val inbox = Inbox[Event]("evt")
|
||||
val inbox = TestInbox[Event]("evt")
|
||||
val behv = supervise(target(inbox.ref)).onFailure[Exc1](SupervisorStrategy.resume)
|
||||
val ctx = mkCtx(behv)
|
||||
ctx.run(NextState)
|
||||
|
|
@ -128,7 +128,7 @@ class RestarterSpec extends TypedSpec {
|
|||
}
|
||||
|
||||
"support nesting to handle different exceptions" in {
|
||||
val inbox = Inbox[Event]("evt")
|
||||
val inbox = TestInbox[Event]("evt")
|
||||
val behv =
|
||||
supervise(
|
||||
supervise(
|
||||
|
|
@ -159,7 +159,7 @@ class RestarterSpec extends TypedSpec {
|
|||
}
|
||||
|
||||
"not catch fatal error" in {
|
||||
val inbox = Inbox[Event]("evt")
|
||||
val inbox = TestInbox[Event]("evt")
|
||||
val behv = supervise(target(inbox.ref)).onFailure[Throwable](SupervisorStrategy.restart)
|
||||
val ctx = mkCtx(behv)
|
||||
intercept[StackOverflowError] {
|
||||
|
|
@ -169,7 +169,7 @@ class RestarterSpec extends TypedSpec {
|
|||
}
|
||||
|
||||
"stop after restart retries limit" in {
|
||||
val inbox = Inbox[Event]("evt")
|
||||
val inbox = TestInbox[Event]("evt")
|
||||
val strategy = SupervisorStrategy.restartWithLimit(maxNrOfRetries = 2, withinTimeRange = 1.minute)
|
||||
val behv = supervise(target(inbox.ref)).onFailure[Exc1](strategy)
|
||||
val ctx = mkCtx(behv)
|
||||
|
|
@ -184,7 +184,7 @@ class RestarterSpec extends TypedSpec {
|
|||
}
|
||||
|
||||
"reset retry limit after withinTimeRange" in {
|
||||
val inbox = Inbox[Event]("evt")
|
||||
val inbox = TestInbox[Event]("evt")
|
||||
val withinTimeRange = 2.seconds
|
||||
val strategy = SupervisorStrategy.restartWithLimit(maxNrOfRetries = 2, withinTimeRange)
|
||||
val behv = supervise(target(inbox.ref)).onFailure[Exc1](strategy)
|
||||
|
|
@ -206,7 +206,7 @@ class RestarterSpec extends TypedSpec {
|
|||
}
|
||||
|
||||
"stop at first exception when restart retries limit is 0" in {
|
||||
val inbox = Inbox[Event]("evt")
|
||||
val inbox = TestInbox[Event]("evt")
|
||||
val strategy = SupervisorStrategy.restartWithLimit(maxNrOfRetries = 0, withinTimeRange = 1.minute)
|
||||
val behv = supervise(target(inbox.ref)).onFailure[Exc1](strategy)
|
||||
val ctx = mkCtx(behv)
|
||||
|
|
@ -217,12 +217,12 @@ class RestarterSpec extends TypedSpec {
|
|||
}
|
||||
|
||||
"create underlying deferred behavior immediately" in {
|
||||
val inbox = Inbox[Event]("evt")
|
||||
val inbox = TestInbox[Event]("evt")
|
||||
val behv = supervise(deferred[Command] { _ ⇒
|
||||
inbox.ref ! Started
|
||||
target(inbox.ref)
|
||||
}).onFailure[Exc1](SupervisorStrategy.restart)
|
||||
val ctx = mkCtx(behv)
|
||||
mkCtx(behv)
|
||||
// it's supposed to be created immediately (not waiting for first message)
|
||||
inbox.receiveMsg() should ===(Started)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ import scala.util.control.NoStackTrace
|
|||
|
||||
import akka.actor.typed.scaladsl.Actor
|
||||
import akka.actor.typed.scaladsl.TimerScheduler
|
||||
import akka.typed.testkit.TestKitSettings
|
||||
import akka.typed.testkit.scaladsl._
|
||||
import akka.testkit.typed.TestKitSettings
|
||||
import akka.testkit.typed.scaladsl._
|
||||
|
||||
class TimerSpec extends TypedSpec(
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -22,13 +22,12 @@ import akka.actor.typed.scaladsl.Actor._
|
|||
import org.scalatest.concurrent.ScalaFutures
|
||||
import org.scalactic.TypeCheckedTripleEquals
|
||||
import org.scalactic.CanEqual
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import scala.util.control.NonFatal
|
||||
import akka.actor.typed.scaladsl.AskPattern
|
||||
|
||||
import scala.util.control.NoStackTrace
|
||||
import akka.typed.testkit.{ Inbox, TestKitSettings }
|
||||
import akka.testkit.typed.{ TestInbox, TestKitSettings }
|
||||
import org.scalatest.time.Span
|
||||
|
||||
/**
|
||||
|
|
@ -52,7 +51,7 @@ trait StartSupport {
|
|||
|
||||
def start[T](behv: Behavior[T]): ActorRef[T] = {
|
||||
import akka.actor.typed.scaladsl.AskPattern._
|
||||
import akka.typed.testkit.scaladsl._
|
||||
import akka.testkit.typed.scaladsl._
|
||||
implicit val testSettings = TestKitSettings(system)
|
||||
Await.result(system ? TypedSpec.Create(behv, nextName()), 3.seconds.dilated)
|
||||
}
|
||||
|
|
@ -145,7 +144,7 @@ abstract class TypedSpec(val config: Config) extends TypedSpecSetup {
|
|||
/**
|
||||
* Group assertion that ensures that the given inboxes are empty.
|
||||
*/
|
||||
def assertEmpty(inboxes: Inbox[_]*): Unit = {
|
||||
def assertEmpty(inboxes: TestInbox[_]*): Unit = {
|
||||
inboxes foreach (i ⇒ withClue(s"inbox $i had messages")(i.hasMessages should be(false)))
|
||||
}
|
||||
|
||||
|
|
@ -163,8 +162,6 @@ abstract class TypedSpec(val config: Config) extends TypedSpecSetup {
|
|||
|
||||
object TypedSpec {
|
||||
|
||||
import akka.{ typed ⇒ t }
|
||||
|
||||
sealed abstract class Start
|
||||
case object Start extends Start
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,7 @@ package internal
|
|||
|
||||
import akka.actor.InvalidMessageException
|
||||
import akka.actor.typed.scaladsl.Actor
|
||||
import akka.actor.typed.scaladsl.Actor._
|
||||
import akka.typed.testkit.Inbox
|
||||
import org.scalactic.ConversionCheckedTripleEquals
|
||||
import akka.testkit.typed.TestInbox
|
||||
import org.scalatest._
|
||||
import org.scalatest.concurrent.{ Eventually, ScalaFutures }
|
||||
|
||||
|
|
@ -16,7 +14,8 @@ import scala.concurrent.duration._
|
|||
import scala.concurrent.{ Future, Promise }
|
||||
import scala.util.control.NonFatal
|
||||
|
||||
class ActorSystemSpec extends WordSpec with Matchers with BeforeAndAfterAll with ScalaFutures with Eventually with ConversionCheckedTripleEquals {
|
||||
class ActorSystemSpec extends WordSpec with Matchers with BeforeAndAfterAll
|
||||
with ScalaFutures with Eventually {
|
||||
|
||||
override implicit val patienceConfig = PatienceConfig(1.second)
|
||||
def system[T](behavior: Behavior[T], name: String) = ActorSystem(behavior, name)
|
||||
|
|
@ -37,46 +36,56 @@ class ActorSystemSpec extends WordSpec with Matchers with BeforeAndAfterAll with
|
|||
}
|
||||
|
||||
"An ActorSystem" must {
|
||||
"must start the guardian actor and terminate when it terminates" in {
|
||||
val t = withSystem("a", immutable[Probe] { case (_, p) ⇒ p.replyTo ! p.msg; stopped }, doTerminate = false) { sys ⇒
|
||||
val inbox = Inbox[String]("a")
|
||||
sys ! Probe("hello", inbox.ref)
|
||||
eventually {
|
||||
inbox.hasMessages should ===(true)
|
||||
"start the guardian actor and terminate when it terminates" in {
|
||||
val t = withSystem(
|
||||
"a",
|
||||
Actor.immutable[Probe] { case (_, p) ⇒ p.replyTo ! p.msg; Actor.stopped }, doTerminate = false) { sys ⇒
|
||||
val inbox = TestInbox[String]("a")
|
||||
sys ! Probe("hello", inbox.ref)
|
||||
eventually {
|
||||
inbox.hasMessages should ===(true)
|
||||
}
|
||||
inbox.receiveAll() should ===("hello" :: Nil)
|
||||
}
|
||||
inbox.receiveAll() should ===("hello" :: Nil)
|
||||
}
|
||||
val p = t.ref.path
|
||||
p.name should ===("/")
|
||||
p.address.system should ===(suite + "-a")
|
||||
}
|
||||
|
||||
"must terminate the guardian actor" in {
|
||||
val inbox = Inbox[String]("terminate")
|
||||
// see issue #24172
|
||||
"shutdown if guardian shuts down immediately" in {
|
||||
pending
|
||||
withSystem("shutdown", Actor.stopped[String], doTerminate = false) { sys: ActorSystem[String] ⇒
|
||||
sys.whenTerminated.futureValue
|
||||
}
|
||||
}
|
||||
|
||||
"terminate the guardian actor" in {
|
||||
val inbox = TestInbox[String]("terminate")
|
||||
val sys = system(
|
||||
immutable[Probe] {
|
||||
case (_, _) ⇒ unhandled
|
||||
Actor.immutable[Probe] {
|
||||
case (_, _) ⇒ Actor.unhandled
|
||||
} onSignal {
|
||||
case (ctx, PostStop) ⇒
|
||||
case (_, PostStop) ⇒
|
||||
inbox.ref ! "done"
|
||||
same
|
||||
Actor.same
|
||||
},
|
||||
"terminate")
|
||||
sys.terminate().futureValue
|
||||
inbox.receiveAll() should ===("done" :: Nil)
|
||||
}
|
||||
|
||||
"must log to the event stream" in {
|
||||
"log to the event stream" in {
|
||||
pending
|
||||
}
|
||||
|
||||
"must have a name" in {
|
||||
"have a name" in {
|
||||
withSystem("name", Actor.empty[String]) { sys ⇒
|
||||
sys.name should ===(suite + "-name")
|
||||
}
|
||||
}
|
||||
|
||||
"must report its uptime" in {
|
||||
"report its uptime" in {
|
||||
withSystem("uptime", Actor.empty[String]) { sys ⇒
|
||||
sys.uptime should be < 1L
|
||||
Thread.sleep(1000)
|
||||
|
|
@ -84,7 +93,7 @@ class ActorSystemSpec extends WordSpec with Matchers with BeforeAndAfterAll with
|
|||
}
|
||||
}
|
||||
|
||||
"must have a working thread factory" in {
|
||||
"have a working thread factory" in {
|
||||
withSystem("thread", Actor.empty[String]) { sys ⇒
|
||||
val p = Promise[Int]
|
||||
sys.threadFactory.newThread(new Runnable {
|
||||
|
|
@ -94,14 +103,14 @@ class ActorSystemSpec extends WordSpec with Matchers with BeforeAndAfterAll with
|
|||
}
|
||||
}
|
||||
|
||||
"must be able to run Futures" in {
|
||||
"be able to run Futures" in {
|
||||
withSystem("futures", Actor.empty[String]) { sys ⇒
|
||||
val f = Future(42)(sys.executionContext)
|
||||
f.futureValue should ===(42)
|
||||
}
|
||||
}
|
||||
|
||||
"must not allow null messages" in {
|
||||
"not allow null messages" in {
|
||||
withSystem("null-messages", Actor.empty[String]) { sys ⇒
|
||||
intercept[InvalidMessageException] {
|
||||
sys ! null
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ import akka.actor.typed._
|
|||
import akka.actor.typed.receptionist.Receptionist._
|
||||
import akka.actor.typed.scaladsl.Actor
|
||||
import akka.actor.typed.scaladsl.AskPattern._
|
||||
import akka.typed.testkit.EffectfulActorContext
|
||||
import akka.typed.testkit.Inbox
|
||||
import akka.typed.testkit.TestKitSettings
|
||||
import akka.typed.testkit.scaladsl.TestProbe
|
||||
import akka.testkit.typed.BehaviorTestkit
|
||||
import akka.testkit.typed.TestInbox
|
||||
import akka.testkit.typed.TestKitSettings
|
||||
import akka.testkit.typed.scaladsl.TestProbe
|
||||
import org.scalatest.concurrent.Eventually
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
|
@ -26,7 +26,7 @@ class LocalReceptionistSpec extends TypedSpec with Eventually with StartSupport
|
|||
val behaviorB = Actor.empty[ServiceB]
|
||||
|
||||
case object Stop extends ServiceA with ServiceB
|
||||
val stoppableBehavior = Actor.immutable[Any] { (ctx, msg) ⇒
|
||||
val stoppableBehavior = Actor.immutable[Any] { (_, msg) ⇒
|
||||
msg match {
|
||||
case Stop ⇒ Behavior.stopped
|
||||
case _ ⇒ Behavior.same
|
||||
|
|
@ -44,29 +44,29 @@ class LocalReceptionistSpec extends TypedSpec with Eventually with StartSupport
|
|||
"A local receptionist" must {
|
||||
|
||||
"must register a service" in {
|
||||
val ctx = new EffectfulActorContext("register", behavior, 1000, system)
|
||||
val a = Inbox[ServiceA]("a")
|
||||
val r = Inbox[Registered[_]]("r")
|
||||
val ctx = new BehaviorTestkit("register", behavior)
|
||||
val a = TestInbox[ServiceA]("a")
|
||||
val r = TestInbox[Registered[_]]("r")
|
||||
ctx.run(Register(ServiceKeyA, a.ref)(r.ref))
|
||||
ctx.getEffect() // watching however that is implemented
|
||||
ctx.retrieveEffect() // watching however that is implemented
|
||||
r.receiveMsg() should be(Registered(ServiceKeyA, a.ref))
|
||||
val q = Inbox[Listing[ServiceA]]("q")
|
||||
val q = TestInbox[Listing[ServiceA]]("q")
|
||||
ctx.run(Find(ServiceKeyA)(q.ref))
|
||||
ctx.getAllEffects() should be(Nil)
|
||||
ctx.retrieveAllEffects() should be(Nil)
|
||||
q.receiveMsg() should be(Listing(ServiceKeyA, Set(a.ref)))
|
||||
assertEmpty(a, r, q)
|
||||
}
|
||||
|
||||
"must register two services" in {
|
||||
val ctx = new EffectfulActorContext("registertwo", behavior, 1000, system)
|
||||
val a = Inbox[ServiceA]("a")
|
||||
val r = Inbox[Registered[_]]("r")
|
||||
val ctx = new BehaviorTestkit("registertwo", behavior)
|
||||
val a = TestInbox[ServiceA]("a")
|
||||
val r = TestInbox[Registered[_]]("r")
|
||||
ctx.run(Register(ServiceKeyA, a.ref)(r.ref))
|
||||
r.receiveMsg() should be(Registered(ServiceKeyA, a.ref))
|
||||
val b = Inbox[ServiceB]("b")
|
||||
val b = TestInbox[ServiceB]("b")
|
||||
ctx.run(Register(ServiceKeyB, b.ref)(r.ref))
|
||||
r.receiveMsg() should be(Registered(ServiceKeyB, b.ref))
|
||||
val q = Inbox[Listing[_]]("q")
|
||||
val q = TestInbox[Listing[_]]("q")
|
||||
ctx.run(Find(ServiceKeyA)(q.ref))
|
||||
q.receiveMsg() should be(Listing(ServiceKeyA, Set(a.ref)))
|
||||
ctx.run(Find(ServiceKeyB)(q.ref))
|
||||
|
|
@ -75,15 +75,15 @@ class LocalReceptionistSpec extends TypedSpec with Eventually with StartSupport
|
|||
}
|
||||
|
||||
"must register two services with the same key" in {
|
||||
val ctx = new EffectfulActorContext("registertwosame", behavior, 1000, system)
|
||||
val a1 = Inbox[ServiceA]("a1")
|
||||
val r = Inbox[Registered[_]]("r")
|
||||
val ctx = new BehaviorTestkit("registertwosame", behavior)
|
||||
val a1 = TestInbox[ServiceA]("a1")
|
||||
val r = TestInbox[Registered[_]]("r")
|
||||
ctx.run(Register(ServiceKeyA, a1.ref)(r.ref))
|
||||
r.receiveMsg() should be(Registered(ServiceKeyA, a1.ref))
|
||||
val a2 = Inbox[ServiceA]("a2")
|
||||
val a2 = TestInbox[ServiceA]("a2")
|
||||
ctx.run(Register(ServiceKeyA, a2.ref)(r.ref))
|
||||
r.receiveMsg() should be(Registered(ServiceKeyA, a2.ref))
|
||||
val q = Inbox[Listing[_]]("q")
|
||||
val q = TestInbox[Listing[_]]("q")
|
||||
ctx.run(Find(ServiceKeyA)(q.ref))
|
||||
q.receiveMsg() should be(Listing(ServiceKeyA, Set(a1.ref, a2.ref)))
|
||||
ctx.run(Find(ServiceKeyB)(q.ref))
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@
|
|||
package akka.actor.typed
|
||||
package scaladsl
|
||||
|
||||
import akka.typed.testkit.{ EffectfulActorContext, TestKitSettings }
|
||||
import akka.typed.testkit.scaladsl.TestProbe
|
||||
import akka.testkit.typed.{ BehaviorTestkit, TestKitSettings }
|
||||
import akka.testkit.typed.scaladsl.TestProbe
|
||||
import scala.concurrent.duration.DurationInt
|
||||
|
||||
class ImmutablePartialSpec extends TypedSpec with StartSupport {
|
||||
|
|
@ -22,7 +22,7 @@ class ImmutablePartialSpec extends TypedSpec with StartSupport {
|
|||
probe.ref ! Command2
|
||||
Actor.same
|
||||
}
|
||||
val context = new EffectfulActorContext("ctx", behavior, 42, null)
|
||||
val context = new BehaviorTestkit("ctx", behavior)
|
||||
|
||||
context.run(Command1)
|
||||
context.currentBehavior shouldBe behavior
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ package akka.actor.typed
|
|||
package scaladsl
|
||||
|
||||
import akka.Done
|
||||
import akka.typed.testkit.TestKitSettings
|
||||
import akka.typed.testkit.scaladsl.TestProbe
|
||||
import akka.testkit.typed.TestKitSettings
|
||||
import akka.testkit.typed.scaladsl.TestProbe
|
||||
|
||||
final class OnSignalSpec extends TypedSpec with StartSupport {
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
package docs.akka.typed.testing.async
|
||||
|
||||
import akka.actor.typed.scaladsl.Actor
|
||||
import akka.actor.typed._
|
||||
import akka.testkit.typed.TestKit
|
||||
import akka.testkit.typed.scaladsl._
|
||||
import org.scalatest._
|
||||
|
||||
object BasicAsyncTestingSpec {
|
||||
//#under-test
|
||||
case class Ping(msg: String, response: ActorRef[Pong])
|
||||
case class Pong(msg: String)
|
||||
|
||||
val echoActor = Actor.immutable[Ping] { (_, msg) ⇒
|
||||
msg match {
|
||||
case Ping(m, replyTo) ⇒
|
||||
replyTo ! Pong(m)
|
||||
Actor.same
|
||||
}
|
||||
}
|
||||
//#under-test
|
||||
}
|
||||
|
||||
//#test-header
|
||||
class BasicAsyncTestingSpec extends TestKit(ActorSystem(Actor.empty, "BasicTestingSpec"))
|
||||
with WordSpecLike with BeforeAndAfterAll {
|
||||
//#test-header
|
||||
|
||||
import BasicAsyncTestingSpec._
|
||||
|
||||
"A testkit" must {
|
||||
"support verifying a response" in {
|
||||
//#test
|
||||
val probe = TestProbe[Pong]()
|
||||
val pinger = actorOf(echoActor, "ping")
|
||||
pinger ! Ping("hello", probe.ref)
|
||||
probe.expectMsg(Pong("hello"))
|
||||
//#test
|
||||
}
|
||||
}
|
||||
|
||||
//#test-shutdown
|
||||
override def afterAll(): Unit = shutdown()
|
||||
//#test-shutdown
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
package docs.akka.typed.testing.sync
|
||||
|
||||
//#imports
|
||||
import akka.actor.typed._
|
||||
import akka.actor.typed.scaladsl._
|
||||
import akka.testkit.typed._
|
||||
import akka.testkit.typed.Effect._
|
||||
//#imports
|
||||
import org.scalatest.{ Matchers, WordSpec }
|
||||
|
||||
object BasicSyncTestingSpec {
|
||||
//#child
|
||||
val childActor = Actor.immutable[String] { (_, _) ⇒
|
||||
Actor.same[String]
|
||||
}
|
||||
//#child
|
||||
|
||||
//#under-test
|
||||
sealed trait Cmd
|
||||
case object CreateAnonymousChild extends Cmd
|
||||
case class CreateChild(childName: String) extends Cmd
|
||||
case class SayHelloToChild(childName: String) extends Cmd
|
||||
case object SayHelloToAnonymousChild extends Cmd
|
||||
case class SayHello(who: ActorRef[String]) extends Cmd
|
||||
|
||||
val myBehaviour = Actor.immutablePartial[Cmd] {
|
||||
case (ctx, CreateChild(name)) ⇒
|
||||
ctx.spawn(childActor, name)
|
||||
Actor.same
|
||||
case (ctx, CreateAnonymousChild) ⇒
|
||||
ctx.spawnAnonymous(childActor)
|
||||
Actor.same
|
||||
case (ctx, SayHelloToChild(childName)) ⇒
|
||||
val child: ActorRef[String] = ctx.spawn(childActor, childName)
|
||||
child ! "hello"
|
||||
Actor.same
|
||||
case (ctx, SayHelloToAnonymousChild) ⇒
|
||||
val child: ActorRef[String] = ctx.spawnAnonymous(childActor)
|
||||
child ! "hello stranger"
|
||||
Actor.same
|
||||
case (_, SayHello(who)) ⇒
|
||||
who ! "hello"
|
||||
Actor.same
|
||||
//#under-test
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class BasicSyncTestingSpec extends WordSpec with Matchers {
|
||||
|
||||
import BasicSyncTestingSpec._
|
||||
|
||||
"Typed actor synchronous testing" must {
|
||||
|
||||
"record spawning" in {
|
||||
//#test-child
|
||||
val testKit = BehaviorTestkit(myBehaviour)
|
||||
testKit.run(CreateChild("child"))
|
||||
testKit.expectEffect(Spawned(childActor, "child"))
|
||||
//#test-child
|
||||
}
|
||||
|
||||
"record spawning anonymous" in {
|
||||
//#test-anonymous-child
|
||||
val testKit = BehaviorTestkit(myBehaviour)
|
||||
testKit.run(CreateAnonymousChild)
|
||||
testKit.expectEffect(SpawnedAnonymous(childActor))
|
||||
//#test-anonymous-child
|
||||
}
|
||||
|
||||
"record message sends" in {
|
||||
//#test-message
|
||||
val testKit = BehaviorTestkit(myBehaviour)
|
||||
val inbox = TestInbox[String]()
|
||||
testKit.run(SayHello(inbox.ref))
|
||||
inbox.expectMsg("hello")
|
||||
//#test-message
|
||||
}
|
||||
|
||||
"send a message to a spawned child" in {
|
||||
//#test-child-message
|
||||
val testKit = BehaviorTestkit(myBehaviour)
|
||||
testKit.run(SayHelloToChild("child"))
|
||||
val childInbox = testKit.childInbox[String]("child")
|
||||
childInbox.expectMsg("hello")
|
||||
//#test-child-message
|
||||
}
|
||||
|
||||
"send a message to an anonymous spawned child" in {
|
||||
//#test-child-message-anonymous
|
||||
val testKit = BehaviorTestkit(myBehaviour)
|
||||
testKit.run(SayHelloToAnonymousChild)
|
||||
// Anonymous actors are created as: $a $b etc
|
||||
val childInbox = testKit.childInbox[String]("$a")
|
||||
childInbox.expectMsg("hello stranger")
|
||||
//#test-child-message-anonymous
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ import java.lang.annotation.*;
|
|||
* Marks APIs that are meant to evolve towards becoming stable APIs, but are not stable APIs yet.
|
||||
*
|
||||
* Evolving interfaces MAY change from one patch release to another (i.e. 2.4.10 to 2.4.11) without up-front notice.
|
||||
* A best-effort approach is taken to not cause more breakage than really neccessary, and usual deprecation techniques
|
||||
* A best-effort approach is taken to not cause more breakage than really necessary, and usual deprecation techniques
|
||||
* are utilised while evolving these APIs, however there is NO strong guarantee regarding the source or binary
|
||||
* compatibility of APIs marked using this annotation.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ import org.scalatest.concurrent.ScalaFutures
|
|||
|
||||
import scala.concurrent.duration._
|
||||
import akka.actor.typed.Behavior
|
||||
import akka.typed.testkit.TestKitSettings
|
||||
import akka.typed.testkit.scaladsl.TestProbe
|
||||
import akka.testkit.typed.TestKitSettings
|
||||
import akka.testkit.typed.scaladsl.TestProbe
|
||||
import akka.persistence.typed.scaladsl.PersistentActor
|
||||
import akka.persistence.typed.scaladsl.PersistentActor.PersistNothing
|
||||
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ import akka.cluster.typed.Cluster
|
|||
import akka.actor.typed.internal.adapter.ActorSystemAdapter
|
||||
import akka.actor.typed.scaladsl.Actor
|
||||
import akka.actor.typed.scaladsl.adapter._
|
||||
import akka.typed.testkit.TestKitSettings
|
||||
import akka.typed.testkit.scaladsl.TestProbe
|
||||
import akka.testkit.typed.TestKitSettings
|
||||
import akka.testkit.typed.scaladsl.TestProbe
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import org.scalatest.concurrent.ScalaFutures
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ package akka.cluster.typed;
|
|||
|
||||
import akka.cluster.ClusterEvent;
|
||||
import akka.actor.typed.ActorSystem;
|
||||
import akka.typed.testkit.TestKitSettings;
|
||||
import akka.typed.testkit.javadsl.TestProbe;
|
||||
import akka.testkit.typed.TestKitSettings;
|
||||
import akka.testkit.typed.javadsl.TestProbe;
|
||||
import com.typesafe.config.Config;
|
||||
import com.typesafe.config.ConfigFactory;
|
||||
import org.junit.Test;
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ import akka.actor.typed.{ ActorRef, ActorSystem, Behavior, StartSupport, TypedSp
|
|||
import akka.actor.typed.scaladsl.Actor
|
||||
import akka.actor.typed.scaladsl.AskPattern._
|
||||
import akka.actor.typed.scaladsl.adapter._
|
||||
import akka.typed.testkit.TestKitSettings
|
||||
import akka.typed.testkit.scaladsl._
|
||||
import akka.testkit.typed.TestKitSettings
|
||||
import akka.testkit.typed.scaladsl._
|
||||
import akka.cluster.Cluster
|
||||
import akka.cluster.ddata.{ GCounter, GCounterKey, ReplicatedData }
|
||||
import akka.cluster.ddata.typed.scaladsl.Replicator._
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ import akka.cluster.MemberStatus
|
|||
import akka.actor.typed.TypedSpec
|
||||
import akka.actor.typed.internal.adapter.ActorSystemAdapter
|
||||
import akka.actor.typed.scaladsl.adapter._
|
||||
import akka.typed.testkit.TestKitSettings
|
||||
import akka.typed.testkit.scaladsl.TestProbe
|
||||
import akka.testkit.typed.TestKitSettings
|
||||
import akka.testkit.typed.scaladsl.TestProbe
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import org.scalatest.concurrent.ScalaFutures
|
||||
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ import akka.actor.ExtendedActorSystem
|
|||
import akka.serialization.SerializerWithStringManifest
|
||||
import akka.actor.typed.scaladsl.Actor
|
||||
import akka.actor.typed.scaladsl.adapter._
|
||||
import akka.typed.testkit.TestKitSettings
|
||||
import akka.typed.testkit.scaladsl.TestProbe
|
||||
import akka.testkit.typed.TestKitSettings
|
||||
import akka.testkit.typed.scaladsl.TestProbe
|
||||
import akka.actor.typed.{ ActorRef, ActorRefResolver, Props, TypedSpec }
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import org.scalatest.concurrent.ScalaFutures
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ import akka.actor.typed.Props
|
|||
import akka.actor.typed.TypedSpec
|
||||
import akka.persistence.typed.scaladsl.PersistentActor
|
||||
import akka.persistence.typed.scaladsl.PersistentActor.{ CommandHandler, Effect }
|
||||
import akka.typed.testkit.TestKitSettings
|
||||
import akka.typed.testkit.scaladsl.TestProbe
|
||||
import akka.testkit.typed.TestKitSettings
|
||||
import akka.testkit.typed.scaladsl.TestProbe
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import org.scalatest.concurrent.ScalaFutures
|
||||
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ import akka.actor.typed.internal.adapter.ActorSystemAdapter
|
|||
import akka.actor.typed.receptionist.Receptionist
|
||||
import akka.actor.typed.scaladsl.Actor
|
||||
import akka.actor.typed.scaladsl.adapter._
|
||||
import akka.typed.testkit.TestKitSettings
|
||||
import akka.typed.testkit.scaladsl.TestProbe
|
||||
import akka.testkit.typed.TestKitSettings
|
||||
import akka.testkit.typed.scaladsl.TestProbe
|
||||
import com.typesafe.config.ConfigFactory
|
||||
|
||||
import scala.concurrent.Await
|
||||
|
|
|
|||
|
|
@ -9,7 +9,13 @@ This module is currently marked as @ref:[may change](common/may-change.md) in th
|
|||
|
||||
@@@
|
||||
|
||||
## Dependency
|
||||
### Migrating to 2.5.9
|
||||
|
||||
* `EffectfulActorContext` has been renamed to `BehaviourTestKit`
|
||||
* `Inbox` has been renamed to `TestInbox` to allign with `TestProbe`
|
||||
* Separated into modules e.g. `akka-actor-typed` `akka-persistence-typed` along with matching package names
|
||||
|
||||
### Dependency
|
||||
|
||||
To use typed actors add the following dependency:
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
TODO
|
||||
|
||||
## Dependency
|
||||
### Dependency
|
||||
|
||||
@@dependency [sbt,Maven,Gradle] {
|
||||
group=com.typesafe.akka
|
||||
|
|
|
|||
|
|
@ -1,9 +1,5 @@
|
|||
# Cluster
|
||||
|
||||
TODO
|
||||
|
||||
## Dependency
|
||||
|
||||
sbt
|
||||
: @@@vars
|
||||
```
|
||||
|
|
@ -29,4 +25,6 @@ Maven
|
|||
<version>$akka.version$</version>
|
||||
</dependency>
|
||||
```
|
||||
@@@
|
||||
@@@
|
||||
|
||||
TODO
|
||||
|
|
|
|||
|
|
@ -11,33 +11,165 @@ This module is currently marked as @ref:[may change](common/may-change.md) in th
|
|||
@@@
|
||||
|
||||
To use the testkit add the following dependency:
|
||||
## Dependency
|
||||
To use the testkit add the following dependency:
|
||||
|
||||
sbt
|
||||
: @@@vars
|
||||
```
|
||||
"com.typesafe.akka" %% "akka-testkit-typed" % "$akka.version$"
|
||||
```
|
||||
@@@
|
||||
@@dependency [sbt,Maven,Gradle] {
|
||||
group=com.typesafe.akka
|
||||
artifact=akka-testkit-typed_2.11
|
||||
version=$version$
|
||||
scope=test
|
||||
}
|
||||
|
||||
Testing can either be done asynchronously using a real `ActorSystem` or synchronously on the testing thread using the `BehaviousTestKit`.
|
||||
|
||||
Gradle
|
||||
: @@@vars
|
||||
```
|
||||
dependencies {
|
||||
compile group: 'com.typesafe.akka', name: 'akka-testkit-typed_2.11', version: '$akka.version$'
|
||||
}
|
||||
```
|
||||
@@@
|
||||
For testing logic in a `Behavior` in isolation synchronous testing is preferred. For testing interactions between multiple
|
||||
actors a more realistic asynchronous test is preferred.
|
||||
|
||||
Maven
|
||||
: @@@vars
|
||||
```
|
||||
<dependency>
|
||||
<groupId>com.typesafe.akka</groupId>
|
||||
<artifactId>akka-testkit-typed_$scala.binary_version$</artifactId>
|
||||
<version>$akka.version$</version>
|
||||
</dependency>
|
||||
```
|
||||
@@@
|
||||
Certain `Behavior`s will be hard to test synchronously e.g. if they spawn Future's and you rely on a callback to complete
|
||||
before observing the effect you want to test. Further support for controlling the scheduler and execution context used
|
||||
will be added.
|
||||
|
||||
TODO
|
||||
## Synchronous behaviour testing
|
||||
|
||||
The following demonstrates how to test:
|
||||
|
||||
* Spawning child actors
|
||||
* Spawning child actors anonymously
|
||||
* Sending a message either as a reply or to another actor
|
||||
* Sending a message to a child actor
|
||||
|
||||
The examples below require the following imports:
|
||||
|
||||
Scala
|
||||
: @@snip [BasicSyncTestingSpec.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/testing/sync/BasicSyncTestingSpec.scala) { #imports }
|
||||
|
||||
Java
|
||||
: @@snip [BasicSyncTestingTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/testing/sync/BasicSyncTestingTest.java) { #imports }
|
||||
|
||||
Each of the tests are testing an actor that based on the message executes a different effect to be tested:
|
||||
|
||||
Scala
|
||||
: @@snip [BasicSyncTestingSpec.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/testing/sync/BasicSyncTestingSpec.scala) { #under-test }
|
||||
|
||||
Java
|
||||
: @@snip [BasicSyncTestingTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/testing/sync/BasicSyncTestingTest.java) { #under-test }
|
||||
|
||||
For creating a child actor a noop actor is created:
|
||||
|
||||
|
||||
Scala
|
||||
: @@snip [BasicSyncTestingSpec.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/testing/sync/BasicSyncTestingSpec.scala) { #child }
|
||||
|
||||
Java
|
||||
: @@snip [BasicSyncTestingTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/testing/sync/BasicSyncTestingTest.java) { #child }
|
||||
|
||||
All of the tests make use of the `BehaviourTestkit` to avoid the need for a real `ActorContext`. Some of the tests
|
||||
make use of the `TestInbox` which allows the creation of an `ActorRef` that can be used for synchronous testing, similar to the
|
||||
`TestProbe` used for asynchronous testing.
|
||||
|
||||
|
||||
### Spawning children
|
||||
|
||||
With a name:
|
||||
|
||||
Scala
|
||||
: @@snip [BasicSyncTestingSpec.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/testing/sync/BasicSyncTestingSpec.scala) { #test-child }
|
||||
|
||||
Java
|
||||
: @@snip [BasicSyncTestingTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/testing/sync/BasicSyncTestingTest.java) { #test-child }
|
||||
|
||||
Anonymously:
|
||||
|
||||
Scala
|
||||
: @@snip [BasicSyncTestingSpec.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/testing/sync/BasicSyncTestingSpec.scala) { #test-anonymous-child }
|
||||
|
||||
Java
|
||||
: @@snip [BasicSyncTestingTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/testing/sync/BasicSyncTestingTest.java) { #test-anonymous-child }
|
||||
|
||||
### Sending messages
|
||||
|
||||
For testing sending a message a `TestInbox` is created that provides an `ActorRef` and methods to assert against the
|
||||
messages that have been sent to it.
|
||||
|
||||
Scala
|
||||
: @@snip [BasicSyncTestingSpec.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/testing/sync/BasicSyncTestingSpec.scala) { #test-message }
|
||||
|
||||
Java
|
||||
: @@snip [BasicSyncTestingTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/testing/sync/BasicSyncTestingTest.java) { #test-message }
|
||||
|
||||
Another use case is sending a message to a child actor you can do this by looking up the 'TestInbox' for
|
||||
a child actor from the 'BehaviorTestKit':
|
||||
|
||||
Scala
|
||||
: @@snip [BasicSyncTestingSpec.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/testing/sync/BasicSyncTestingSpec.scala) { #test-child-message }
|
||||
|
||||
Java
|
||||
: @@snip [BasicSyncTestingTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/testing/sync/BasicSyncTestingTest.java) { #test-child-message }
|
||||
|
||||
For anonymous children the actor names are generated in a deterministic way:
|
||||
|
||||
Scala
|
||||
: @@snip [BasicSyncTestingSpec.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/testing/sync/BasicSyncTestingSpec.scala) { #test-child-message-anonymous }
|
||||
|
||||
Java
|
||||
: @@snip [BasicSyncTestingTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/testing/sync/BasicSyncTestingTest.java) { #test-child-message-anonymous }
|
||||
|
||||
### Testing other effects
|
||||
|
||||
The `BehaviorTestkit` keeps track other effects you can verify, look at the sub-classes of `akka.testkit.typed.Effect`
|
||||
|
||||
* SpawnedAdapter
|
||||
* Stopped
|
||||
* Watched
|
||||
* Unwatched
|
||||
* Scheduled
|
||||
|
||||
See the other public methods and API documentation on `BehaviourTestkit` for other types of verification.
|
||||
|
||||
## Asynchronous testing
|
||||
|
||||
Asynchronous testing uses a real `ActorSystem` that allows you to test your Actors in a realistic environment.
|
||||
|
||||
The minimal setup consists of the test procedure, which provides the desired stimuli, the actor under test,
|
||||
and an actor receiving replies. Bigger systems replace the actor under test with a network of actors, apply stimuli
|
||||
at varying injection points and arrange results to be sent from different emission points, but the basic principle stays
|
||||
the same in that a single procedure drives the test.
|
||||
|
||||
### Basic example
|
||||
|
||||
Actor under test:
|
||||
|
||||
Scala
|
||||
: @@snip [BasicAsyncTestingSpec.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/testing/async/BasicAsyncTestingSpec.scala) { #under-test }
|
||||
|
||||
Java
|
||||
: @@snip [BasicAsyncTestingTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/testing/async/BasicAsyncTestingTest.java) { #under-test }
|
||||
|
||||
Tests can optionally extend `TestKit` or include the `TestKitBase`.
|
||||
|
||||
Scala
|
||||
: @@snip [BasicTestingSpec.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/testing/async/BasicAsyncTestingSpec.scala) { #test-header }
|
||||
|
||||
Java
|
||||
: @@snip [BasicAsyncTestingTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/testing/async/BasicAsyncTestingTest.java) { #test-header }
|
||||
|
||||
Your test is responsible for shutting down the `ActorSystem` e.g. using `BeforeAndAfterAll` when using ScalaTest
|
||||
|
||||
Scala
|
||||
: @@snip [BasicTestingSpec.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/testing/async/BasicAsyncTestingSpec.scala) { #test-shutdown }
|
||||
|
||||
Java
|
||||
: @@snip [BasicAsyncTestingTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/testing/async/BasicAsyncTestingTest.java) { #test-shutdown }
|
||||
|
||||
The following demonstrates:
|
||||
|
||||
* Creating a typed actor from the `TestKit`'s system using `actorOf`
|
||||
* Creating a typed `TestProbe`
|
||||
* Verifying that the actor under test responds via the `TestProbe`
|
||||
|
||||
Scala
|
||||
: @@snip [BasicTestingSpec.scala]($akka$/akka-actor-typed-tests/src/test/scala/docs/akka/typed/testing/async/BasicAsyncTestingSpec.scala) { #test }
|
||||
|
||||
Java
|
||||
: @@snip [BasicAsyncTestingTest.java]($akka$/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/testing/async/BasicAsyncTestingTest.java) { #test }
|
||||
|
||||
|
|
|
|||
|
|
@ -6,13 +6,10 @@ package akka.persistence.typed.scaladsl
|
|||
import scala.concurrent.duration._
|
||||
import akka.actor.typed.{ ActorRef, ActorSystem, Behavior, StartSupport, SupervisorStrategy, Terminated, TypedSpec }
|
||||
import akka.actor.typed.scaladsl.Actor
|
||||
import akka.actor.typed.scaladsl.AskPattern._
|
||||
import akka.actor.typed.scaladsl.adapter._
|
||||
import akka.typed.testkit.TestKitSettings
|
||||
import akka.typed.testkit.scaladsl._
|
||||
import akka.testkit.typed.TestKitSettings
|
||||
import akka.testkit.typed.scaladsl._
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import org.scalatest.concurrent.Eventually
|
||||
import akka.util.Timeout
|
||||
import akka.persistence.typed.scaladsl.PersistentActor._
|
||||
|
||||
object PersistentActorSpec {
|
||||
|
|
|
|||
|
|
@ -4,18 +4,21 @@
|
|||
package akka.actor.typed
|
||||
package internal
|
||||
|
||||
import akka.{ actor ⇒ a, event ⇒ e }
|
||||
|
||||
import scala.concurrent._
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import java.util.concurrent.ThreadFactory
|
||||
|
||||
import akka.annotation.InternalApi
|
||||
import akka.event.Logging
|
||||
import akka.event.typed.{ BusLogging, DefaultLoggingFilter, EventStream }
|
||||
import akka.event.typed.{ BusLogging, DefaultLoggingFilter }
|
||||
import akka.util.Timeout
|
||||
import akka.{ actor ⇒ a, event ⇒ e }
|
||||
import com.typesafe.config.ConfigFactory
|
||||
|
||||
private[typed] class ActorSystemStub(val name: String)
|
||||
import scala.concurrent._
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
@InternalApi private[akka] class ActorSystemStub(val name: String)
|
||||
extends ActorSystem[Nothing] with ActorRef[Nothing] with ActorRefImpl[Nothing] {
|
||||
|
||||
override val path: a.ActorPath = a.RootActorPath(a.Address("akka", name)) / "user"
|
||||
|
|
@ -3,9 +3,10 @@
|
|||
*/
|
||||
package akka.actor.typed.internal
|
||||
|
||||
import scala.concurrent.ExecutionContextExecutor
|
||||
import java.util.LinkedList
|
||||
|
||||
import scala.concurrent.ExecutionContextExecutor
|
||||
|
||||
class ControlledExecutor extends ExecutionContextExecutor {
|
||||
private val tasks = new LinkedList[Runnable]
|
||||
|
||||
|
|
@ -4,11 +4,17 @@
|
|||
package akka.actor.typed
|
||||
package internal
|
||||
|
||||
import akka.{ actor ⇒ a }
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
|
||||
import akka.annotation.InternalApi
|
||||
import akka.{ actor ⇒ a }
|
||||
|
||||
import scala.annotation.tailrec
|
||||
|
||||
private[typed] class DebugRef[T](override val path: a.ActorPath, override val isLocal: Boolean)
|
||||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
@InternalApi private[akka] class DebugRef[T](override val path: a.ActorPath, override val isLocal: Boolean)
|
||||
extends ActorRef[T] with ActorRefImpl[T] {
|
||||
|
||||
private val q = new ConcurrentLinkedQueue[Either[SystemMessage, T]]
|
||||
|
|
@ -0,0 +1,206 @@
|
|||
/**
|
||||
* Copyright (C) 2014-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package akka.testkit.typed
|
||||
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
|
||||
import akka.actor.typed.{ ActorRef, Behavior, PostStop, Props, Signal }
|
||||
import akka.annotation.{ ApiMayChange, InternalApi }
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.collection.immutable
|
||||
import scala.util.control.Exception.Catcher
|
||||
import scala.util.control.NonFatal
|
||||
import scala.concurrent.duration.{ Duration, FiniteDuration }
|
||||
|
||||
import scala.language.existentials
|
||||
|
||||
/**
|
||||
* All tracked effects must extend implement this type. It is deliberately
|
||||
* not sealed in order to allow extensions.
|
||||
*/
|
||||
abstract class Effect
|
||||
|
||||
// TODO offer a better Java API for default params that are rarely used e.g. props
|
||||
@ApiMayChange
|
||||
object Effect {
|
||||
|
||||
abstract class SpawnedEffect extends Effect
|
||||
|
||||
@SerialVersionUID(1L) final case class Spawned(behavior: Behavior[_], childName: String, props: Props = Props.empty) extends SpawnedEffect
|
||||
@SerialVersionUID(1L) final case class SpawnedAnonymous(behaviour: Behavior[_], props: Props = Props.empty) extends SpawnedEffect
|
||||
@SerialVersionUID(1L) final case object SpawnedAdapter extends SpawnedEffect
|
||||
@SerialVersionUID(1L) final case class Stopped(childName: String) extends Effect
|
||||
@SerialVersionUID(1L) final case class Watched[T](other: ActorRef[T]) extends Effect
|
||||
@SerialVersionUID(1L) final case class Unwatched[T](other: ActorRef[T]) extends Effect
|
||||
@SerialVersionUID(1L) final case class ReceiveTimeoutSet[T](d: Duration, msg: T) extends Effect
|
||||
@SerialVersionUID(1L) final case class Scheduled[U](delay: FiniteDuration, target: ActorRef[U], msg: U) extends Effect
|
||||
@SerialVersionUID(1L) case object NoEffects extends Effect
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
@InternalApi private[akka] class EffectfulActorContext[T](name: String) extends StubbedActorContext[T](name) {
|
||||
|
||||
import Effect._
|
||||
import akka.{ actor ⇒ a }
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
@InternalApi private[akka] val effectQueue = new ConcurrentLinkedQueue[Effect]
|
||||
|
||||
override def spawnAnonymous[U](behavior: Behavior[U], props: Props = Props.empty): ActorRef[U] = {
|
||||
val ref = super.spawnAnonymous(behavior, props)
|
||||
effectQueue.offer(SpawnedAnonymous(behavior, props))
|
||||
ref
|
||||
}
|
||||
|
||||
override def spawnAdapter[U](f: U ⇒ T): ActorRef[U] = {
|
||||
spawnAdapter(f, "")
|
||||
}
|
||||
|
||||
override def spawnAdapter[U](f: U ⇒ T, name: String): ActorRef[U] = {
|
||||
val ref = super.spawnAdapter(f, name)
|
||||
effectQueue.offer(SpawnedAdapter)
|
||||
ref
|
||||
}
|
||||
override def spawn[U](behavior: Behavior[U], name: String, props: Props = Props.empty): ActorRef[U] = {
|
||||
effectQueue.offer(Spawned(behavior, name, props))
|
||||
super.spawn(behavior, name, props)
|
||||
}
|
||||
override def stop[U](child: ActorRef[U]): Boolean = {
|
||||
effectQueue.offer(Stopped(child.path.name))
|
||||
super.stop(child)
|
||||
}
|
||||
override def watch[U](other: ActorRef[U]): Unit = {
|
||||
effectQueue.offer(Watched(other))
|
||||
super.watch(other)
|
||||
}
|
||||
override def unwatch[U](other: ActorRef[U]): Unit = {
|
||||
effectQueue.offer(Unwatched(other))
|
||||
super.unwatch(other)
|
||||
}
|
||||
override def setReceiveTimeout(d: FiniteDuration, msg: T): Unit = {
|
||||
effectQueue.offer(ReceiveTimeoutSet(d, msg))
|
||||
super.setReceiveTimeout(d, msg)
|
||||
}
|
||||
override def cancelReceiveTimeout(): Unit = {
|
||||
effectQueue.offer(ReceiveTimeoutSet(Duration.Undefined, null))
|
||||
super.cancelReceiveTimeout()
|
||||
}
|
||||
override def schedule[U](delay: FiniteDuration, target: ActorRef[U], msg: U): a.Cancellable = {
|
||||
effectQueue.offer(Scheduled(delay, target, msg))
|
||||
super.schedule(delay, target, msg)
|
||||
}
|
||||
}
|
||||
|
||||
@ApiMayChange
|
||||
object BehaviorTestkit {
|
||||
def apply[T](initialBehavior: Behavior[T], name: String): BehaviorTestkit[T] =
|
||||
new BehaviorTestkit[T](name, initialBehavior)
|
||||
def apply[T](initialBehavior: Behavior[T]): BehaviorTestkit[T] =
|
||||
apply(initialBehavior, "testkit")
|
||||
|
||||
/**
|
||||
* JAVA API
|
||||
*/
|
||||
def create[T](initialBehavior: Behavior[T], name: String): BehaviorTestkit[T] =
|
||||
new BehaviorTestkit[T](name, initialBehavior)
|
||||
/**
|
||||
* JAVA API
|
||||
*/
|
||||
def create[T](initialBehavior: Behavior[T]): BehaviorTestkit[T] =
|
||||
apply(initialBehavior, "ctx")
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for testing [[Behavior]]s. Stores all effects e.g. Spawning of children,
|
||||
* watching and offers access to what effects have taken place.
|
||||
*/
|
||||
@ApiMayChange
|
||||
class BehaviorTestkit[T](_name: String, _initialBehavior: Behavior[T]) {
|
||||
|
||||
import Effect._
|
||||
|
||||
// really this should be private, make so when we port out tests that need it
|
||||
private[akka] val ctx = new EffectfulActorContext[T](_name)
|
||||
|
||||
/**
|
||||
* Requests the oldest [[Effect]] or [[NoEffects]] if no effects
|
||||
* have taken place. The effect is consumed, subsequent calls won't
|
||||
* will not include this effect.
|
||||
*/
|
||||
def retrieveEffect(): Effect = ctx.effectQueue.poll() match {
|
||||
case null ⇒ NoEffects
|
||||
case x ⇒ x
|
||||
}
|
||||
|
||||
def childInbox[U](name: String): TestInbox[U] = {
|
||||
val inbox = ctx.childInbox[U](name)
|
||||
assert(inbox.isDefined, s"Child not created: $name. Children created: [${ctx.childrenNames.mkString(",")}]")
|
||||
inbox.get
|
||||
}
|
||||
|
||||
def selfInbox(): TestInbox[T] = ctx.selfInbox
|
||||
|
||||
/**
|
||||
* Requests all the effects. The effects are consumed, subsequent calls will only
|
||||
* see new effects.
|
||||
*/
|
||||
def retrieveAllEffects(): immutable.Seq[Effect] = {
|
||||
@tailrec def rec(acc: List[Effect]): List[Effect] = ctx.effectQueue.poll() match {
|
||||
case null ⇒ acc.reverse
|
||||
case x ⇒ rec(x :: acc)
|
||||
}
|
||||
|
||||
rec(Nil)
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the oldest effect is the expectedEffect. Removing it from
|
||||
* further assertions.
|
||||
*/
|
||||
def expectEffect(expectedEffect: Effect): Unit = {
|
||||
ctx.effectQueue.poll() match {
|
||||
case null ⇒ assert(assertion = false, s"expected: $expectedEffect but no effects were recorded")
|
||||
case effect ⇒ assert(expectedEffect == effect, s"expected: $expectedEffect but for $effect")
|
||||
}
|
||||
}
|
||||
|
||||
private var current = Behavior.validateAsInitial(Behavior.undefer(_initialBehavior, ctx))
|
||||
|
||||
def currentBehavior: Behavior[T] = current
|
||||
def isAlive: Boolean = Behavior.isAlive(current)
|
||||
|
||||
private def handleException: Catcher[Unit] = {
|
||||
case NonFatal(e) ⇒
|
||||
try Behavior.canonicalize(Behavior.interpretSignal(current, ctx, PostStop), current, ctx) // TODO why canonicalize here?
|
||||
catch {
|
||||
case NonFatal(_) ⇒ /* ignore, real is logging */
|
||||
}
|
||||
throw e
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the msg to the behavior and record any [[Effect]]s
|
||||
*/
|
||||
def run(msg: T): Unit = {
|
||||
try {
|
||||
current = Behavior.canonicalize(Behavior.interpretMessage(current, ctx, msg), current, ctx)
|
||||
} catch handleException
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the signal to the beheavior and record any [[Effect]]s
|
||||
*/
|
||||
def signal(signal: Signal): Unit = {
|
||||
try {
|
||||
current = Behavior.canonicalize(Behavior.interpretSignal(current, ctx, signal), current, ctx)
|
||||
} catch handleException
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package akka.typed.testkit
|
||||
package akka.testkit.typed
|
||||
|
||||
import akka.actor.InvalidMessageException
|
||||
import akka.{ actor ⇒ untyped }
|
||||
|
|
@ -11,7 +11,7 @@ import scala.collection.immutable.TreeMap
|
|||
import scala.concurrent.ExecutionContextExecutor
|
||||
import scala.concurrent.duration.FiniteDuration
|
||||
import akka.annotation.InternalApi
|
||||
import akka.actor.typed.internal.{ ActorContextImpl, ActorRefImpl }
|
||||
import akka.actor.typed.internal.{ ActorContextImpl, ActorRefImpl, ActorSystemStub }
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.util.control.NonFatal
|
||||
|
|
@ -31,7 +31,7 @@ private[akka] final class FunctionRef[-T](
|
|||
if (msg == null) throw InvalidMessageException("[null] is not an allowed message")
|
||||
if (isAlive)
|
||||
try send(msg, this) catch {
|
||||
case NonFatal(ex) ⇒ // nothing we can do here
|
||||
case NonFatal(_) ⇒ // nothing we can do here
|
||||
}
|
||||
else () // we don’t have deadLetters available
|
||||
}
|
||||
|
|
@ -39,12 +39,12 @@ private[akka] final class FunctionRef[-T](
|
|||
import internal._
|
||||
|
||||
override def sendSystem(signal: SystemMessage): Unit = signal match {
|
||||
case internal.Create() ⇒ // nothing to do
|
||||
case internal.DeathWatchNotification(ref, cause) ⇒ // we’re not watching, and we’re not a parent either
|
||||
case internal.Terminate() ⇒ doTerminate()
|
||||
case internal.Watch(watchee, watcher) ⇒ if (watchee == this && watcher != this) addWatcher(watcher.sorryForNothing)
|
||||
case internal.Unwatch(watchee, watcher) ⇒ if (watchee == this && watcher != this) remWatcher(watcher.sorryForNothing)
|
||||
case NoMessage ⇒ // nothing to do
|
||||
case internal.Create() ⇒ // nothing to do
|
||||
case internal.DeathWatchNotification(_, _) ⇒ // we’re not watching, and we’re not a parent either
|
||||
case internal.Terminate() ⇒ doTerminate()
|
||||
case internal.Watch(watchee, watcher) ⇒ if (watchee == this && watcher != this) addWatcher(watcher.sorryForNothing)
|
||||
case internal.Unwatch(watchee, watcher) ⇒ if (watchee == this && watcher != this) remWatcher(watcher.sorryForNothing)
|
||||
case NoMessage ⇒ // nothing to do
|
||||
}
|
||||
|
||||
override def isLocal = true
|
||||
|
|
@ -105,27 +105,37 @@ private[typed] object WatchableRef {
|
|||
}
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*
|
||||
* An [[ActorContext]] for synchronous execution of a [[Behavior]] that
|
||||
* provides only stubs for the effects an Actor can perform and replaces
|
||||
* created child Actors by a synchronous Inbox (see `Inbox.sync`).
|
||||
*
|
||||
* See [[EffectfulActorContext]] for more advanced uses.
|
||||
* See [[BehaviorTestkit]] for more advanced uses.
|
||||
*/
|
||||
class StubbedActorContext[T](
|
||||
val name: String,
|
||||
override val mailboxCapacity: Int,
|
||||
override val system: ActorSystem[Nothing]) extends ActorContextImpl[T] {
|
||||
@InternalApi private[akka] class StubbedActorContext[T](
|
||||
val name: String) extends ActorContextImpl[T] {
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
@InternalApi private[akka] val selfInbox = TestInbox[T](name)
|
||||
|
||||
val selfInbox = Inbox[T](name)
|
||||
override val self = selfInbox.ref
|
||||
override val system = new ActorSystemStub("StubbedActorContext")
|
||||
// Not used for a stubbed actor context
|
||||
override def mailboxCapacity = 1
|
||||
|
||||
private var _children = TreeMap.empty[String, Inbox[_]]
|
||||
private val childName = Iterator from 1 map (Helpers.base64(_))
|
||||
private var _children = TreeMap.empty[String, TestInbox[_]]
|
||||
private val childName = Iterator from 0 map (Helpers.base64(_))
|
||||
|
||||
override def children: Iterable[ActorRef[Nothing]] = _children.values map (_.ref)
|
||||
def childrenNames: Iterable[String] = _children.keys
|
||||
|
||||
override def child(name: String): Option[ActorRef[Nothing]] = _children get name map (_.ref)
|
||||
|
||||
override def spawnAnonymous[U](behavior: Behavior[U], props: Props = Props.empty): ActorRef[U] = {
|
||||
val i = Inbox[U](childName.next())
|
||||
val i = TestInbox[U](childName.next())
|
||||
_children += i.ref.path.name → i
|
||||
i.ref
|
||||
}
|
||||
|
|
@ -134,7 +144,7 @@ class StubbedActorContext[T](
|
|||
case Some(_) ⇒ throw untyped.InvalidActorNameException(s"actor name $name is already taken")
|
||||
case None ⇒
|
||||
// FIXME correct child path for the Inbox ref
|
||||
val i = Inbox[U](name)
|
||||
val i = TestInbox[U](name)
|
||||
_children += name → i
|
||||
i.ref
|
||||
}
|
||||
|
|
@ -160,6 +170,7 @@ class StubbedActorContext[T](
|
|||
override def isCancelled = true
|
||||
}
|
||||
|
||||
// TODO allow overriding of this
|
||||
override def executionContext: ExecutionContextExecutor = system.executionContext
|
||||
|
||||
/**
|
||||
|
|
@ -168,7 +179,7 @@ class StubbedActorContext[T](
|
|||
@InternalApi private[akka] def internalSpawnAdapter[U](f: U ⇒ T, name: String): ActorRef[U] = {
|
||||
|
||||
val n = if (name != "") s"${childName.next()}-$name" else childName.next()
|
||||
val i = Inbox[U](n)
|
||||
val i = TestInbox[U](n)
|
||||
_children += i.ref.path.name → i
|
||||
|
||||
new FunctionRef[U](
|
||||
|
|
@ -181,8 +192,8 @@ class StubbedActorContext[T](
|
|||
* Retrieve the inbox representing the given child actor. The passed ActorRef must be one that was returned
|
||||
* by one of the spawn methods earlier.
|
||||
*/
|
||||
def childInbox[U](child: ActorRef[U]): Inbox[U] = {
|
||||
val inbox = _children(child.path.name).asInstanceOf[Inbox[U]]
|
||||
def childInbox[U](child: ActorRef[U]): TestInbox[U] = {
|
||||
val inbox = _children(child.path.name).asInstanceOf[TestInbox[U]]
|
||||
if (inbox.ref != child) throw new IllegalArgumentException(s"$child is not a child of $this")
|
||||
inbox
|
||||
}
|
||||
|
|
@ -190,7 +201,7 @@ class StubbedActorContext[T](
|
|||
/**
|
||||
* Retrieve the inbox representing the child actor with the given name.
|
||||
*/
|
||||
def childInbox[U](name: String): Inbox[U] = _children(name).asInstanceOf[Inbox[U]]
|
||||
def childInbox[U](name: String): Option[TestInbox[U]] = _children.get(name).map(_.asInstanceOf[TestInbox[U]])
|
||||
|
||||
/**
|
||||
* Remove the given inbox from the list of children, for example after
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package akka.typed.testkit
|
||||
package akka.testkit.typed
|
||||
|
||||
import akka.event.Logging.{ LogEvent, StdOutLogger }
|
||||
import akka.testkit.{ EventFilter, TestEvent ⇒ TE }
|
||||
|
|
@ -16,7 +16,7 @@ import akka.event.typed.Logger
|
|||
*
|
||||
* <pre><code>
|
||||
* akka.typed {
|
||||
* loggers = ["akka.typed.testkit.TestEventListener"]
|
||||
* loggers = ["akka.testkit.typed.TestEventListener"]
|
||||
* }
|
||||
* </code></pre>
|
||||
*/
|
||||
|
|
@ -1,23 +1,26 @@
|
|||
/**
|
||||
* Copyright (C) 2014-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package akka.typed.testkit
|
||||
package akka.testkit.typed
|
||||
|
||||
import java.util.concurrent.{ ConcurrentLinkedQueue, ThreadLocalRandom }
|
||||
|
||||
import akka.actor.{ Address, RootActorPath }
|
||||
import akka.actor.typed.ActorRef
|
||||
import akka.annotation.ApiMayChange
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.collection.immutable
|
||||
|
||||
/**
|
||||
* Utility for receiving messages outside of an actor. No methods are provided
|
||||
* for synchronously awaiting a message, this is primarily useful for synchronous
|
||||
* tests of behaviors that send messages to other actors, where an Inbox’s ActorRef
|
||||
* can conveniently be used as a stub.
|
||||
* Utility for use as an [[ActorRef]] when synchronously testing [[akka.actor.typed.Behavior]]
|
||||
* to be used along with [[BehaviorTestkit]].
|
||||
*
|
||||
* See [[akka.testkit.typed.scaladsl.TestProbe]] for asynchronous testing.
|
||||
*/
|
||||
class Inbox[T](name: String) {
|
||||
@ApiMayChange
|
||||
class TestInbox[T](name: String) {
|
||||
def this() = this("inbox")
|
||||
|
||||
private val q = new ConcurrentLinkedQueue[T]
|
||||
|
||||
|
|
@ -27,22 +30,40 @@ class Inbox[T](name: String) {
|
|||
new FunctionRef[T](path, (msg, self) ⇒ q.add(msg), (self) ⇒ ())
|
||||
}
|
||||
|
||||
/**
|
||||
* Get and remove the oldest message
|
||||
*/
|
||||
def receiveMsg(): T = q.poll() match {
|
||||
case null ⇒ throw new NoSuchElementException(s"polling on an empty inbox: $name")
|
||||
case x ⇒ x
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert and remove the the oldest message.
|
||||
*/
|
||||
def expectMsg(expectedMessage: T): TestInbox[T] = {
|
||||
q.poll() match {
|
||||
case null ⇒ assert(assertion = false, s"expected msg: $expectedMessage but no messages were received")
|
||||
case message ⇒ assert(message == expectedMessage, s"expected: $expectedMessage but received $message")
|
||||
}
|
||||
this
|
||||
}
|
||||
|
||||
def receiveAll(): immutable.Seq[T] = {
|
||||
@tailrec def rec(acc: List[T]): List[T] = q.poll() match {
|
||||
case null ⇒ acc.reverse
|
||||
case x ⇒ rec(x :: acc)
|
||||
}
|
||||
|
||||
rec(Nil)
|
||||
}
|
||||
|
||||
def hasMessages: Boolean = q.peek() != null
|
||||
|
||||
// TODO expectNoMsg etc
|
||||
}
|
||||
|
||||
object Inbox {
|
||||
def apply[T](name: String): Inbox[T] = new Inbox(name)
|
||||
@ApiMayChange
|
||||
object TestInbox {
|
||||
def apply[T](name: String = "inbox"): TestInbox[T] = new TestInbox(name)
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package akka.testkit.typed
|
||||
|
||||
import akka.actor.typed.{ ActorRef, ActorSystem, Behavior }
|
||||
import akka.annotation.ApiMayChange
|
||||
import akka.util.Timeout
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.{ Await, TimeoutException }
|
||||
|
||||
/**
|
||||
* Testkit for typed actors. Extending this removes some boiler plate when testing
|
||||
* typed actors.
|
||||
*
|
||||
* If a test can't extend then use the [[TestKitBase]] trait
|
||||
*
|
||||
* @param _system The [ActorSystem] for the test
|
||||
*/
|
||||
@ApiMayChange
|
||||
class TestKit(_system: ActorSystem[_]) extends TestKitBase {
|
||||
implicit val system = _system
|
||||
}
|
||||
|
||||
@ApiMayChange
|
||||
trait TestKitBase {
|
||||
def system: ActorSystem[_]
|
||||
implicit def testkitSettings = TestKitSettings(system)
|
||||
|
||||
def shutdown(): Unit = {
|
||||
shutdown(system, 5.seconds)
|
||||
}
|
||||
|
||||
def shutdown(
|
||||
actorSystem: ActorSystem[_],
|
||||
duration: Duration,
|
||||
verifySystemShutdown: Boolean = false): Unit = {
|
||||
system.terminate()
|
||||
try Await.ready(actorSystem.whenTerminated, duration) catch {
|
||||
case _: TimeoutException ⇒
|
||||
val msg = "Failed to stop [%s] within [%s] \n%s".format(actorSystem.name, duration,
|
||||
actorSystem.printTree)
|
||||
if (verifySystemShutdown) throw new RuntimeException(msg)
|
||||
else println(msg)
|
||||
}
|
||||
}
|
||||
|
||||
// The only current impl of a typed actor system returns a Future.successful currently
|
||||
// hence the hardcoded timeouts
|
||||
def actorOf[T](behaviour: Behavior[T], name: String): ActorRef[T] =
|
||||
Await.result(system.systemActorOf(behaviour, name)(Timeout(20.seconds)), 21.seconds)
|
||||
}
|
||||
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package akka.typed.testkit
|
||||
package akka.testkit.typed
|
||||
|
||||
import com.typesafe.config.Config
|
||||
import scala.concurrent.duration.FiniteDuration
|
||||
|
|
@ -1,15 +1,15 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package akka.typed.testkit.javadsl
|
||||
package akka.testkit.typed.javadsl
|
||||
|
||||
import akka.actor.typed.ActorSystem
|
||||
import akka.typed.testkit.TestKitSettings
|
||||
import akka.testkit.typed.TestKitSettings
|
||||
|
||||
/**
|
||||
* Java API:
|
||||
*/
|
||||
class TestProbe[M](name: String, system: ActorSystem[_], settings: TestKitSettings) extends akka.typed.testkit.scaladsl.TestProbe[M](name)(system, settings) {
|
||||
class TestProbe[M](name: String, system: ActorSystem[_], settings: TestKitSettings) extends akka.testkit.typed.scaladsl.TestProbe[M](name)(system, settings) {
|
||||
|
||||
def this(system: ActorSystem[_], settings: TestKitSettings) = this("testProbe", system, settings)
|
||||
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package akka.typed.testkit.scaladsl
|
||||
package akka.testkit.typed.scaladsl
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import java.util.concurrent.BlockingDeque
|
||||
|
|
@ -18,7 +18,7 @@ import akka.util.PrettyDuration.PrettyPrintableDuration
|
|||
|
||||
import scala.concurrent.Await
|
||||
import com.typesafe.config.Config
|
||||
import akka.typed.testkit.TestKitSettings
|
||||
import akka.testkit.typed.TestKitSettings
|
||||
import akka.util.BoxedType
|
||||
|
||||
import scala.annotation.tailrec
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package akka.typed.testkit
|
||||
package akka.testkit.typed
|
||||
|
||||
import scala.concurrent.duration.{ Duration, FiniteDuration }
|
||||
import scala.reflect.ClassTag
|
||||
|
|
@ -18,7 +18,7 @@ package object scaladsl {
|
|||
*
|
||||
* {{{
|
||||
* import scala.concurrent.duration._
|
||||
* import akka.typed.testkit.scaladsl._
|
||||
* import akka.testkit.typed.scaladsl._
|
||||
* 10.milliseconds.dilated
|
||||
* }}}
|
||||
*
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2014-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package akka.typed.testkit
|
||||
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
|
||||
import akka.actor.typed.{ ActorContext, ActorRef, ActorSystem, Behavior, PostStop, Props, Signal }
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.collection.immutable
|
||||
import scala.util.control.Exception.Catcher
|
||||
import scala.util.control.NonFatal
|
||||
import scala.concurrent.duration.{ Duration, FiniteDuration }
|
||||
|
||||
/**
|
||||
* All tracked effects must extend implement this type. It is deliberately
|
||||
* not sealed in order to allow extensions.
|
||||
*/
|
||||
abstract class Effect
|
||||
|
||||
object Effect {
|
||||
|
||||
abstract class SpawnedEffect extends Effect
|
||||
|
||||
@SerialVersionUID(1L) final case class Spawned(childName: String, props: Props) extends SpawnedEffect
|
||||
@SerialVersionUID(1L) final case class SpawnedAnonymous(props: Props) extends SpawnedEffect
|
||||
@SerialVersionUID(1L) final case object SpawnedAdapter extends SpawnedEffect
|
||||
@SerialVersionUID(1L) final case class Stopped(childName: String) extends Effect
|
||||
@SerialVersionUID(1L) final case class Watched[T](other: ActorRef[T]) extends Effect
|
||||
@SerialVersionUID(1L) final case class Unwatched[T](other: ActorRef[T]) extends Effect
|
||||
@SerialVersionUID(1L) final case class ReceiveTimeoutSet[T](d: Duration, msg: T) extends Effect
|
||||
@SerialVersionUID(1L) final case class Messaged[U](other: ActorRef[U], msg: U) extends Effect
|
||||
@SerialVersionUID(1L) final case class Scheduled[U](delay: FiniteDuration, target: ActorRef[U], msg: U) extends Effect
|
||||
@SerialVersionUID(1L) case object EmptyEffect extends Effect
|
||||
}
|
||||
|
||||
/**
|
||||
* An [[ActorContext]] for testing purposes that records the effects performed
|
||||
* on it and otherwise stubs them out like a [[StubbedActorContext]].
|
||||
*/
|
||||
class EffectfulActorContext[T](_name: String, _initialBehavior: Behavior[T], _mailboxCapacity: Int, _system: ActorSystem[Nothing])
|
||||
extends StubbedActorContext[T](_name, _mailboxCapacity, _system) {
|
||||
import Effect._
|
||||
import akka.{ actor ⇒ a }
|
||||
|
||||
private val effectQueue = new ConcurrentLinkedQueue[Effect]
|
||||
def getEffect(): Effect = effectQueue.poll() match {
|
||||
case null ⇒ throw new NoSuchElementException(s"polling on an empty effect queue: $name")
|
||||
case x ⇒ x
|
||||
}
|
||||
def getAllEffects(): immutable.Seq[Effect] = {
|
||||
@tailrec def rec(acc: List[Effect]): List[Effect] = effectQueue.poll() match {
|
||||
case null ⇒ acc.reverse
|
||||
case x ⇒ rec(x :: acc)
|
||||
}
|
||||
rec(Nil)
|
||||
}
|
||||
def hasEffects: Boolean = effectQueue.peek() != null
|
||||
|
||||
private var current = Behavior.validateAsInitial(Behavior.undefer(_initialBehavior, this))
|
||||
|
||||
def currentBehavior: Behavior[T] = current
|
||||
def isAlive: Boolean = Behavior.isAlive(current)
|
||||
|
||||
private def handleException: Catcher[Unit] = {
|
||||
case NonFatal(e) ⇒
|
||||
try Behavior.canonicalize(Behavior.interpretSignal(current, this, PostStop), current, this) // TODO why canonicalize here?
|
||||
catch { case NonFatal(ex) ⇒ /* ignore, real is logging */ }
|
||||
throw e
|
||||
}
|
||||
|
||||
def run(msg: T): Unit = {
|
||||
try {
|
||||
current = Behavior.canonicalize(Behavior.interpretMessage(current, this, msg), current, this)
|
||||
} catch handleException
|
||||
}
|
||||
|
||||
def signal(signal: Signal): Unit = {
|
||||
try {
|
||||
current = Behavior.canonicalize(Behavior.interpretSignal(current, this, signal), current, this)
|
||||
} catch handleException
|
||||
}
|
||||
|
||||
override def spawnAnonymous[U](behavior: Behavior[U], props: Props = Props.empty): ActorRef[U] = {
|
||||
val ref = super.spawnAnonymous(behavior, props)
|
||||
effectQueue.offer(SpawnedAnonymous(props))
|
||||
ref
|
||||
}
|
||||
|
||||
override def spawnAdapter[U](f: U ⇒ T): ActorRef[U] = {
|
||||
spawnAdapter(f, "")
|
||||
}
|
||||
|
||||
override def spawnAdapter[U](f: U ⇒ T, name: String): ActorRef[U] = {
|
||||
val ref = super.spawnAdapter(f, name)
|
||||
effectQueue.offer(SpawnedAdapter)
|
||||
ref
|
||||
}
|
||||
override def spawn[U](behavior: Behavior[U], name: String, props: Props = Props.empty): ActorRef[U] = {
|
||||
effectQueue.offer(Spawned(name, props))
|
||||
super.spawn(behavior, name, props)
|
||||
}
|
||||
override def stop[U](child: ActorRef[U]): Boolean = {
|
||||
effectQueue.offer(Stopped(child.path.name))
|
||||
super.stop(child)
|
||||
}
|
||||
override def watch[U](other: ActorRef[U]): Unit = {
|
||||
effectQueue.offer(Watched(other))
|
||||
super.watch(other)
|
||||
}
|
||||
override def unwatch[U](other: ActorRef[U]): Unit = {
|
||||
effectQueue.offer(Unwatched(other))
|
||||
super.unwatch(other)
|
||||
}
|
||||
override def setReceiveTimeout(d: FiniteDuration, msg: T): Unit = {
|
||||
effectQueue.offer(ReceiveTimeoutSet(d, msg))
|
||||
super.setReceiveTimeout(d, msg)
|
||||
}
|
||||
override def cancelReceiveTimeout(): Unit = {
|
||||
effectQueue.offer(ReceiveTimeoutSet(Duration.Undefined, null))
|
||||
super.cancelReceiveTimeout()
|
||||
}
|
||||
override def schedule[U](delay: FiniteDuration, target: ActorRef[U], msg: U): a.Cancellable = {
|
||||
effectQueue.offer(Scheduled(delay, target, msg))
|
||||
super.schedule(delay, target, msg)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,16 +2,16 @@
|
|||
* Copyright (C) 2014-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package akka.typed.testkit
|
||||
package akka.testkit.typed
|
||||
|
||||
import akka.actor.typed.scaladsl.Actor
|
||||
import akka.typed.testkit.Effect.{ Spawned, SpawnedAdapter, SpawnedAnonymous }
|
||||
import akka.typed.testkit.EffectfulActorContextSpec.Father
|
||||
import akka.typed.testkit.EffectfulActorContextSpec.Father._
|
||||
import akka.actor.typed.{ ActorSystem, Behavior, Props }
|
||||
import akka.testkit.typed.Effect.{ Spawned, SpawnedAdapter, SpawnedAnonymous }
|
||||
import akka.testkit.typed.BehaviorTestkitSpec.{ Child, Father }
|
||||
import akka.testkit.typed.BehaviorTestkitSpec.Father._
|
||||
import akka.actor.typed.{ Behavior, Props }
|
||||
import org.scalatest.{ FlatSpec, Matchers }
|
||||
|
||||
object EffectfulActorContextSpec {
|
||||
object BehaviorTestkitSpec {
|
||||
object Father {
|
||||
|
||||
case class Reproduce(times: Int)
|
||||
|
|
@ -67,7 +67,7 @@ object EffectfulActorContextSpec {
|
|||
|
||||
sealed trait Action
|
||||
|
||||
def initial: Behavior[Action] = Actor.immutable[Action] { (_, msg) ⇒
|
||||
val initial: Behavior[Action] = Actor.immutable[Action] { (_, msg) ⇒
|
||||
msg match {
|
||||
case _ ⇒
|
||||
Actor.empty
|
||||
|
|
@ -78,61 +78,56 @@ object EffectfulActorContextSpec {
|
|||
|
||||
}
|
||||
|
||||
class EffectfulActorContextSpec extends FlatSpec with Matchers {
|
||||
//TODO WordSpec
|
||||
class BehaviorTestkitSpec extends FlatSpec with Matchers {
|
||||
|
||||
private val props = Props.empty
|
||||
|
||||
"EffectfulActorContext's spawn" should "create children when no props specified" in {
|
||||
val system = ActorSystem.create(Father.init(), "father-system")
|
||||
val ctx = new EffectfulActorContext[Father.Command]("father-test", Father.init(), 100, system)
|
||||
"BehaviourTestkit's spawn" should "create children when no props specified" in {
|
||||
val ctx = BehaviorTestkit[Father.Command](Father.init())
|
||||
|
||||
ctx.run(SpawnChildren(2))
|
||||
val effects = ctx.getAllEffects()
|
||||
effects should contain only (Spawned("child0", Props.empty), Spawned("child1", Props.empty))
|
||||
val effects = ctx.retrieveAllEffects()
|
||||
effects should contain only (Spawned(Child.initial, "child0"), Spawned(Child.initial, "child1", Props.empty))
|
||||
}
|
||||
|
||||
it should "create children when props specified and record effects" in {
|
||||
val system = ActorSystem.create(Father.init(), "father-system")
|
||||
val ctx = new EffectfulActorContext[Father.Command]("father-test", Father.init(), 100, system)
|
||||
val ctx = BehaviorTestkit[Father.Command](Father.init())
|
||||
|
||||
ctx.run(SpawnChildrenWithProps(2, props))
|
||||
val effects = ctx.getAllEffects()
|
||||
effects should contain only (Spawned("child0", props), Spawned("child1", props))
|
||||
val effects = ctx.retrieveAllEffects()
|
||||
effects should contain only (Spawned(Child.initial, "child0", props), Spawned(Child.initial, "child1", props))
|
||||
}
|
||||
|
||||
"EffectfulActorContext's spawnAnonymous" should "create children when no props specified and record effects" in {
|
||||
val system = ActorSystem.create(Father.init(), "father-system")
|
||||
val ctx = new EffectfulActorContext[Father.Command]("father-test", Father.init(), 100, system)
|
||||
"BehaviourTestkit's spawnAnonymous" should "create children when no props specified and record effects" in {
|
||||
val ctx = BehaviorTestkit[Father.Command](Father.init())
|
||||
|
||||
ctx.run(SpawnAnonymous(2))
|
||||
val effects = ctx.getAllEffects()
|
||||
effects shouldBe Seq(SpawnedAnonymous(Props.empty), SpawnedAnonymous(Props.empty))
|
||||
val effects = ctx.retrieveAllEffects()
|
||||
effects shouldBe Seq(SpawnedAnonymous(Child.initial, Props.empty), SpawnedAnonymous(Child.initial, Props.empty))
|
||||
}
|
||||
|
||||
it should "create children when props specified and record effects" in {
|
||||
val system = ActorSystem.create(Father.init(), "father-system")
|
||||
val ctx = new EffectfulActorContext[Father.Command]("father-test", Father.init(), 100, system)
|
||||
val ctx = BehaviorTestkit[Father.Command](Father.init())
|
||||
|
||||
ctx.run(SpawnAnonymousWithProps(2, props))
|
||||
val effects = ctx.getAllEffects()
|
||||
effects shouldBe Seq(SpawnedAnonymous(props), SpawnedAnonymous(props))
|
||||
val effects = ctx.retrieveAllEffects()
|
||||
effects shouldBe Seq(SpawnedAnonymous(Child.initial, props), SpawnedAnonymous(Child.initial, props))
|
||||
}
|
||||
|
||||
"EffectfulActorContext's spawnAdapter" should "create adapters without name and record effects" in {
|
||||
val system = ActorSystem.create(Father.init(), "father-system")
|
||||
val ctx = new EffectfulActorContext[Father.Command]("father-test", Father.init(), 100, system)
|
||||
"BehaviourTestkit's spawnAdapter" should "create adapters without name and record effects" in {
|
||||
val ctx = BehaviorTestkit[Father.Command](Father.init())
|
||||
|
||||
ctx.run(SpawnAdapter)
|
||||
val effects = ctx.getAllEffects()
|
||||
val effects = ctx.retrieveAllEffects()
|
||||
effects shouldBe Seq(SpawnedAdapter)
|
||||
}
|
||||
|
||||
it should "create adapters with name and record effects" in {
|
||||
val system = ActorSystem.create(Father.init(), "father-system")
|
||||
val ctx = new EffectfulActorContext[Father.Command]("father-test", Father.init(), 100, system)
|
||||
val ctx = BehaviorTestkit[Father.Command](Father.init())
|
||||
|
||||
ctx.run(SpawnAdapterWithName("adapter"))
|
||||
val effects = ctx.getAllEffects()
|
||||
val effects = ctx.retrieveAllEffects()
|
||||
effects shouldBe Seq(SpawnedAdapter)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue