diff --git a/supervisor/test-code/test/scala/GenericServerContainerSuite.scala b/supervisor/test-code/test/scala/GenericServerContainerSuite.scala new file mode 100755 index 0000000000..1b85425d74 --- /dev/null +++ b/supervisor/test-code/test/scala/GenericServerContainerSuite.scala @@ -0,0 +1,202 @@ +/** + * Copyright (C) 2009 Scalable Solutions. + */ + +package com.scalablesolutions.akka.supervisor + +import org.specs.runner.JUnit4 +import org.specs.Specification + +import scala.actors._ +import scala.actors.Actor._ + +/** + * @author Jonas Bonér + */ +class GenericServerContainerTest extends JUnit4(genericServerContainerSpec) // for JUnit4 and Maven +object genericServerContainerSpec extends Specification { + + var inner: GenericServerContainerActor = null + var server: GenericServerContainer = null + def createProxy(f: () => GenericServer) = { + val server = new GenericServerContainer("server", f) + server.setTimeout(100) + server + } + + inner = new GenericServerContainerActor + server = createProxy(() => inner) + server.newServer + server.start + + "server should be initialized" in { + server.init("testInit") + Thread.sleep(100) + expect("initializing: testInit") { + inner.log + } + } + + "server should terminate with a reason " in { + server.terminate("testTerminateWithReason", 100) + Thread.sleep(100) + expect("terminating: testTerminateWithReason") { + inner.log + } + } + + "server respond to async oneway message" in { + server ! OneWay + Thread.sleep(100) + expect("got a oneway") { + inner.log + } + } + + "server respond to async ping message" in { + server ! Ping + Thread.sleep(100) + expect("got a ping") { + inner.log + } + } + + "server respond to !!!" in { + expect("pong") { + (server !!! Ping).getOrElse("nil") + } + expect("got a ping") { + inner.log + } + } + + "server respond to !?" in { + expect("pong") { + val res: String = server !? Ping + res + } + expect("got a ping") { + inner.log + } + } + + "server respond to !!! with timeout" in { + expect("pong") { + (server !!! Ping).getOrElse("nil") + } + expect("got a ping") { + inner.log + } + } + + "server respond to !!! with timeout" in { + expect("error handler") { + server !!! (OneWay, "error handler") + } + expect("got a oneway") { + inner.log + } + } + + "server respond to !!! and return future with timeout" in { + val future = server !! Ping + future.receiveWithin(100) match { + case None => fail("timed out") // timed out + case Some(reply) => + expect("got a ping") { + inner.log + } + assert("pong" === reply) + } + } + + "server respond to !!! and return future with timeout" in { + val future = server !! OneWay + future.receiveWithin(100) match { + case None => + expect("got a oneway") { + inner.log + } + case Some(reply) => + fail("expected a timeout, got Some(reply)") + } + } + + "server respond do hotswap" in { + // using base + expect("pong") { + (server !!! Ping).getOrElse("nil") + } + + // hotswapping + server.hotswap(Some({ + case Ping => reply("hotswapped pong") + })) + expect("hotswapped pong") { + (server !!! Ping).getOrElse("nil") + } + } + + "server respond do double hotswap" in { + // using base + expect("pong") { + (server !!! Ping).getOrElse("nil") + } + + // hotswapping + server.hotswap(Some({ + case Ping => reply("hotswapped pong") + })) + expect("hotswapped pong") { + (server !!! Ping).getOrElse("nil") + } + + // hotswapping again + server.hotswap(Some({ + case Ping => reply("hotswapped pong again") + })) + expect("hotswapped pong again") { + (server !!! Ping).getOrElse("nil") + } + } + + "server respond do hotswap and then revert" in { + // using base + expect("pong") { + (server !!! Ping).getOrElse("nil") + } + + // hotswapping + server.hotswap(Some({ + case Ping => reply("hotswapped pong") + })) + expect("hotswapped pong") { + (server !!! Ping).getOrElse("nil") + } + + // restoring original base + server.hotswap(None) + expect("pong") { + (server !!! Ping).getOrElse("nil") + } + } +} + + +class GenericServerContainerActor extends GenericServer { + var log = "" + + override def body: PartialFunction[Any, Unit] = { + case Ping => + log = "got a ping" + reply("pong") + + case OneWay => + log = "got a oneway" + } + + override def init(config: AnyRef) = log = "initializing: " + config + override def shutdown(reason: AnyRef) = log = "terminating: " + reason +} + + diff --git a/supervisor/test-code/test/scala/GenericServerSuite.scala b/supervisor/test-code/test/scala/GenericServerSuite.scala new file mode 100755 index 0000000000..44aab326eb --- /dev/null +++ b/supervisor/test-code/test/scala/GenericServerSuite.scala @@ -0,0 +1,40 @@ +/** + * Copyright (C) 2009 Scalable Solutions. + */ + +package com.scalablesolutions.akka.supervisor + +import org.specs.runner.JUnit4 +import org.specs.Specification + +import scala.actors._ +import scala.actors.Actor._ + +/** + * @author Jonas Bonér + */ +class GenericServerTest extends JUnit4(genericServerSpec) // for JUnit4 and Maven +object genericServerSpec extends Specification { + + "server should respond to a regular message" in { + val server = new TestGenericServerActor + server.start + server !? Ping match { + case reply: String => + assert("got a ping" === server.log) + assert("pong" === reply) + case _ => fail() + } + } +} + +class TestGenericServerActor extends GenericServer { + var log: String = "" + + override def body: PartialFunction[Any, Unit] = { + case Ping => + log = "got a ping" + reply("pong") + } +} + diff --git a/supervisor/test-code/test/scala/Messages.scala b/supervisor/test-code/test/scala/Messages.scala new file mode 100755 index 0000000000..0f014ca692 --- /dev/null +++ b/supervisor/test-code/test/scala/Messages.scala @@ -0,0 +1,12 @@ +/** + * Copyright (C) 2009 Scalable Solutions. + */ + +package com.scalablesolutions.akka.supervisor + +sealed abstract class TestMessage +case object Ping extends TestMessage +case object Pong extends TestMessage +case object OneWay extends TestMessage +case object Die extends TestMessage +case object NotifySupervisorExit extends TestMessage diff --git a/supervisor/test-code/test/scala/SupervisorStateSuite.scala b/supervisor/test-code/test/scala/SupervisorStateSuite.scala new file mode 100755 index 0000000000..6df3b7e059 --- /dev/null +++ b/supervisor/test-code/test/scala/SupervisorStateSuite.scala @@ -0,0 +1,77 @@ +/** + * Copyright (C) 2009 Scalable Solutions. + */ + +package com.scalablesolutions.akka.supervisor + +import org.specs.runner.JUnit4 +import org.specs.Specification + +import scala.actors._ +import scala.actors.Actor._ + +/** + * @author Jonas Bonér + */ +class SupervisorStateTest extends JUnit4(supervisorStateSpec) // for JUnit4 and Maven +object supervisorStateSpec extends Specification { + val dummyActor = new GenericServer { override def body: PartialFunction[Any, Unit] = { case _ => }} + val newDummyActor = () => dummyActor + var state: SupervisorState = _ + var proxy: GenericServerContainer = _ + var supervisor: Supervisor = _ + + proxy = new GenericServerContainer("server1", newDummyActor) + object factory extends SupervisorFactory { + override def getSupervisorConfig: SupervisorConfig = { + SupervisorConfig( + RestartStrategy(AllForOne, 3, 100), + Worker( + proxy, + LifeCycle(Permanent, 100)) + :: Nil) + } + } + + supervisor = factory.newSupervisor + state = new SupervisorState(supervisor, new AllForOneStrategy(3, 100)) + + "supervisor state should return added server" in { + state.addServerContainer(proxy) + state.getServerContainer("server1") match { + case None => fail("should have returned server") + case Some(server) => + assert(server != null) + assert(server.isInstanceOf[GenericServerContainer]) + assert(proxy === server) + } + } + + "supervisor state should remove added server" in { + state.addServerContainer(proxy) + + state.removeServerContainer("server1") + state.getServerContainer("server1") match { + case Some(_) => fail("should have returned None") + case None => + } + state.getServerContainer("dummyActor") match { + case Some(_) => fail("should have returned None") + case None => + } + } + + "supervisor state should fail getting non-existent server by symbol" in { + state.getServerContainer("server2") match { + case Some(_) => fail("should have returned None") + case None => + } + } + + "supervisor state should fail getting non-existent server by actor" in { + state.getServerContainer("dummyActor") match { + case Some(_) => fail("should have returned None") + case None => + } + } +} diff --git a/supervisor/test-code/test/scala/SupervisorSuite.scala b/supervisor/test-code/test/scala/SupervisorSuite.scala new file mode 100755 index 0000000000..4e8bd048e1 --- /dev/null +++ b/supervisor/test-code/test/scala/SupervisorSuite.scala @@ -0,0 +1,434 @@ +/** + * Copyright (C) 2009 Scalable Solutions. + */ + +package com.scalablesolutions.akka.supervisor + +import org.specs.runner.JUnit4 +import org.specs.Specification + +import scala.actors._ +import scala.actors.Actor._ +import scala.collection.Map +import scala.collection.mutable.HashMap + +/** + * @author Jonas Bonér + */ +class SupervisorTest extends JUnit4(supervisorSpec) // for JUnit4 and Maven +object supervisorSpec extends Specification { + + var messageLog: String = "" + val pingpong1 = new GenericServerContainer("pingpong1", () => new PingPong1Actor) + val pingpong2 = new GenericServerContainer("pingpong2", () => new PingPong2Actor) + val pingpong3 = new GenericServerContainer("pingpong3", () => new PingPong3Actor) + + pingpong1.setTimeout(100) + pingpong2.setTimeout(100) + pingpong3.setTimeout(100) + + @BeforeMethod + def setup = messageLog = "" + + // =========================================== + "starting supervisor should start the servers" in { + val sup = getSingleActorAllForOneSupervisor + sup ! Start + + expect("pong") { + (pingpong1 !!! Ping).getOrElse("nil") + } + } + + // =========================================== + "started supervisor should be able to return started servers" in { + val sup = getSingleActorAllForOneSupervisor + sup ! Start + val server = sup.getServerOrElse("pingpong1", throw new RuntimeException("server not found")) + assert(server.isInstanceOf[GenericServerContainer]) + assert(server === pingpong1) + } + + // =========================================== + "started supervisor should fail returning non-existing server" in { + val sup = getSingleActorAllForOneSupervisor + sup ! Start + intercept(classOf[RuntimeException]) { + sup.getServerOrElse("wrong_name", throw new RuntimeException("server not found")) + } + } + + // =========================================== + "supervisor should restart killed server with restart strategy one_for_one" in { + val sup = getSingleActorOneForOneSupervisor + sup ! Start + + intercept(classOf[RuntimeException]) { + pingpong1 !!! (Die, throw new RuntimeException("TIME OUT")) + } + Thread.sleep(100) + expect("oneforone") { + messageLog + } + } + + // =========================================== + "supervisor should restart used killed server with restart strategy one_for_one" in { + val sup = getSingleActorOneForOneSupervisor + sup ! Start + + expect("pong") { + (pingpong1 !!! Ping).getOrElse("nil") + } + Thread.sleep(100) + expect("ping") { + messageLog + } + intercept(classOf[RuntimeException]) { + pingpong1 !!! (Die, throw new RuntimeException("TIME OUT")) + } + Thread.sleep(100) + expect("pingoneforone") { + messageLog + } + expect("pong") { + (pingpong1 !!! Ping).getOrElse("nil") + } + Thread.sleep(100) + expect("pingoneforoneping") { + messageLog + } + } + + // =========================================== + "supervisor should restart killed server with restart strategy all_for_one" in { + val sup = getSingleActorAllForOneSupervisor + sup ! Start + intercept(classOf[RuntimeException]) { + pingpong1 !!! (Die, throw new RuntimeException("TIME OUT")) + } + Thread.sleep(100) + expect("allforone") { + messageLog + } + } + + // =========================================== + "supervisor should restart used killed server with restart strategy all_for_one" in { + val sup = getSingleActorAllForOneSupervisor + sup ! Start + expect("pong") { + (pingpong1 !!! Ping).getOrElse("nil") + } + Thread.sleep(100) + expect("ping") { + messageLog + } + intercept(classOf[RuntimeException]) { + pingpong1 !!! (Die, throw new RuntimeException("TIME OUT")) + } + Thread.sleep(100) + expect("pingallforone") { + messageLog + } + expect("pong") { + (pingpong1 !!! Ping).getOrElse("nil") + } + Thread.sleep(100) + expect("pingallforoneping") { + messageLog + } + } + + // =========================================== + "supervisor should restart killed multiple servers with restart strategy one_for_one" in { + val sup = getMultipleActorsOneForOneConf + sup ! Start + intercept(classOf[RuntimeException]) { + pingpong3 !!! (Die, throw new RuntimeException("TIME OUT")) + } + Thread.sleep(100) + expect("oneforone") { + messageLog + } + } + + // =========================================== + "supervisor should restart killed multiple servers with restart strategy one_for_one" in { + val sup = getMultipleActorsOneForOneConf + sup ! Start + expect("pong") { + (pingpong1 !!! Ping).getOrElse("nil") + } + Thread.sleep(100) + expect("pong") { + (pingpong2 !!! Ping).getOrElse("nil") + } + Thread.sleep(100) + expect("pong") { + (pingpong3 !!! Ping).getOrElse("nil") + } + Thread.sleep(100) + expect("pingpingping") { + messageLog + } + intercept(classOf[RuntimeException]) { + pingpong2 !!! (Die, throw new RuntimeException("TIME OUT")) + } + Thread.sleep(100) + expect("pingpingpingoneforone") { + messageLog + } + expect("pong") { + (pingpong1 !!! Ping).getOrElse("nil") + } + Thread.sleep(100) + expect("pong") { + (pingpong2 !!! Ping).getOrElse("nil") + } + Thread.sleep(100) + expect("pong") { + (pingpong3 !!! Ping).getOrElse("nil") + } + Thread.sleep(100) + expect("pingpingpingoneforonepingpingping") { + messageLog + } + } + + // =========================================== + "supervisor should restart killed muliple servers with restart strategy all_for_one" in { + val sup = getMultipleActorsAllForOneConf + sup ! Start + intercept(classOf[RuntimeException]) { + pingpong2 !!! (Die, throw new RuntimeException("TIME OUT")) + } + Thread.sleep(100) + expect("allforoneallforoneallforone") { + messageLog + } + } + + // =========================================== + "supervisor should restart killed muliple servers with restart strategy all_for_one" in { + val sup = getMultipleActorsAllForOneConf + sup ! Start + expect("pong") { + (pingpong1 !!! Ping).getOrElse("nil") + } + Thread.sleep(100) + expect("pong") { + (pingpong2 !!! Ping).getOrElse("nil") + } + Thread.sleep(100) + expect("pong") { + (pingpong3 !!! Ping).getOrElse("nil") + } + Thread.sleep(100) + expect("pingpingping") { + messageLog + } + intercept(classOf[RuntimeException]) { + pingpong2 !!! (Die, throw new RuntimeException("TIME OUT")) + } + Thread.sleep(100) + expect("pingpingpingallforoneallforoneallforone") { + messageLog + } + expect("pong") { + (pingpong1 !!! Ping).getOrElse("nil") + } + Thread.sleep(100) + expect("pong") { + (pingpong2 !!! Ping).getOrElse("nil") + } + Thread.sleep(100) + expect("pong") { + (pingpong3 !!! Ping).getOrElse("nil") + } + Thread.sleep(100) + expect("pingpingpingallforoneallforoneallforonepingpingping") { + messageLog + } + } + + "supervisor should restart killed first-level server with restart strategy all_for_one" in { + val sup = getNestedSupervisorsAllForOneConf + sup ! Start + intercept(classOf[RuntimeException]) { + pingpong1 !!! (Die, throw new RuntimeException("TIME OUT")) + } + Thread.sleep(100) + expect("allforoneallforoneallforone") { + messageLog + } + } + + + // ============================================= + // Creat some supervisors with different configurations + + def getSingleActorAllForOneSupervisor: Supervisor = { + + // Create an abstract SupervisorContainer that works for all implementations + // of the different Actors (Services). + // + // Then create a concrete container in which we mix in support for the specific + // implementation of the Actors we want to use. + + object factory extends TestSupervisorFactory { + override def getSupervisorConfig: SupervisorConfig = { + SupervisorConfig( + RestartStrategy(AllForOne, 3, 100), + Worker( + pingpong1, + LifeCycle(Permanent, 100)) + :: Nil) + } + } + factory.newSupervisor + } + + def getSingleActorOneForOneSupervisor: Supervisor = { + object factory extends TestSupervisorFactory { + override def getSupervisorConfig: SupervisorConfig = { + SupervisorConfig( + RestartStrategy(OneForOne, 3, 100), + Worker( + pingpong1, + LifeCycle(Permanent, 100)) + :: Nil) + } + } + factory.newSupervisor + } + + def getMultipleActorsAllForOneConf: Supervisor = { + object factory extends TestSupervisorFactory { + override def getSupervisorConfig: SupervisorConfig = { + SupervisorConfig( + RestartStrategy(AllForOne, 3, 100), + Worker( + pingpong1, + LifeCycle(Permanent, 100)) + :: + Worker( + pingpong2, + LifeCycle(Permanent, 100)) + :: + Worker( + pingpong3, + LifeCycle(Permanent, 100)) + :: Nil) + } + } + factory.newSupervisor + } + + def getMultipleActorsOneForOneConf: Supervisor = { + object factory extends TestSupervisorFactory { + override def getSupervisorConfig: SupervisorConfig = { + SupervisorConfig( + RestartStrategy(OneForOne, 3, 100), + Worker( + pingpong1, + LifeCycle(Permanent, 100)) + :: + Worker( + pingpong2, + LifeCycle(Permanent, 100)) + :: + Worker( + pingpong3, + LifeCycle(Permanent, 100)) + :: Nil) + } + } + factory.newSupervisor + } + + def getNestedSupervisorsAllForOneConf: Supervisor = { + object factory extends TestSupervisorFactory { + override def getSupervisorConfig: SupervisorConfig = { + SupervisorConfig( + RestartStrategy(AllForOne, 3, 100), + Worker( + pingpong1, + LifeCycle(Permanent, 100)) + :: + SupervisorConfig( + RestartStrategy(AllForOne, 3, 100), + Worker( + pingpong2, + LifeCycle(Permanent, 100)) + :: + Worker( + pingpong3, + LifeCycle(Permanent, 100)) + :: Nil) + :: Nil) + } + } + factory.newSupervisor + } + + class PingPong1Actor extends GenericServer { + override def body: PartialFunction[Any, Unit] = { + case Ping => + messageLog += "ping" + reply("pong") + case Die => + throw new RuntimeException("Recieved Die message") + } + } + + class PingPong2Actor extends GenericServer { + override def body: PartialFunction[Any, Unit] = { + case Ping => + messageLog += "ping" + reply("pong") + case Die => + throw new RuntimeException("Recieved Die message") + } + } + + class PingPong3Actor extends GenericServer { + override def body: PartialFunction[Any, Unit] = { + case Ping => + messageLog += "ping" + reply("pong") + case Die => + throw new RuntimeException("Recieved Die message") + } + } + + // ============================================= + + class TestAllForOneStrategy(maxNrOfRetries: Int, withinTimeRange: Int) extends AllForOneStrategy(maxNrOfRetries, withinTimeRange) { + override def postRestart(serverContainer: GenericServerContainer) = { + messageLog += "allforone" + } + } + + class TestOneForOneStrategy(maxNrOfRetries: Int, withinTimeRange: Int) extends OneForOneStrategy(maxNrOfRetries, withinTimeRange) { + override def postRestart(serverContainer: GenericServerContainer) = { + messageLog += "oneforone" + } + } + + abstract class TestSupervisorFactory extends SupervisorFactory { + override def create(strategy: RestartStrategy): Supervisor = strategy match { + case RestartStrategy(scheme, maxNrOfRetries, timeRange) => + scheme match { + case AllForOne => new Supervisor(new TestAllForOneStrategy(maxNrOfRetries, timeRange)) + case OneForOne => new Supervisor(new TestOneForOneStrategy(maxNrOfRetries, timeRange)) + } + } + } +} + + + + + +