From 0a0a41658510cbfccedd898e823a1b3f5604afd7 Mon Sep 17 00:00:00 2001 From: FateJH Date: Tue, 26 Sep 2017 23:33:08 -0400 Subject: [PATCH] standardized UNS reporting and recovery; added tests for UNS and better accommodated future Actor testing by splitting out ActorTest into its own file --- .../objects/guid/NumberPoolHub.scala | 8 +- .../guid/actor/UniqueNumberSystem.scala | 8 +- common/src/test/scala/objects/ActorTest.scala | 13 + .../scala/objects/NumberPoolActorTest.scala | 10 +- .../objects/UniqueNumberSystemTest.scala | 288 ++++++++++++++++++ 5 files changed, 312 insertions(+), 15 deletions(-) create mode 100644 common/src/test/scala/objects/ActorTest.scala create mode 100644 common/src/test/scala/objects/UniqueNumberSystemTest.scala diff --git a/common/src/main/scala/net/psforever/objects/guid/NumberPoolHub.scala b/common/src/main/scala/net/psforever/objects/guid/NumberPoolHub.scala index e574c0ec..39054335 100644 --- a/common/src/main/scala/net/psforever/objects/guid/NumberPoolHub.scala +++ b/common/src/main/scala/net/psforever/objects/guid/NumberPoolHub.scala @@ -60,14 +60,18 @@ class NumberPoolHub(private val source : NumberSource) { * @param name the name of the pool * @param pool the `List` of numbers that will belong to the pool * @return the newly-created number pool - * @throws IllegalArgumentException if the pool is already defined; - * if the pool contains numbers the source does not + * @throws IllegalArgumentException if the pool's name is already defined; + * if the pool is (already) empty; + * if the pool contains numbers the source does not; * if the pool contains numbers from already existing pools */ def AddPool(name : String, pool : List[Int]) : NumberPool = { if(hash.get(name).isDefined) { throw new IllegalArgumentException(s"can not add pool $name - name already known to this hub?") } + if(pool.isEmpty) { + throw new IllegalArgumentException(s"can not add empty pool $name") + } if(source.Size <= pool.max) { throw new IllegalArgumentException(s"can not add pool $name - max(pool) is greater than source.size") } diff --git a/common/src/main/scala/net/psforever/objects/guid/actor/UniqueNumberSystem.scala b/common/src/main/scala/net/psforever/objects/guid/actor/UniqueNumberSystem.scala index 9e37624f..fcd16c17 100644 --- a/common/src/main/scala/net/psforever/objects/guid/actor/UniqueNumberSystem.scala +++ b/common/src/main/scala/net/psforever/objects/guid/actor/UniqueNumberSystem.scala @@ -76,12 +76,12 @@ class UniqueNumberSystem(private val guid : NumberPoolHub, private val poolActor case Some(entry) => entry.replyTo ! Failure(ex) //ONLY callback that is possible case None => ; - log.warn(s"awkward no number error $ex") //neither a successful request nor an entry of making the request + log.warn(s"failed number request and no record of number request - $ex") //neither a successful request nor an entry of making the request } case None => ; - log.warn(s"awkward no number error $ex") //neither a successful request nor an entry of making the request + log.warn(s"failed number request and no record of number request - $ex") //neither a successful request nor an entry of making the request case _ => ; - log.warn(s"unrecognized request $id accompanying a no number error $ex") + log.warn(s"unrecognized request $id accompanying a failed number request - $ex") } case Unregister(obj, call) => @@ -126,7 +126,7 @@ class UniqueNumberSystem(private val guid : NumberPoolHub, private val poolActor log.error(s"could not find original request $nid that caused error $ex, but pool was $sender") //no callback is possible } - case None => ; + case _ => ; log.error(s"could not find original request $id that caused error $ex, but pool was $sender") //no callback is possible } diff --git a/common/src/test/scala/objects/ActorTest.scala b/common/src/test/scala/objects/ActorTest.scala new file mode 100644 index 00000000..5b08b920 --- /dev/null +++ b/common/src/test/scala/objects/ActorTest.scala @@ -0,0 +1,13 @@ +// Copyright (c) 2017 PSForever +package objects + +import akka.actor.ActorSystem +import akka.testkit.{ImplicitSender, TestKit} +import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} +import org.specs2.specification.Scope + +abstract class ActorTest(sys : ActorSystem) extends TestKit(sys) with Scope with ImplicitSender with WordSpecLike with Matchers with BeforeAndAfterAll { + override def afterAll { + TestKit.shutdownActorSystem(system) + } +} diff --git a/common/src/test/scala/objects/NumberPoolActorTest.scala b/common/src/test/scala/objects/NumberPoolActorTest.scala index 52787c81..4fec728b 100644 --- a/common/src/test/scala/objects/NumberPoolActorTest.scala +++ b/common/src/test/scala/objects/NumberPoolActorTest.scala @@ -2,25 +2,17 @@ package objects import akka.actor.{ActorSystem, Props} -import akka.testkit.{ImplicitSender, TestKit, TestProbe} +import akka.testkit.TestProbe import net.psforever.objects.entity.IdentifiableEntity import net.psforever.objects.guid.NumberPoolHub -import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} import net.psforever.objects.guid.actor.{NumberPoolAccessorActor, NumberPoolActor, Register} import net.psforever.objects.guid.pool.ExclusivePool import net.psforever.objects.guid.selector.RandomSelector import net.psforever.objects.guid.source.LimitedNumberSource -import org.specs2.specification.Scope import scala.concurrent.duration.Duration import scala.util.Success -abstract class ActorTest(sys : ActorSystem) extends TestKit(sys) with Scope with ImplicitSender with WordSpecLike with Matchers with BeforeAndAfterAll { - override def afterAll { - TestKit.shutdownActorSystem(system) - } -} - class NumberPoolActorTest extends ActorTest(ActorSystem("test")) { "NumberPoolActor" should { "GetAnyNumber" in { diff --git a/common/src/test/scala/objects/UniqueNumberSystemTest.scala b/common/src/test/scala/objects/UniqueNumberSystemTest.scala new file mode 100644 index 00000000..a685b035 --- /dev/null +++ b/common/src/test/scala/objects/UniqueNumberSystemTest.scala @@ -0,0 +1,288 @@ +// Copyright (c) 2017 PSForever +package objects + +import akka.actor.{ActorRef, ActorSystem, Props} +import net.psforever.objects.entity.IdentifiableEntity +import net.psforever.objects.guid.NumberPoolHub +import net.psforever.objects.guid.actor.{NumberPoolActor, Register, UniqueNumberSystem, Unregister} +import net.psforever.objects.guid.selector.RandomSelector +import net.psforever.objects.guid.source.LimitedNumberSource + +import scala.concurrent.duration.Duration +import scala.util.{Failure, Success} + +class AllocateNumberPoolActors extends ActorTest(ActorSystem("test")) { + "AllocateNumberPoolActors" in { + val src : LimitedNumberSource = LimitedNumberSource(6000) + val guid : NumberPoolHub = new NumberPoolHub(src) + guid.AddPool("pool1", (1001 to 2000).toList) + guid.AddPool("pool2", (3001 to 4000).toList) + guid.AddPool("pool3", (5001 to 6000).toList) + val actorMap = UniqueNumberSystemTest.AllocateNumberPoolActors(guid) + assert(actorMap.size == 4) + assert(actorMap.get("generic").isDefined) //automatically generated + assert(actorMap.get("pool1").isDefined) + assert(actorMap.get("pool2").isDefined) + assert(actorMap.get("pool3").isDefined) + } +} + +class UniqueNumberSystemTest extends ActorTest(ActorSystem("test")) { + "UniqueNumberSystem" should { + "constructor" in { + val src : LimitedNumberSource = LimitedNumberSource(6000) + val guid : NumberPoolHub = new NumberPoolHub(src) + guid.AddPool("pool1", (1001 to 2000).toList) + guid.AddPool("pool2", (3001 to 4000).toList) + guid.AddPool("pool3", (5001 to 6000).toList) + system.actorOf(Props(classOf[UniqueNumberSystem], guid, UniqueNumberSystemTest.AllocateNumberPoolActors(guid)), "uns") + //as long as it constructs ... + + } + } +} + +class UniqueNumberSystemTest1 extends ActorTest(ActorSystem("test")) { + class EntityTestClass extends IdentifiableEntity + + "UniqueNumberSystem" should { + "Register (success)" in { + val src : LimitedNumberSource = LimitedNumberSource(6000) + val guid : NumberPoolHub = new NumberPoolHub(src) + val pool1 = (1001 to 2000).toList + val pool2 = (3001 to 4000).toList + val pool3 = (5001 to 6000).toList + guid.AddPool("pool1", pool1).Selector = new RandomSelector + guid.AddPool("pool2", pool2).Selector = new RandomSelector + guid.AddPool("pool3", pool3).Selector = new RandomSelector + val uns = system.actorOf(Props(classOf[UniqueNumberSystem], guid, UniqueNumberSystemTest.AllocateNumberPoolActors(guid)), "uns") + assert(src.CountUsed == 0) + //pool1 + for(_ <- 1 to 100) { + val testObj = new EntityTestClass() + uns ! Register(testObj, "pool1") + val msg = receiveOne(Duration.create(100, "ms")) + assert(msg.isInstanceOf[Success[_]]) + assert(pool1.contains(testObj.GUID.guid)) + } + //pool2 + for(_ <- 1 to 100) { + val testObj = new EntityTestClass() + uns ! Register(testObj, "pool2") + val msg = receiveOne(Duration.create(100, "ms")) + assert(msg.isInstanceOf[Success[_]]) + assert(pool2.contains(testObj.GUID.guid)) + } + //pool3 + for(_ <- 1 to 100) { + val testObj = new EntityTestClass() + uns ! Register(testObj, "pool3") + val msg = receiveOne(Duration.create(100, "ms")) + assert(msg.isInstanceOf[Success[_]]) + assert(pool3.contains(testObj.GUID.guid)) + } + assert(src.CountUsed == 300) + } + } +} + +class UniqueNumberSystemTest2 extends ActorTest(ActorSystem("test")) { + class EntityTestClass extends IdentifiableEntity + + "UniqueNumberSystem" should { + "Register (success; already registered)" in { + val src : LimitedNumberSource = LimitedNumberSource(6000) + val guid : NumberPoolHub = new NumberPoolHub(src) + guid.AddPool("pool1", (1001 to 2000).toList).Selector = new RandomSelector + guid.AddPool("pool2", (3001 to 4000).toList).Selector = new RandomSelector + guid.AddPool("pool3", (5001 to 6000).toList).Selector = new RandomSelector + val uns = system.actorOf(Props(classOf[UniqueNumberSystem], guid, UniqueNumberSystemTest.AllocateNumberPoolActors(guid)), "uns") + val testObj = new EntityTestClass() + assert(!testObj.HasGUID) + assert(src.CountUsed == 0) + + uns ! Register(testObj, "pool1") + val msg1 = receiveOne(Duration.create(100, "ms")) + assert(msg1.isInstanceOf[Success[_]]) + assert(testObj.HasGUID) + assert(src.CountUsed == 1) + + val id = testObj.GUID.guid + uns ! Register(testObj, "pool2") //different pool; makes no difference + val msg2 = receiveOne(Duration.create(100, "ms")) + assert(msg2.isInstanceOf[Success[_]]) + assert(testObj.HasGUID) + assert(src.CountUsed == 1) + assert(testObj.GUID.guid == id) //unchanged + } + } + //a log.warn should have been generated during this test +} + +class UniqueNumberSystemTest3 extends ActorTest(ActorSystem("test")) { + class EntityTestClass extends IdentifiableEntity + + "UniqueNumberSystem" should { + "Register (failure; no pool)" in { + val src : LimitedNumberSource = LimitedNumberSource(6000) + val guid : NumberPoolHub = new NumberPoolHub(src) + guid.AddPool("pool1", (1001 to 2000).toList).Selector = new RandomSelector + guid.AddPool("pool2", (3001 to 4000).toList).Selector = new RandomSelector + guid.AddPool("pool3", (5001 to 6000).toList).Selector = new RandomSelector + val uns = system.actorOf(Props(classOf[UniqueNumberSystem], guid, UniqueNumberSystemTest.AllocateNumberPoolActors(guid)), "uns") + val testObj = new EntityTestClass() + assert(!testObj.HasGUID) + assert(src.CountUsed == 0) + + uns ! Register(testObj, "pool4") + val msg1 = receiveOne(Duration.create(100, "ms")) + assert(msg1.isInstanceOf[Failure[_]]) + assert(!testObj.HasGUID) + assert(src.CountUsed == 0) + } + } +} + +class UniqueNumberSystemTest4 extends ActorTest(ActorSystem("test")) { + class EntityTestClass extends IdentifiableEntity + + "UniqueNumberSystem" should { + "Register (failure; empty pool)" in { + val src : LimitedNumberSource = LimitedNumberSource(6000) + val guid : NumberPoolHub = new NumberPoolHub(src) + guid.AddPool("pool1", (1001 to 2000).toList).Selector = new RandomSelector + guid.AddPool("pool2", (3001 to 4000).toList).Selector = new RandomSelector + guid.AddPool("pool3", (5001 to 6000).toList).Selector = new RandomSelector + guid.AddPool("pool4", 50 :: Nil).Selector = new RandomSelector //list of one element; can not add an empty list + val uns = system.actorOf(Props(classOf[UniqueNumberSystem], guid, UniqueNumberSystemTest.AllocateNumberPoolActors(guid)), "uns") + + val testObj1 = new EntityTestClass() + uns ! Register(testObj1, "pool4") + val msg1 = receiveOne(Duration.create(100, "ms")) + assert(msg1.isInstanceOf[Success[_]]) //pool4 is now empty + + val testObj2 = new EntityTestClass() + uns ! Register(testObj2, "pool4") + val msg2 = receiveOne(Duration.create(100, "ms")) + assert(msg2.isInstanceOf[Failure[_]]) + } + } +} + +class UniqueNumberSystemTest5 extends ActorTest(ActorSystem("test")) { + class EntityTestClass extends IdentifiableEntity + + "UniqueNumberSystem" should { + "Unregister (success)" in { + val src : LimitedNumberSource = LimitedNumberSource(6000) + val guid : NumberPoolHub = new NumberPoolHub(src) + val pool2 = (3001 to 4000).toList + guid.AddPool("pool1", (1001 to 2000).toList).Selector = new RandomSelector + guid.AddPool("pool2", pool2).Selector = new RandomSelector + guid.AddPool("pool3", (5001 to 6000).toList).Selector = new RandomSelector + val uns = system.actorOf(Props(classOf[UniqueNumberSystem], guid, UniqueNumberSystemTest.AllocateNumberPoolActors(guid)), "uns") + val testObj = new EntityTestClass() + assert(!testObj.HasGUID) + assert(src.CountUsed == 0) + + uns ! Register(testObj, "pool2") + val msg1 = receiveOne(Duration.create(100, "ms")) + assert(msg1.isInstanceOf[Success[_]]) + assert(testObj.HasGUID) + assert(pool2.contains(testObj.GUID.guid)) + assert(src.CountUsed == 1) + + uns ! Unregister(testObj) + val msg2 = receiveOne(Duration.create(100, "ms")) + assert(msg2.isInstanceOf[Success[_]]) + assert(!testObj.HasGUID) + assert(src.CountUsed == 0) + } + } +} + +class UniqueNumberSystemTest6 extends ActorTest(ActorSystem("test")) { + class EntityTestClass extends IdentifiableEntity + + "UniqueNumberSystem" should { + "Unregister (success; object not registered at all)" in { + val src : LimitedNumberSource = LimitedNumberSource(6000) + val guid : NumberPoolHub = new NumberPoolHub(src) + guid.AddPool("pool1", (1001 to 2000).toList).Selector = new RandomSelector + guid.AddPool("pool2", (3001 to 4000).toList).Selector = new RandomSelector + guid.AddPool("pool3", (5001 to 6000).toList).Selector = new RandomSelector + val uns = system.actorOf(Props(classOf[UniqueNumberSystem], guid, UniqueNumberSystemTest.AllocateNumberPoolActors(guid)), "uns") + val testObj = new EntityTestClass() + assert(!testObj.HasGUID) + assert(src.CountUsed == 0) + + uns ! Unregister(testObj) + val msg1 = receiveOne(Duration.create(100, "ms")) + assert(msg1.isInstanceOf[Success[_]]) + assert(!testObj.HasGUID) + assert(src.CountUsed == 0) + } + } +} + +class UniqueNumberSystemTest7 extends ActorTest(ActorSystem("test")) { + class EntityTestClass extends IdentifiableEntity + + "UniqueNumberSystem" should { + "Unregister (failure; number not in system)" in { + val src : LimitedNumberSource = LimitedNumberSource(6000) + val guid : NumberPoolHub = new NumberPoolHub(src) + guid.AddPool("pool1", (1001 to 2000).toList).Selector = new RandomSelector + guid.AddPool("pool2", (3001 to 4000).toList).Selector = new RandomSelector + guid.AddPool("pool3", (5001 to 6000).toList).Selector = new RandomSelector + val uns = system.actorOf(Props(classOf[UniqueNumberSystem], guid, UniqueNumberSystemTest.AllocateNumberPoolActors(guid)), "uns") + val testObj = new EntityTestClass() + testObj.GUID = net.psforever.packet.game.PlanetSideGUID(6001) //fake registering; number too high + assert(testObj.HasGUID) + assert(src.CountUsed == 0) + + uns ! Unregister(testObj) + val msg1 = receiveOne(Duration.create(100, "ms")) + assert(msg1.isInstanceOf[Failure[_]]) + assert(testObj.HasGUID) + assert(src.CountUsed == 0) + } + } +} + +class UniqueNumberSystemTest8 extends ActorTest(ActorSystem("test")) { + class EntityTestClass extends IdentifiableEntity + + "UniqueNumberSystem" should { + "Unregister (failure; object is not registered to that number)" in { + val src : LimitedNumberSource = LimitedNumberSource(6000) + val guid : NumberPoolHub = new NumberPoolHub(src) + guid.AddPool("pool1", (1001 to 2000).toList).Selector = new RandomSelector + guid.AddPool("pool2", (3001 to 4000).toList).Selector = new RandomSelector + guid.AddPool("pool3", (5001 to 6000).toList).Selector = new RandomSelector + val uns = system.actorOf(Props(classOf[UniqueNumberSystem], guid, UniqueNumberSystemTest.AllocateNumberPoolActors(guid)), "uns") + val testObj = new EntityTestClass() + testObj.GUID = net.psforever.packet.game.PlanetSideGUID(3500) //fake registering + assert(testObj.HasGUID) + assert(src.CountUsed == 0) + + uns ! Unregister(testObj) + val msg1 = receiveOne(Duration.create(100, "ms")) + assert(msg1.isInstanceOf[Failure[_]]) + assert(testObj.HasGUID) + assert(src.CountUsed == 0) + } + } +} + +object UniqueNumberSystemTest { + /** + * @see `UniqueNumberSystem.AllocateNumberPoolActors(NumberPoolHub)(implicit ActorContext)` + */ + def AllocateNumberPoolActors(poolSource : NumberPoolHub)(implicit system : ActorSystem) : Map[String, ActorRef] = { + poolSource.Pools.map({ case ((pname, pool)) => + pname -> system.actorOf(Props(classOf[NumberPoolActor], pool), pname) + }).toMap + } +} +