From 989c19a41c0e99fe9c5a6536abce751844cc9091 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Fri, 15 Jul 2022 00:50:20 +0200 Subject: [PATCH 1/5] Consolidate SimScheduler tests, first part --- .../scheduler/ExtendedSimSchedulerSpec.scala | 1049 ----------------- .../simona/scheduler/SimSchedulerSpec.scala | 895 ++++++++++---- 2 files changed, 669 insertions(+), 1275 deletions(-) delete mode 100644 src/test/scala/edu/ie3/simona/scheduler/ExtendedSimSchedulerSpec.scala diff --git a/src/test/scala/edu/ie3/simona/scheduler/ExtendedSimSchedulerSpec.scala b/src/test/scala/edu/ie3/simona/scheduler/ExtendedSimSchedulerSpec.scala deleted file mode 100644 index 5442460528..0000000000 --- a/src/test/scala/edu/ie3/simona/scheduler/ExtendedSimSchedulerSpec.scala +++ /dev/null @@ -1,1049 +0,0 @@ -/* - * © 2020. TU Dortmund University, - * Institute of Energy Systems, Energy Efficiency and Energy Economics, - * Research group Distribution grid planning and operation - */ - -package edu.ie3.simona.scheduler - -import java.util.concurrent.{BlockingQueue, LinkedBlockingQueue, TimeUnit} -import akka.actor.{ActorRef, ActorSystem} -import akka.testkit.{ImplicitSender, TestActorRef} -import com.typesafe.config.ConfigFactory -import edu.ie3.simona.config.SimonaConfig -import edu.ie3.simona.event.RuntimeEvent -import edu.ie3.simona.event.RuntimeEvent._ -import edu.ie3.simona.event.listener.RuntimeEventListener -import edu.ie3.simona.ontology.messages.SchedulerMessage._ -import edu.ie3.simona.ontology.trigger.Trigger.{ - ActivityStartTrigger, - InitializeServiceTrigger -} -import edu.ie3.simona.service.ServiceStateData -import edu.ie3.simona.test.common.{ConfigTestData, TestKitWithShutdown} -import org.scalatest._ -import org.scalatest.matchers.should -import org.scalatest.wordspec.AnyWordSpecLike - -import scala.collection.mutable - -class ExtendedSimSchedulerSpec - extends TestKitWithShutdown( - ActorSystem( - "ExtendedSimSchedulerSpec", - ConfigFactory - .parseString(""" - |akka.loggers = ["akka.testkit.TestEventListener"] - |akka.loglevel="OFF" - """.stripMargin) - ) - ) - with ImplicitSender - with AnyWordSpecLike - with should.Matchers - with PrivateMethodTester - with ConfigTestData { - - val eventQueue: BlockingQueue[RuntimeEvent] = - new LinkedBlockingQueue[RuntimeEvent] - - private final case class DummyInitServiceStateData( - ) extends ServiceStateData - - "The extended version of the SimScheduler" should { - - def expectReturnComplete( - scheduler: ActorRef, - triggerToBeScheduled: Option[Vector[ScheduleTriggerMessage]] = None - ): Unit = { - expectMsgPF() { - case msg: TriggerWithIdMessage => - msg.trigger match { - case InitializeServiceTrigger(_) => - scheduler ! CompletionMessage(msg.triggerId, triggerToBeScheduled) - case ActivityStartTrigger(_) => - scheduler ! CompletionMessage(msg.triggerId, triggerToBeScheduled) - case _ => - fail(s"Unexpected trigger received: $msg") - } - case unexpected => - fail(s"Unexpected message received: $unexpected") - } - } - - def setupExtendedTest(simonaConfig: SimonaConfig): ActorRef = { - // clean event queue - eventQueue.clear() - - // build the run time listener - val runtimeListeners = Vector( - TestActorRef( - new RuntimeEventListener( - None, - Some(eventQueue), - simonaConfig.simona.time.startDateTime - ) - ) - ) - - // build scheduler - system.actorOf( - SimScheduler - .props( - simonaConfig.simona.time, - runtimeListeners, - stopOnFailedPowerFlow = false, - autoStart = false - ) - ) - } - - def expectSimulatingEvent( - receivedEvent: Simulating, - expectedElements: mutable.ArrayBuffer[RuntimeEvent] - ): Assertion = { - val expectedHead = expectedElements.headOption.getOrElse( - fail("ExpectedElements is empty!") - ) match { - case expectedHead: Simulating => - expectedHead - case unexpectedEvent => - fail( - s"Unexpected event when Simulating was expected: $unexpectedEvent" - ) - } - expectedElements.remove(0) - receivedEvent.startTick shouldBe expectedHead.startTick - receivedEvent.endTick shouldBe expectedHead.endTick - } - - def expectDoneEvent( - receivedEvent: Done, - expectedElements: mutable.ArrayBuffer[RuntimeEvent] - ): Boolean = { - val expectedHead = expectedElements.headOption.getOrElse( - fail("ExpectedElements is empty!") - ) match { - case expectedHead: Done => - expectedHead - case unexpectedEvent => - fail(s"Unexpected event when Done was expected: $unexpectedEvent") - } - expectedElements.remove(0) - receivedEvent.tick shouldBe expectedHead.tick - expectedElements.isEmpty - } - - "finish simulation correctly when endTick == pauseScheduleAtTick" in { - - val simonaConfig = SimonaConfig( - ConfigFactory - .parseString( - s"""simona.time.startDateTime = "2011-01-01 00:00:00" - simona.time.endDateTime = "2011-01-01 01:00:00" - simona.time.schedulerReadyCheckWindow = 900""".stripMargin - ) - .withFallback(typesafeConfig) - .resolve() - ) - - val scheduler = setupExtendedTest(simonaConfig) - - // send to init triggers to scheduler - scheduler ! ScheduleTriggerMessage( - InitializeServiceTrigger(DummyInitServiceStateData()), - testActor - ) - scheduler ! ScheduleTriggerMessage( - InitializeServiceTrigger(DummyInitServiceStateData()), - testActor - ) - - // tell scheduler to init - scheduler ! InitSimMessage - - // we expect two init triggers we need to answer with completion, otherwise scheduler won't go on - expectReturnComplete( - scheduler, - Some( - Vector(ScheduleTriggerMessage(ActivityStartTrigger(3600), testActor)) - ) - ) - expectReturnComplete( - scheduler, - Some( - Vector(ScheduleTriggerMessage(ActivityStartTrigger(3600), testActor)) - ) - ) - - // wait until init is done - var initDone = false - while (!initDone) { - eventQueue.poll(2, TimeUnit.SECONDS) match { - case InitComplete(_) => - initDone = true - case _ => false - } - } - - // tell scheduler we want to run until tick 3600 - scheduler ! StartScheduleMessage(Some(3600L)) - - // the expected events in their expected order - val expectedElements: mutable.ArrayBuffer[RuntimeEvent] = - mutable.ArrayBuffer( - Simulating(0, 3600), - CheckWindowPassed(900, 0), - CheckWindowPassed(1800, 0), - CheckWindowPassed(2700, 0), - CheckWindowPassed(3600, 0), - Ready(3600, 0), - Done(3600, 0, 0, errorInSim = false) - ) - var runDone = false - while (!runDone) { - eventQueue.poll(2, TimeUnit.SECONDS) match { - case event: CheckWindowPassed => - val expectedHead = expectedElements.headOption.getOrElse( - fail("ExpectedElements is empty!") - ) match { - case expectedHead: CheckWindowPassed => - expectedHead - case unexpectedEvent => - fail( - s"Unexpected event when CheckWindowPassed was expected: $unexpectedEvent" - ) - } - expectedElements.remove(0) - event.tick shouldBe expectedHead.tick - - if (event.tick == 2700) { - // when we passed tick 2700 we also expect that two message arrive at tick 3600 we need to answer with complete - expectReturnComplete(scheduler) - expectReturnComplete(scheduler) - - // we also expect that the scheduler answers with a SimulationSuccessfullMessage to the sender of the run request (this test) - expectMsgPF() { - case SimulationSuccessfulMessage => - case msg => - fail( - s"Unexpected message received when SimulationSuccessfulMessage was expected: $msg" - ) - } - } - case event: Simulating => - expectSimulatingEvent(event, expectedElements) - - case event: Ready => - val expectedHead = expectedElements.headOption.getOrElse( - fail("ExpectedElements is empty!") - ) match { - case expectedHead: Ready => - expectedHead - case unexpectedEvent => - fail( - s"Unexpected event when Ready was expected: $unexpectedEvent" - ) - } - expectedElements.remove(0) - event.tick shouldBe expectedHead.tick - case event: Done => - runDone = expectDoneEvent(event, expectedElements) - case unexpectedEvent => - fail(s"Unexpected event received: $unexpectedEvent") - } - } - } - - "work as expected when resolution == schedulerReadyCheckWindow" in { - - val simonaConfig = SimonaConfig( - ConfigFactory - .parseString( - s"""simona.time.startDateTime = "2011-01-01 00:00:00" - simona.time.endDateTime = "2011-01-01 01:00:00" - simona.time.schedulerReadyCheckWindow = 900""".stripMargin - ) - .withFallback(typesafeConfig) - .resolve() - ) - - val scheduler = setupExtendedTest(simonaConfig) - val resolution = 900 - - // send to init triggers to scheduler - scheduler ! ScheduleTriggerMessage( - InitializeServiceTrigger(DummyInitServiceStateData()), - testActor - ) - scheduler ! ScheduleTriggerMessage( - InitializeServiceTrigger(DummyInitServiceStateData()), - testActor - ) - - // tell scheduler to init - scheduler ! InitSimMessage - - // we expect two init triggers we need to answer with completion, otherwise scheduler won't go on - expectReturnComplete( - scheduler, - Some( - Vector( - ScheduleTriggerMessage(ActivityStartTrigger(resolution), testActor) - ) - ) - ) - expectReturnComplete( - scheduler, - Some( - Vector( - ScheduleTriggerMessage(ActivityStartTrigger(resolution), testActor) - ) - ) - ) - - // wait until init is done - var initDone = false - while (!initDone) { - eventQueue.poll(2, TimeUnit.SECONDS) match { - case InitComplete(_) => - initDone = true - case _ => false - } - } - - // tell scheduler we want to run until tick 3600 - scheduler ! StartScheduleMessage() - - // the expected events in their expected order - val expectedElements: mutable.ArrayBuffer[RuntimeEvent] = - mutable.ArrayBuffer( - Simulating(0, 3600), - CheckWindowPassed(900, 0), - CheckWindowPassed(1800, 0), - CheckWindowPassed(2700, 0), - CheckWindowPassed(3600, 0), - Done(3600, 0, 0, errorInSim = false) - ) - - // expect a simulating event - eventQueue.poll(2, TimeUnit.SECONDS) match { - case simulating: Simulating => - expectSimulatingEvent(simulating, expectedElements) - case event => fail(s"Expected Simulating event but received $event") - } - - // return with completion incl. new trigger for resolution*2 - expectReturnComplete( - scheduler, - Some( - Vector( - ScheduleTriggerMessage( - ActivityStartTrigger(resolution * 2), - testActor - ) - ) - ) - ) - expectReturnComplete( - scheduler, - Some( - Vector( - ScheduleTriggerMessage( - ActivityStartTrigger(resolution * 2), - testActor - ) - ) - ) - ) - - // expect a checkWindowPassed event - eventQueue.poll(2, TimeUnit.SECONDS) match { - case event: CheckWindowPassed => - val expectedHead = expectedElements.headOption.getOrElse( - fail("ExpectedElements is empty!") - ) match { - case expectedHead: CheckWindowPassed => - expectedHead - case unexpectedEvent => - fail( - s"Unexpected event when CheckWindowPassed was expected: $unexpectedEvent" - ) - } - expectedElements.remove(0) - event.tick shouldBe expectedHead.tick - - case event => fail(s"Expected Simulating event but received $event") - } - - // return with completion incl. new trigger for resolution*3 - expectReturnComplete( - scheduler, - Some( - Vector( - ScheduleTriggerMessage( - ActivityStartTrigger(resolution * 3), - testActor - ) - ) - ) - ) - expectReturnComplete( - scheduler, - Some( - Vector( - ScheduleTriggerMessage( - ActivityStartTrigger(resolution * 3), - testActor - ) - ) - ) - ) - - // expect a checkWindowPassed event - eventQueue.poll(2, TimeUnit.SECONDS) match { - case event: CheckWindowPassed => - val expectedHead = expectedElements.headOption.getOrElse( - fail("ExpectedElements is empty!") - ) match { - case expectedHead: CheckWindowPassed => - expectedHead - case unexpectedEvent => - fail( - s"Unexpected event when CheckWindowPassed was expected: $unexpectedEvent" - ) - } - expectedElements.remove(0) - event.tick shouldBe expectedHead.tick - - case event => fail(s"Expected Simulating event but received $event") - } - - // return with completion incl. new trigger for resolution*4 - expectReturnComplete( - scheduler, - Some( - Vector( - ScheduleTriggerMessage( - ActivityStartTrigger(resolution * 4), - testActor - ) - ) - ) - ) - expectReturnComplete( - scheduler, - Some( - Vector( - ScheduleTriggerMessage( - ActivityStartTrigger(resolution * 4), - testActor - ) - ) - ) - ) - - // expect a checkWindowPassed for tick 2700, 3600 and a Done event - var runDone = false - while (!runDone) { - eventQueue.poll(2, TimeUnit.SECONDS) match { - case event: CheckWindowPassed => - val expectedHead = expectedElements.headOption.getOrElse( - fail("ExpectedElements is empty!") - ) match { - case expectedHead: CheckWindowPassed => - expectedHead - case unexpectedEvent => - fail( - s"Unexpected event when CheckWindowPassed was expected: $unexpectedEvent" - ) - } - expectedElements.remove(0) - event.tick shouldBe expectedHead.tick - - if (event.tick == 2700) { - // when we passed tick 2700 we also expect that two message arrive at tick 3600 we need to answer with complete - expectReturnComplete(scheduler) - expectReturnComplete(scheduler) - - // we also expect that the scheduler answers with a SimulationSuccessfullMessage to the sender of the run request (this test) - expectMsgPF() { - case SimulationSuccessfulMessage => - case msg => - fail( - s"Unexpected message received when SimulationSuccessfulMessage was expected: $msg" - ) - } - } - - case event: Done => - runDone = expectDoneEvent(event, expectedElements) - case event => fail(s"Expected Simulating event but received $event") - } - } - - expectedElements.size shouldBe 0 - - } - - "finish simulation correctly when endTick < pauseScheduleAtTick" in { - - val simonaConfig = SimonaConfig( - ConfigFactory - .parseString( - s"""simona.time.startDateTime = "2011-01-01 00:00:00" - simona.time.endDateTime = "2011-01-01 01:00:00" - simona.time.schedulerReadyCheckWindow = 900""".stripMargin - ) - .withFallback(typesafeConfig) - .resolve() - ) - - val scheduler = setupExtendedTest(simonaConfig) - - // send to init triggers to scheduler - scheduler ! ScheduleTriggerMessage( - InitializeServiceTrigger(DummyInitServiceStateData()), - testActor - ) - scheduler ! ScheduleTriggerMessage( - InitializeServiceTrigger(DummyInitServiceStateData()), - testActor - ) - - // tell scheduler to init - scheduler ! InitSimMessage - - // we expect two init triggers we need to answer with completion, otherwise scheduler won't go on - expectReturnComplete( - scheduler, - Some( - Vector(ScheduleTriggerMessage(ActivityStartTrigger(3600), testActor)) - ) - ) - expectReturnComplete( - scheduler, - Some( - Vector(ScheduleTriggerMessage(ActivityStartTrigger(3600), testActor)) - ) - ) - - // wait until init is done - var initDone = false - while (!initDone) { - eventQueue.poll(2, TimeUnit.SECONDS) match { - case InitComplete(_) => - initDone = true - case _ => false - } - } - - // tell scheduler we want to run until tick 7200 (endTick == 3600) - scheduler ! StartScheduleMessage(Some(7200L)) - - val expectedElements: mutable.ArrayBuffer[RuntimeEvent] = - mutable.ArrayBuffer( - Simulating(0, 3600), - CheckWindowPassed(900, 0), - CheckWindowPassed(1800, 0), - CheckWindowPassed(2700, 0), - CheckWindowPassed(3600, 0), - Done(3600, 0, 0, errorInSim = false) - ) - - var runDone = false - while (!runDone) { - eventQueue.poll(2, TimeUnit.SECONDS) match { - case event: CheckWindowPassed => - val expectedHead = expectedElements.headOption.getOrElse( - fail("ExpectedElements is empty!") - ) match { - case expectedHead: CheckWindowPassed => - expectedHead - case unexpectedEvent => - fail( - s"Unexpected event when CheckWindowPassed was expected: $unexpectedEvent" - ) - } - expectedElements.remove(0) - event.tick shouldBe expectedHead.tick - - if (event.tick == 2700) { - // when we passed tick 2700 we also expect that two message arrive at tick 3600 we need to answer with complete - expectReturnComplete(scheduler) - expectReturnComplete(scheduler) - - // we also expect that the scheduler answers with a SimulationSuccessfullMessage to the sender of the run request (this test) - expectMsgPF() { - case SimulationSuccessfulMessage => - case msg => - fail( - s"Unexpected message received when SimulationSuccessfulMessage was expected: $msg" - ) - } - } - case event: Simulating => - expectSimulatingEvent(event, expectedElements) - case event: Done => - runDone = expectDoneEvent(event, expectedElements) - case unexpectedEvent => - fail(s"Unexpected event received: $unexpectedEvent") - } - } - } - - "finish simulation correctly when endTick > first pauseScheduleAtTick" in { - - val simonaConfig = SimonaConfig( - ConfigFactory - .parseString( - s"""simona.time.startDateTime = "2011-01-01 00:00:00" - simona.time.endDateTime = "2011-01-01 02:00:00" - simona.time.schedulerReadyCheckWindow = 900""".stripMargin - ) - .withFallback(typesafeConfig) - .resolve() - ) - - val scheduler = setupExtendedTest(simonaConfig) - - // send to init triggers to scheduler - scheduler ! ScheduleTriggerMessage( - InitializeServiceTrigger(DummyInitServiceStateData()), - testActor - ) - scheduler ! ScheduleTriggerMessage( - InitializeServiceTrigger(DummyInitServiceStateData()), - testActor - ) - - // tell scheduler to init - scheduler ! InitSimMessage - - // we expect two init triggers we need to answer with completion, otherwise scheduler won't go on - expectReturnComplete( - scheduler, - Some( - Vector(ScheduleTriggerMessage(ActivityStartTrigger(3600), testActor)) - ) - ) - expectReturnComplete( - scheduler, - Some( - Vector(ScheduleTriggerMessage(ActivityStartTrigger(3600), testActor)) - ) - ) - - // wait until init is done - var initDone = false - while (!initDone) { - eventQueue.poll(2, TimeUnit.SECONDS) match { - case InitComplete(_) => - initDone = true - case _ => false - } - } - - // tell scheduler we want to run until tick 7200 (endTick == 3600) - scheduler ! StartScheduleMessage(Some(3600L)) - - val expectedElements: mutable.ArrayBuffer[RuntimeEvent] = - mutable.ArrayBuffer( - Simulating(0, 3600), - CheckWindowPassed(900, 0), - CheckWindowPassed(1800, 0), - CheckWindowPassed(2700, 0), - CheckWindowPassed(3600, 0), - Ready(3600, 0), - Simulating(3601, 7200), - CheckWindowPassed(4500, 0), - CheckWindowPassed(5400, 0), - CheckWindowPassed(6300, 0), - CheckWindowPassed(7200, 0), - Ready(7200, 0), - Done(7200, 0, 0, errorInSim = false) - ) - - var runDone = false - while (!runDone) { - eventQueue.poll(2, TimeUnit.SECONDS) match { - case event: CheckWindowPassed => - val expectedHead = expectedElements.headOption.getOrElse( - fail("ExpectedElements is empty!") - ) match { - case expectedHead: CheckWindowPassed => - expectedHead - case unexpectedEvent => - fail( - s"Unexpected event when CheckWindowPassed was expected: $unexpectedEvent" - ) - } - expectedElements.remove(0) - event.tick shouldBe expectedHead.tick - - if (event.tick == 2700 || event.tick == 6300) { - // when we passed tick 2700 or 6300 we also expect that two message arrive at tick 3600 we need to answer with complete - expectReturnComplete( - scheduler, - Some( - Vector( - ScheduleTriggerMessage( - ActivityStartTrigger(7200), - testActor - ) - ) - ) - ) - expectReturnComplete( - scheduler, - Some( - Vector( - ScheduleTriggerMessage( - ActivityStartTrigger(7200), - testActor - ) - ) - ) - ) - } - - if (event.tick == 6300) { - expectReturnComplete(scheduler) - expectReturnComplete(scheduler) - // we also expect that the scheduler answers with a SimulationSuccessfulMessage @ tick 6300 to the sender of the run request (this test) - expectMsgPF() { - case SimulationSuccessfulMessage => - case msg => - fail( - s"Unexpected message received when SimulationSuccessfulMessage was expected: $msg" - ) - - } - } - - case event: Simulating => - expectSimulatingEvent(event, expectedElements) - case event: Ready => - val expectedHead = expectedElements.headOption.getOrElse( - fail("ExpectedElements is empty!") - ) match { - case expectedHead: Ready => - expectedHead - case unexpectedEvent => - fail( - s"Unexpected event when Ready was expected: $unexpectedEvent" - ) - } - expectedElements.remove(0) - event.tick shouldBe expectedHead.tick - - // when we received the first ready, we need to tell the scheduler to go on - if (event.tick == 3600) - scheduler ! StartScheduleMessage(Some(7200L)) - - case event: Done => - runDone = expectDoneEvent(event, expectedElements) - case unexpectedEvent => - fail(s"Unexpected event received: $unexpectedEvent") - } - } - } - - "stop correctly for a pauseScheduleAtTick if endTick - pausescheduleAtTick = 1" in { - - val simonaConfig = SimonaConfig( - ConfigFactory - .parseString( - s"""simona.time.startDateTime = "2011-01-01 00:00:00" - simona.time.endDateTime = "2011-01-01 01:00:00" - simona.time.schedulerReadyCheckWindow = 900""".stripMargin - ) - .withFallback(typesafeConfig) - .resolve() - ) - - val scheduler = setupExtendedTest(simonaConfig) - - // send to init triggers to scheduler - scheduler ! ScheduleTriggerMessage( - InitializeServiceTrigger(DummyInitServiceStateData()), - testActor - ) - scheduler ! ScheduleTriggerMessage( - InitializeServiceTrigger(DummyInitServiceStateData()), - testActor - ) - - // tell scheduler to init - scheduler ! InitSimMessage - - // we expect two init triggers we need to answer with completion, otherwise scheduler won't go on - expectReturnComplete( - scheduler, - Some( - Vector(ScheduleTriggerMessage(ActivityStartTrigger(3600), testActor)) - ) - ) - expectReturnComplete( - scheduler, - Some( - Vector(ScheduleTriggerMessage(ActivityStartTrigger(3600), testActor)) - ) - ) - - // wait until init is done - var initDone = false - while (!initDone) { - eventQueue.poll(2, TimeUnit.SECONDS) match { - case InitComplete(_) => - initDone = true - case _ => false - } - } - - // tell scheduler we want to run until tick 7200 (endTick == 3600) - scheduler ! StartScheduleMessage(Some(3599L)) - - val expectedElements: mutable.ArrayBuffer[RuntimeEvent] = - mutable.ArrayBuffer( - Simulating(0, 3599), - CheckWindowPassed(900, 0), - CheckWindowPassed(1800, 0), - CheckWindowPassed(2700, 0), - Ready(3599, 0), - Simulating(3600, 3600), - CheckWindowPassed(3600, 0), - Ready(3600, 0), - Done(3600, 0, 0, errorInSim = false) - ) - - var runDone = false - while (!runDone) { - eventQueue.poll(2, TimeUnit.SECONDS) match { - case event: CheckWindowPassed => - val expectedHead = expectedElements.headOption.getOrElse( - fail("ExpectedElements is empty!") - ) match { - case expectedHead: CheckWindowPassed => - expectedHead - case unexpectedEvent => - fail( - s"Unexpected event when CheckWindowPassed was expected: $unexpectedEvent" - ) - } - expectedElements.remove(0) - event.tick shouldBe expectedHead.tick - - case event: Simulating => - expectSimulatingEvent(event, expectedElements) - case event: Ready => - val expectedHead = expectedElements.headOption.getOrElse( - fail("ExpectedElements is empty!") - ) match { - case expectedHead: Ready => - expectedHead - case unexpectedEvent => - fail( - s"Unexpected event when Ready was expected: $unexpectedEvent" - ) - } - expectedElements.remove(0) - event.tick shouldBe expectedHead.tick - - // when we received the first ready after 3599 ticks, we need to tell the scheduler to go on - if (event.tick == 3599) { - scheduler ! StartScheduleMessage(Some(3600L)) - - // when continue the sim we also expect that two message arrive at tick 3600 we need to answer with complete - expectReturnComplete( - scheduler, - Some( - Vector( - ScheduleTriggerMessage( - ActivityStartTrigger(7200), - testActor - ) - ) - ) - ) - expectReturnComplete( - scheduler, - Some( - Vector( - ScheduleTriggerMessage( - ActivityStartTrigger(7200), - testActor - ) - ) - ) - ) - } - - case event: Done => - runDone = expectDoneEvent(event, expectedElements) - case unexpectedEvent => - fail(s"Unexpected event received: $unexpectedEvent") - } - } - - expectMsgPF() { - case SimulationSuccessfulMessage => - case msg => - fail( - s"Unexpected message received when SimulationSuccessfulMessage was expected: $msg" - ) - } - } - - "should work correctly if the parallel window is disabled (== 0), powerflow resolution == 1 and readyCheckWindow == 1" in { - - def checkWindowPassed( - expectedElements: mutable.ArrayBuffer[RuntimeEvent], - timeoutInSec: Int = 2 - ) = { - // expect check window passed - eventQueue.poll(timeoutInSec, TimeUnit.SECONDS) match { - case event: CheckWindowPassed => - val expectedHead = expectedElements.headOption.getOrElse( - fail("ExpectedElements is empty!") - ) match { - case expectedHead: CheckWindowPassed => - expectedHead - case unexpectedEvent => - fail( - s"Unexpected event when CheckWindowPassed was expected: $unexpectedEvent" - ) - } - expectedElements.remove(0) - event.tick shouldBe expectedHead.tick - - case event => fail(s"Expected Simulating event but received $event") - } - - } - - // test configuration part - val noOfTestActors = - Range(1, 2) // todo does not work with more than one actor - - // the expected events in their expected order - val expectedElements: mutable.ArrayBuffer[RuntimeEvent] = - mutable.ArrayBuffer( - Simulating(0, 10), - CheckWindowPassed(1, 0), - CheckWindowPassed(2, 0), - CheckWindowPassed(3, 0), - CheckWindowPassed(4, 0), - CheckWindowPassed(5, 0), - CheckWindowPassed(6, 0), - CheckWindowPassed(7, 0), - CheckWindowPassed(8, 0), - CheckWindowPassed(9, 0), - CheckWindowPassed(10, 0), - Ready(10, 0), - Done(10, 0, 0, errorInSim = false) - ) - - val simonaConfig = SimonaConfig( - ConfigFactory - .parseString( - s"""simona.time.startDateTime = "2011-01-01 00:00:00" - simona.time.endDateTime = "2011-01-01 00:00:10" - simona.time.schedulerReadyCheckWindow = 1""".stripMargin - ) - .withFallback(typesafeConfig) - .resolve() - ) - - val scheduler = setupExtendedTest(simonaConfig) - - noOfTestActors.foreach(_ => - // send to init trigger to scheduler - scheduler ! ScheduleTriggerMessage( - InitializeServiceTrigger(DummyInitServiceStateData()), - testActor - ) - ) - - // tell scheduler to init - scheduler ! InitSimMessage - - // we expect one init trigger we need to answer with completion, otherwise scheduler won't go on - expectReturnComplete( - scheduler, - Some( - Vector(ScheduleTriggerMessage(ActivityStartTrigger(1), testActor)) - ) - ) - - // wait until init is done - var initDone = false - while (!initDone) { - eventQueue.poll(2, TimeUnit.SECONDS) match { - case InitComplete(_) => - initDone = true - case _ => - initDone = false - } - } - - // tell scheduler we want to run until tick 10 - scheduler ! StartScheduleMessage(Some(10L)) - - // expect a simulating event - eventQueue.poll(2, TimeUnit.SECONDS) match { - case simulating: Simulating => - expectSimulatingEvent(simulating, expectedElements) - case event => fail(s"Expected Simulating event but received $event") - } - - // interact with the scheduler for each tick - for (tick <- 2 to 10) { - noOfTestActors.foreach(_ => - expectReturnComplete( - scheduler, - Some( - Vector( - ScheduleTriggerMessage( - ActivityStartTrigger(tick), - testActor - ) - ) - ) - ) - ) - // we expect exactly one check window passed event as - // all activity start triggers for the last tick should be answered - checkWindowPassed(expectedElements) - eventQueue.size() shouldBe 0 - } - - // answer with completion for the last tick, schedule another tick to test if the simulation event terminates as - // expected - noOfTestActors.foreach(_ => - expectReturnComplete( - scheduler, - Some( - Vector( - ScheduleTriggerMessage( - ActivityStartTrigger(11), - testActor - ) - ) - ) - ) - ) - - // finally expect a simulation successful message - expectMsgPF() { - case SimulationSuccessfulMessage => - case msg => - fail( - s"Unexpected message received when SimulationSuccessfulMessage was expected: $msg" - ) - - } - - } - } -} diff --git a/src/test/scala/edu/ie3/simona/scheduler/SimSchedulerSpec.scala b/src/test/scala/edu/ie3/simona/scheduler/SimSchedulerSpec.scala index 28868adf8d..3a3be1e682 100644 --- a/src/test/scala/edu/ie3/simona/scheduler/SimSchedulerSpec.scala +++ b/src/test/scala/edu/ie3/simona/scheduler/SimSchedulerSpec.scala @@ -6,27 +6,26 @@ package edu.ie3.simona.scheduler -import java.util.concurrent.{BlockingQueue, LinkedBlockingQueue, TimeUnit} import akka.actor._ -import akka.testkit.{ImplicitSender, TestActorRef} -import akka.util.Timeout +import akka.testkit.{ImplicitSender, TestActorRef, TestProbe} import com.typesafe.config.ConfigFactory import edu.ie3.simona.config.SimonaConfig -import edu.ie3.simona.event.RuntimeEvent -import edu.ie3.simona.event.RuntimeEvent.{ - CheckWindowPassed, - Done, - Initializing, - Simulating -} -import edu.ie3.simona.event.listener.RuntimeEventListener +import edu.ie3.simona.event.RuntimeEvent._ import edu.ie3.simona.ontology.messages.SchedulerMessage._ -import edu.ie3.simona.ontology.trigger.Trigger.InitializeServiceTrigger -import edu.ie3.simona.service.ServiceStateData +import edu.ie3.simona.ontology.trigger.Trigger +import edu.ie3.simona.ontology.trigger.Trigger.{ + ActivityStartTrigger, + InitializeTrigger +} import edu.ie3.simona.test.common.{ConfigTestData, TestKitWithShutdown} +import edu.ie3.simona.util.SimonaConstants +import org.mockito.Mockito.doReturn import org.scalatest.PrivateMethodTester import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpecLike +import org.scalatestplus.mockito.MockitoSugar.mock + +import scala.reflect.ClassTag class SimSchedulerSpec extends TestKitWithShutdown( @@ -47,236 +46,680 @@ class SimSchedulerSpec with PrivateMethodTester with ConfigTestData { - implicit val timeout: Timeout = Timeout(10, TimeUnit.SECONDS) - - private final case class DummyInitServiceStateData( - ) extends ServiceStateData - - // setup values - val resolutionInSec = 3600 - val pauseScheduleAtTick: Long = Long.MaxValue - val triggerId = 0 - val eventQueue: BlockingQueue[RuntimeEvent] = - new LinkedBlockingQueue[RuntimeEvent] - - // setup config for scheduler - private val config = ConfigFactory - .parseString(s"""simona.time.startDateTime = "2011-01-01 00:00:00" - simona.time.endDateTime = "2011-01-01 01:00:00" - simona.input.grid.datasource.id = "csv" - simona.time.schedulerReadyCheckWindow = 900 - simona.output.base.dir = "testOutput/" - simona.output.grid = { - notifier = "grid" - nodes = false - lines = false - switches = false - transformers2w = false - transformers3w = false - } - simona.output.participant.defaultConfig = { - notifier = "default" - powerRequestReply = false - simulationResult = false - } - simona.output.participant.individualConfigs = [] - simona.input.grid.datasource.id = "csv" - simona.input.grid.datasource.csvParams.directoryPath = "netdata" - simona.input.grid.datasource.csvParams.csvSep ="," - simona.input.grid.datasource.csvParams.isHierarchic = false - simona.runtime.participant.load = { - defaultConfig = { - calculateMissingReactivePowerWithModel = false - uuids = ["default"] - scaling = 1.0 - modelBehaviour = "fix" - reference = "power" - } - individualConfigs = [] - } - simona.runtime.participant.fixedFeedIn = { - defaultConfig = { - calculateMissingReactivePowerWithModel = false - uuids = ["default"] - scaling = 1.0 - } - individualConfigs = [ - { - calculateMissingReactivePowerWithModel = false - uuids = ["4eeaf76a-ec17-4fc3-872d-34b7d6004b03"] - scaling = 1.0 - } - ] - } - - simona.runtime.participant.pv = { - defaultConfig = { - calculateMissingReactivePowerWithModel = false - uuids = ["default"] - scaling = 1.0 - } - individualConfigs = [ - { - calculateMissingReactivePowerWithModel = false - uuids = ["4eeaf76a-ec17-4fc3-872d-34b7d6004b03"] - scaling = 1.0 - } - ] - } - - simona.runtime.participant.wec = { - defaultConfig = { - calculateMissingReactivePowerWithModel = false - uuids = ["default"] - scaling = 1.0 - } - individualConfigs = [ - { - calculateMissingReactivePowerWithModel = false - uuids = ["4eeaf76a-ec17-4fc3-872d-34b7d6004b03"] - scaling = 1.0 - } - ] - } - - simona.runtime.participant.evcs = { - defaultConfig = { - calculateMissingReactivePowerWithModel = false - uuids = ["default"] - scaling = 1.0 - } - individualConfigs = [ - { - calculateMissingReactivePowerWithModel = false - uuids = ["4eeaf76a-ec17-4fc3-872d-34b7d6004b03"] - scaling = 1.0 - } - ] - } - - simona.powerflow.maxSweepPowerDeviation = 1E-5 // the maximum allowed deviation in power between two sweeps, before overall convergence is assumed - simona.powerflow.skipOnFailure = true - simona.powerflow.resolution = "${resolutionInSec}s" - simona.powerflow.newtonraphson.epsilon = [1E-12] - simona.powerflow.newtonraphson.iterations = 50 - simona.simulationName = "ConfigTestDataSimulation" - simona.gridConfig.refSystems = [] - """) - .resolve() - override protected val simonaConfig: SimonaConfig = SimonaConfig(config) - - // build the run time listener - val runtimeListeners = Vector( - TestActorRef( - new RuntimeEventListener( - None, - Some(eventQueue), - simonaConfig.simona.time.startDateTime - ) - ) + private val defaultTimeConfig = SimonaConfig.Simona.Time( + startDateTime = "2011-01-01 00:00:00", + endDateTime = "2011-01-01 01:00:00", + schedulerReadyCheckWindow = Some(900), + stopOnFailedPowerFlow = false ) - // build the scheduler - private val scheduler = TestActorRef( - new SimScheduler( - simonaConfig.simona.time, - runtimeListeners, - simonaConfig.simona.time.stopOnFailedPowerFlow + def setupScheduler( + autostart: Boolean = false, + stopOnFailedPowerFlow: Boolean = false, + timeConfig: SimonaConfig.Simona.Time = defaultTimeConfig + ): (TestActorRef[SimScheduler], TestProbe) = { + val resultEventListener = TestProbe("ResultEventListener") + + val simScheduler = TestActorRef( + new SimScheduler( + timeConfig, + Iterable(resultEventListener.ref), + stopOnFailedPowerFlow = stopOnFailedPowerFlow, + autoStart = autostart + ) ) - ) - "The SimScheduler" must { + (simScheduler, resultEventListener) + } + + "The SimScheduler" should { + + "initialize as expected when receiving triggers before InitSimMessage" in { + val (simScheduler, resultEventListener) = setupScheduler() + + val triggeredAgent1 = TestProbe() + val initTrigger1 = createMockInitTrigger() + simScheduler ! ScheduleTriggerMessage( + initTrigger1, + triggeredAgent1.ref + ) + + val triggeredAgent2 = TestProbe() + val initTrigger2 = createMockInitTrigger() + simScheduler ! + ScheduleTriggerMessage( + initTrigger2, + triggeredAgent2.ref + ) + + triggeredAgent1.expectNoMessage() + triggeredAgent2.expectNoMessage() + + simScheduler ! InitSimMessage + + resultEventListener.expectMsg(Initializing) + + val receivedTrigger1 = + triggeredAgent1.expectMsgType[TriggerWithIdMessage] + receivedTrigger1.trigger shouldBe initTrigger1 + receivedTrigger1.receiverActor shouldBe triggeredAgent1.ref + + val receivedTrigger2 = + triggeredAgent2.expectMsgType[TriggerWithIdMessage] + receivedTrigger2.trigger shouldBe initTrigger2 + receivedTrigger2.receiverActor shouldBe triggeredAgent2.ref + + triggeredAgent1.send( + simScheduler, + CompletionMessage( + receivedTrigger1.triggerId, + None + ) + ) + + resultEventListener.expectNoMessage() + + triggeredAgent2.send( + simScheduler, + CompletionMessage( + receivedTrigger2.triggerId, + None + ) + ) + + resultEventListener.expectMsgType[InitComplete] + } + + "initialize as expected when receiving triggers after init trigger" in { + val (simScheduler, resultEventListener) = setupScheduler() + + simScheduler ! InitSimMessage + + resultEventListener.expectMsg(Initializing) + + val triggeredAgent1 = TestProbe() + val initTrigger1 = createMockInitTrigger() + simScheduler ! ScheduleTriggerMessage( + initTrigger1, + triggeredAgent1.ref + ) + + val triggeredAgent2 = TestProbe() + val initTrigger2 = createMockInitTrigger() + simScheduler ! ScheduleTriggerMessage( + initTrigger2, + triggeredAgent2.ref + ) + + // trigger are sent right away + val receivedTrigger1 = + triggeredAgent1.expectMsgType[TriggerWithIdMessage] + receivedTrigger1.trigger shouldBe initTrigger1 + receivedTrigger1.receiverActor shouldBe triggeredAgent1.ref + + val receivedTrigger2 = + triggeredAgent2.expectMsgType[TriggerWithIdMessage] + receivedTrigger2.trigger shouldBe initTrigger2 + receivedTrigger2.receiverActor shouldBe triggeredAgent2.ref + + triggeredAgent1.send( + simScheduler, + CompletionMessage( + receivedTrigger1.triggerId, + None + ) + ) + + resultEventListener.expectNoMessage() + + triggeredAgent2.send( + simScheduler, + CompletionMessage( + receivedTrigger2.triggerId, + None + ) + ) + + resultEventListener.expectMsgType[InitComplete] + } + + "start simulation when autostart is enabled" in { + val (simScheduler, resultEventListener) = setupScheduler(autostart = true) + + val triggeredAgent1 = TestProbe() + val initTrigger1 = createMockInitTrigger() + simScheduler ! ScheduleTriggerMessage( + initTrigger1, + triggeredAgent1.ref + ) + + triggeredAgent1.expectNoMessage() + + simScheduler ! InitSimMessage - "notify the runtime event listener when agents and services are busy initializing " in { + resultEventListener.expectMsg(Initializing) - // send the start schedule message to the scheduler - scheduler ! InitSimMessage + val receivedTrigger1Init = + triggeredAgent1.expectMsgType[TriggerWithIdMessage] + receivedTrigger1Init.trigger shouldBe initTrigger1 + receivedTrigger1Init.receiverActor shouldBe triggeredAgent1.ref - // expect the first runtime event to be initializing - eventQueue.take() match { - case Initializing => - // to receive the next Ready event - // necessary to receive a valid trigger that can be completed - scheduler ! ScheduleTriggerMessage( - InitializeServiceTrigger(DummyInitServiceStateData()), - self + triggeredAgent1.send( + simScheduler, + CompletionMessage( + receivedTrigger1Init.triggerId, + Some( + Seq( + ScheduleTriggerMessage( + ActivityStartTrigger(0L), + triggeredAgent1.ref + ), + ScheduleTriggerMessage( + ActivityStartTrigger(900L), + triggeredAgent1.ref + ) + ) ) + ) + ) - // expect a trigger message from the scheduler which when completed will provide with a - // valid completion message to be sent to the scheduler - val triggerWithIdMessage = expectMsgPF() { - case triggerWithIdMessage @ TriggerWithIdMessage( - InitializeServiceTrigger(_), - _, - _ - ) => - triggerWithIdMessage - case _ => - fail() - } - - // send completion message with a valid trigger ID - scheduler ! CompletionMessage(triggerWithIdMessage.triggerId) - - case _ => fail() - } + resultEventListener.expectMsgType[InitComplete] + resultEventListener.expectMsg(Simulating(0L, 3600L)) + + val receivedTrigger1Start = + triggeredAgent1.expectMsgType[TriggerWithIdMessage] + receivedTrigger1Start.trigger shouldBe ActivityStartTrigger(0L) + receivedTrigger1Start.receiverActor shouldBe triggeredAgent1.ref } - "notify Ready and Simulating events depending on schedulerReadyCheckWindow" + - "notify Done on successful simulation termination" in { - - var pass = false - - simonaConfig.simona.time.schedulerReadyCheckWindow match { - case Some(pauseScheduleCheck) => - var occurrences = (resolutionInSec / pauseScheduleCheck) + 1 - - // check if Ready and Simulating events are notified according to - // the config value provided in simona.time.schedulerReadyCheckWindow - while (occurrences != 0) { - pass = eventQueue.take() match { - case _: CheckWindowPassed => - true - case _: Simulating => true - case _ => false - } - occurrences -= 1 - } - - // the final event thrown should be Done - pass = eventQueue.take() match { - case _: Done => true - case _ => false - } - - case None => - // The only event thrown in the absence of a value in - // schedulerReadyCheckWindow should be Done - pass = eventQueue.take() match { - case _: Done => true - case _ => false - } - } - pass should be - true + "send triggers as expected when triggered at x*schedulerReadyCheckWindow" in { + val (simScheduler, resultEventListener) = setupScheduler() + + simScheduler ! InitSimMessage + resultEventListener.expectMsg(Initializing) + + val triggeredAgent1 = TestProbe() + + val initTrigger1 = createMockInitTrigger() + simScheduler ! ScheduleTriggerMessage( + initTrigger1, + triggeredAgent1.ref + ) + + expectTriggerAndComplete[InitializeTrigger]( + simScheduler, + resultEventListener, + triggeredAgent1, + SimonaConstants.INIT_SIM_TICK, + Seq(0L) + ) + + resultEventListener.expectMsgType[InitComplete] + + simScheduler ! StartScheduleMessage(Some(3600L)) + + resultEventListener.expectMsg(Simulating(0L, 3600L)) + + expectTriggerAndComplete[ActivityStartTrigger]( + simScheduler, + resultEventListener, + triggeredAgent1, + 0L, + Seq(900L) + ) + + expectTriggerAndComplete[ActivityStartTrigger]( + simScheduler, + resultEventListener, + triggeredAgent1, + 900L, + Seq(1800L) + ) + + resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 900L + + expectTriggerAndComplete[ActivityStartTrigger]( + simScheduler, + resultEventListener, + triggeredAgent1, + 1800L, + Seq(2700L) + ) + + resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 1800L + + expectTriggerAndComplete[ActivityStartTrigger]( + simScheduler, + resultEventListener, + triggeredAgent1, + 2700L, + Seq(3600L) + ) + + resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 2700L + + expectTriggerAndComplete[ActivityStartTrigger]( + simScheduler, + resultEventListener, + triggeredAgent1, + 3600L + ) + + resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 3600L + resultEventListener.expectMsgType[Ready].tick shouldBe 3600L + val doneMsg = resultEventListener.expectMsgType[Done] + doneMsg.tick shouldBe 3600L + doneMsg.noOfFailedPF shouldBe 0 + doneMsg.errorInSim shouldBe false + + // we expect that the scheduler answers with a SimulationSuccessfulMessage + // to the sender of the run request (this test) + expectMsg(SimulationSuccessfulMessage) + } + + "finish simulation correctly when endTick == pauseScheduleAtTick" in { + val (simScheduler, resultEventListener) = setupScheduler() + + simScheduler ! InitSimMessage + resultEventListener.expectMsg(Initializing) + + val triggeredAgent1 = TestProbe() + + val initTrigger1 = createMockInitTrigger() + simScheduler ! ScheduleTriggerMessage( + initTrigger1, + triggeredAgent1.ref + ) + val receivedTrigger1Init = + triggeredAgent1.expectMsgType[TriggerWithIdMessage] + + triggeredAgent1.send( + simScheduler, + CompletionMessage( + receivedTrigger1Init.triggerId, + Some( + Seq( + ScheduleTriggerMessage( + ActivityStartTrigger(3600L), + triggeredAgent1.ref + ) + ) + ) + ) + ) + + resultEventListener.expectMsgType[InitComplete] + + triggeredAgent1.expectNoMessage() + + simScheduler ! StartScheduleMessage(Some(3600L)) + + resultEventListener.expectMsg(Simulating(0L, 3600L)) + resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 900L + resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 1800L + resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 2700L + + expectTriggerAndComplete[ActivityStartTrigger]( + simScheduler, + resultEventListener, + triggeredAgent1, + 3600L + ) + + resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 3600L + resultEventListener.expectMsgType[Ready].tick shouldBe 3600L + val doneMsg = resultEventListener.expectMsgType[Done] + doneMsg.tick shouldBe 3600L + doneMsg.noOfFailedPF shouldBe 0 + doneMsg.errorInSim shouldBe false + + // we expect that the scheduler answers with a SimulationSuccessfulMessage + // to the sender of the run request (this test) + expectMsg(SimulationSuccessfulMessage) + } + + "finish simulation correctly when endTick < pauseScheduleAtTick" in { + val (simScheduler, resultEventListener) = setupScheduler() + + simScheduler ! InitSimMessage + resultEventListener.expectMsg(Initializing) + + val triggeredAgent1 = TestProbe() + + val initTrigger1 = createMockInitTrigger() + simScheduler ! ScheduleTriggerMessage( + initTrigger1, + triggeredAgent1.ref + ) + val receivedTrigger1Init = + triggeredAgent1.expectMsgType[TriggerWithIdMessage] + + triggeredAgent1.send( + simScheduler, + CompletionMessage( + receivedTrigger1Init.triggerId, + Some( + Seq( + ScheduleTriggerMessage( + ActivityStartTrigger(3600L), + triggeredAgent1.ref + ) + ) + ) + ) + ) + + resultEventListener.expectMsgType[InitComplete] + + triggeredAgent1.expectNoMessage() + + simScheduler ! StartScheduleMessage(Some(7200L)) + + resultEventListener.expectMsg(Simulating(0L, 3600L)) + resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 900L + resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 1800L + resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 2700L + + expectTriggerAndComplete[ActivityStartTrigger]( + simScheduler, + resultEventListener, + triggeredAgent1, + 3600L + ) + + resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 3600L + + // no ready msg here + + val doneMsg = resultEventListener.expectMsgType[Done] + doneMsg.tick shouldBe 3600L + doneMsg.noOfFailedPF shouldBe 0 + doneMsg.errorInSim shouldBe false + + // we expect that the scheduler answers with a SimulationSuccessfulMessage + // to the sender of the run request (this test) + expectMsg(SimulationSuccessfulMessage) + } + + "finish simulation correctly when endTick > first pauseScheduleAtTick" in { + val (simScheduler, resultEventListener) = setupScheduler( + timeConfig = defaultTimeConfig.copy(endDateTime = "2011-01-01 02:00:00") + ) + + simScheduler ! InitSimMessage + resultEventListener.expectMsg(Initializing) + + val triggeredAgent1 = TestProbe() + + val initTrigger1 = createMockInitTrigger() + simScheduler ! ScheduleTriggerMessage( + initTrigger1, + triggeredAgent1.ref + ) + val receivedTrigger1Init = + triggeredAgent1.expectMsgType[TriggerWithIdMessage] + + triggeredAgent1.send( + simScheduler, + CompletionMessage( + receivedTrigger1Init.triggerId, + Some( + Seq( + ScheduleTriggerMessage( + ActivityStartTrigger(3600L), + triggeredAgent1.ref + ) + ) + ) + ) + ) + + resultEventListener.expectMsgType[InitComplete] + + triggeredAgent1.expectNoMessage() + + simScheduler ! StartScheduleMessage(Some(3600L)) + + resultEventListener.expectMsg(Simulating(0L, 3600L)) + resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 900L + resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 1800L + resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 2700L + + expectTriggerAndComplete[ActivityStartTrigger]( + simScheduler, + resultEventListener, + triggeredAgent1, + 3600L, + Seq(7200L) + ) + + resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 3600L + resultEventListener.expectMsgType[Ready].tick shouldBe 3600L + + simScheduler ! StartScheduleMessage(Some(7200L)) + + resultEventListener.expectMsg(Simulating(3601, 7200)) + + resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 4500L + resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 5400L + resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 6300L + + expectTriggerAndComplete[ActivityStartTrigger]( + simScheduler, + resultEventListener, + triggeredAgent1, + 7200L + ) + + resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 7200L + resultEventListener.expectMsgType[Ready].tick shouldBe 7200L + + val doneMsg = resultEventListener.expectMsgType[Done] + doneMsg.tick shouldBe 7200L + doneMsg.noOfFailedPF shouldBe 0 + doneMsg.errorInSim shouldBe false + + // we expect that the scheduler answers with a SimulationSuccessfulMessage + // to the sender of the run request (this test) + expectMsg(SimulationSuccessfulMessage) + } + + "stop correctly for a pauseScheduleAtTick if endTick - pauseScheduleAtTick = 1" in { + val (simScheduler, resultEventListener) = setupScheduler() + + simScheduler ! InitSimMessage + resultEventListener.expectMsg(Initializing) + + val triggeredAgent1 = TestProbe() + + val initTrigger1 = createMockInitTrigger() + simScheduler ! ScheduleTriggerMessage( + initTrigger1, + triggeredAgent1.ref + ) + val receivedTrigger1Init = + triggeredAgent1.expectMsgType[TriggerWithIdMessage] + + triggeredAgent1.send( + simScheduler, + CompletionMessage( + receivedTrigger1Init.triggerId, + Some( + Seq( + ScheduleTriggerMessage( + ActivityStartTrigger(3600L), + triggeredAgent1.ref + ) + ) + ) + ) + ) + + resultEventListener.expectMsgType[InitComplete] + + triggeredAgent1.expectNoMessage() + + simScheduler ! StartScheduleMessage(Some(3599L)) + + resultEventListener.expectMsg(Simulating(0L, 3599L)) + resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 900L + resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 1800L + resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 2700L + resultEventListener.expectMsgType[Ready].tick shouldBe 3599L + + simScheduler ! StartScheduleMessage(Some(3600L)) + + resultEventListener.expectMsg(Simulating(3600L, 3600L)) + + expectTriggerAndComplete[ActivityStartTrigger]( + simScheduler, + resultEventListener, + triggeredAgent1, + 3600L + ) + + resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 3600L + resultEventListener.expectMsgType[Ready].tick shouldBe 3600L + val doneMsg = resultEventListener.expectMsgType[Done] + doneMsg.tick shouldBe 3600L + doneMsg.noOfFailedPF shouldBe 0 + doneMsg.errorInSim shouldBe false + + // we expect that the scheduler answers with a SimulationSuccessfulMessage + // to the sender of the run request (this test) + expectMsg(SimulationSuccessfulMessage) + } + + "should work correctly if timeBinSize == 1 and readyCheckWindow == 1" in { + val triggeredAgents = Range.inclusive(1, 5).map(i => TestProbe(s"SPA_$i")) + + val (simScheduler, resultEventListener) = setupScheduler( + timeConfig = defaultTimeConfig.copy( + startDateTime = "2011-01-01 00:00:00", + endDateTime = "2011-01-01 00:00:10", + schedulerReadyCheckWindow = Some(1) + ) + ) + + triggeredAgents.foreach(actor => + // send to init trigger to scheduler + simScheduler ! ScheduleTriggerMessage( + createMockInitTrigger(), + actor.ref + ) + ) + + // tell scheduler to init + simScheduler ! InitSimMessage + + resultEventListener.expectMsg(Initializing) + + triggeredAgents.foreach { actor => + expectTriggerAndComplete[InitializeTrigger]( + simScheduler, + resultEventListener, + actor, + -1L, + Seq(1L) + ) } - "notify with Error event on receipt of actor termination message" in { + // wait until init is done + resultEventListener.expectMsgType[InitComplete] + + // tell scheduler we want to run until tick 10 + simScheduler ! StartScheduleMessage(Some(10L)) - // create a test actor and kill the same to receive Terminated - // in simScheduler and Error event in the runtimeEventListener - val deathActor: ActorRef = system.actorOf(Props.empty) - var pass = false - scheduler.watch(deathActor) - deathActor ! PoisonPill + // expect a simulating event + resultEventListener.expectMsg(Simulating(0L, 10L)) - pass = eventQueue.take() match { - case _: Error => true - case _ => false + // interact with the scheduler for each tick + for (tick <- 1L to 9L) { + triggeredAgents.foreach { actor => + expectTriggerAndComplete[ActivityStartTrigger]( + simScheduler, + resultEventListener, + actor, + tick, + Seq(tick + 1) + ) + } + // we expect exactly one check window passed event as + // all activity start triggers for the last tick should be answered + resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe tick } - pass should be - true + + // answer with completion for the last tick, schedule another tick to test if the simulation event terminates as + // expected + triggeredAgents.foreach(actor => + expectTriggerAndComplete[ActivityStartTrigger]( + simScheduler, + resultEventListener, + actor, + 10L, + Seq(11L) + ) + ) + + resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 10L + resultEventListener.expectMsgType[Ready].tick shouldBe 10L + val doneMsg = resultEventListener.expectMsgType[Done] + doneMsg.tick shouldBe 10L + doneMsg.noOfFailedPF shouldBe 0 + doneMsg.errorInSim shouldBe false + + // finally expect a simulation successful message + expectMsg(SimulationSuccessfulMessage) } + + // TODO handle error cases + } + + private def expectTriggerAndComplete[T <: Trigger]( + simScheduler: TestActorRef[SimScheduler], + resultEventListener: TestProbe, + triggeredAgent: TestProbe, + expectedTick: Long, + newTicks: Seq[Long] = Seq.empty + )(implicit tag: ClassTag[T]): Unit = { + val receivedTrigger = + triggeredAgent.expectMsgType[TriggerWithIdMessage] + + receivedTrigger.trigger match { + case trigger: T => + trigger.tick shouldBe expectedTick + case unexpected => + fail(s"Received unexpected trigger $unexpected") + } + receivedTrigger.receiverActor shouldBe triggeredAgent.ref + + // when an agent is triggered, we can be sure that no result event + // is fired at least until the corresponding completion has been sent out + resultEventListener.expectNoMessage() + + val newTriggers = + Option.when(newTicks.nonEmpty)( + newTicks.map(tick => + ScheduleTriggerMessage( + ActivityStartTrigger(tick), + triggeredAgent.ref + ) + ) + ) + + triggeredAgent.send( + simScheduler, + CompletionMessage( + receivedTrigger.triggerId, + newTriggers + ) + ) + } + + private def createMockInitTrigger(): InitializeTrigger = { + val mockTrigger = mock[InitializeTrigger] + doReturn(SimonaConstants.INIT_SIM_TICK).when(mockTrigger).tick + mockTrigger } } From ae021d6250d5952e06933e62f8181566365d67d4 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Fri, 15 Jul 2022 14:57:42 +0200 Subject: [PATCH 2/5] Shortening test by providing rich class Enhancing test with exceptional cases --- .../simona/scheduler/SimSchedulerSpec.scala | 388 ++++++++++-------- 1 file changed, 225 insertions(+), 163 deletions(-) diff --git a/src/test/scala/edu/ie3/simona/scheduler/SimSchedulerSpec.scala b/src/test/scala/edu/ie3/simona/scheduler/SimSchedulerSpec.scala index 3a3be1e682..c571955f7d 100644 --- a/src/test/scala/edu/ie3/simona/scheduler/SimSchedulerSpec.scala +++ b/src/test/scala/edu/ie3/simona/scheduler/SimSchedulerSpec.scala @@ -17,13 +17,22 @@ import edu.ie3.simona.ontology.trigger.Trigger.{ ActivityStartTrigger, InitializeTrigger } -import edu.ie3.simona.test.common.{ConfigTestData, TestKitWithShutdown} +import edu.ie3.simona.test.common.{ + ConfigTestData, + TestKitWithShutdown, + UnitSpec +} import edu.ie3.simona.util.SimonaConstants import org.mockito.Mockito.doReturn import org.scalatest.PrivateMethodTester import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpecLike import org.scalatestplus.mockito.MockitoSugar.mock +import edu.ie3.simona.scheduler.SimSchedulerSpec.{ + DummySupervisor, + RichTriggeredAgent, + createMockInitTrigger +} import scala.reflect.ClassTag @@ -34,9 +43,9 @@ class SimSchedulerSpec ConfigFactory .parseString( """ - |akka.loggers =["edu.ie3.simona.test.common.SilentTestEventListener"] - |akka.loglevel="debug" - """.stripMargin + |akka.loggers =["edu.ie3.simona.test.common.SilentTestEventListener"] + |akka.loglevel="debug" + """.stripMargin ) ) ) @@ -60,13 +69,17 @@ class SimSchedulerSpec ): (TestActorRef[SimScheduler], TestProbe) = { val resultEventListener = TestProbe("ResultEventListener") - val simScheduler = TestActorRef( - new SimScheduler( + // make sure that the scheduler stops on exceptions + val supervisorRef = TestActorRef(new DummySupervisor()) + + val simScheduler = TestActorRef[SimScheduler]( + SimScheduler.props( timeConfig, Iterable(resultEventListener.ref), stopOnFailedPowerFlow = stopOnFailedPowerFlow, autoStart = autostart - ) + ), + supervisor = supervisorRef ) (simScheduler, resultEventListener) @@ -199,28 +212,10 @@ class SimSchedulerSpec resultEventListener.expectMsg(Initializing) - val receivedTrigger1Init = - triggeredAgent1.expectMsgType[TriggerWithIdMessage] - receivedTrigger1Init.trigger shouldBe initTrigger1 - receivedTrigger1Init.receiverActor shouldBe triggeredAgent1.ref - - triggeredAgent1.send( + triggeredAgent1.expectInitAndComplete( simScheduler, - CompletionMessage( - receivedTrigger1Init.triggerId, - Some( - Seq( - ScheduleTriggerMessage( - ActivityStartTrigger(0L), - triggeredAgent1.ref - ), - ScheduleTriggerMessage( - ActivityStartTrigger(900L), - triggeredAgent1.ref - ) - ) - ) - ) + resultEventListener, + Seq(0L, 900L) ) resultEventListener.expectMsgType[InitComplete] @@ -246,11 +241,9 @@ class SimSchedulerSpec triggeredAgent1.ref ) - expectTriggerAndComplete[InitializeTrigger]( + triggeredAgent1.expectInitAndComplete( simScheduler, resultEventListener, - triggeredAgent1, - SimonaConstants.INIT_SIM_TICK, Seq(0L) ) @@ -260,48 +253,43 @@ class SimSchedulerSpec resultEventListener.expectMsg(Simulating(0L, 3600L)) - expectTriggerAndComplete[ActivityStartTrigger]( + triggeredAgent1.expectAstAndComplete( simScheduler, resultEventListener, - triggeredAgent1, 0L, Seq(900L) ) - expectTriggerAndComplete[ActivityStartTrigger]( + triggeredAgent1.expectAstAndComplete( simScheduler, resultEventListener, - triggeredAgent1, 900L, Seq(1800L) ) resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 900L - expectTriggerAndComplete[ActivityStartTrigger]( + triggeredAgent1.expectAstAndComplete( simScheduler, resultEventListener, - triggeredAgent1, 1800L, Seq(2700L) ) resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 1800L - expectTriggerAndComplete[ActivityStartTrigger]( + triggeredAgent1.expectAstAndComplete( simScheduler, resultEventListener, - triggeredAgent1, 2700L, Seq(3600L) ) resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 2700L - expectTriggerAndComplete[ActivityStartTrigger]( + triggeredAgent1.expectAstAndComplete( simScheduler, resultEventListener, - triggeredAgent1, 3600L ) @@ -330,22 +318,10 @@ class SimSchedulerSpec initTrigger1, triggeredAgent1.ref ) - val receivedTrigger1Init = - triggeredAgent1.expectMsgType[TriggerWithIdMessage] - - triggeredAgent1.send( + triggeredAgent1.expectInitAndComplete( simScheduler, - CompletionMessage( - receivedTrigger1Init.triggerId, - Some( - Seq( - ScheduleTriggerMessage( - ActivityStartTrigger(3600L), - triggeredAgent1.ref - ) - ) - ) - ) + resultEventListener, + Seq(3600L) ) resultEventListener.expectMsgType[InitComplete] @@ -359,10 +335,9 @@ class SimSchedulerSpec resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 1800L resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 2700L - expectTriggerAndComplete[ActivityStartTrigger]( + triggeredAgent1.expectAstAndComplete( simScheduler, resultEventListener, - triggeredAgent1, 3600L ) @@ -391,22 +366,10 @@ class SimSchedulerSpec initTrigger1, triggeredAgent1.ref ) - val receivedTrigger1Init = - triggeredAgent1.expectMsgType[TriggerWithIdMessage] - - triggeredAgent1.send( + triggeredAgent1.expectInitAndComplete( simScheduler, - CompletionMessage( - receivedTrigger1Init.triggerId, - Some( - Seq( - ScheduleTriggerMessage( - ActivityStartTrigger(3600L), - triggeredAgent1.ref - ) - ) - ) - ) + resultEventListener, + Seq(3600L) ) resultEventListener.expectMsgType[InitComplete] @@ -420,10 +383,9 @@ class SimSchedulerSpec resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 1800L resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 2700L - expectTriggerAndComplete[ActivityStartTrigger]( + triggeredAgent1.expectAstAndComplete( simScheduler, resultEventListener, - triggeredAgent1, 3600L ) @@ -451,27 +413,14 @@ class SimSchedulerSpec val triggeredAgent1 = TestProbe() - val initTrigger1 = createMockInitTrigger() simScheduler ! ScheduleTriggerMessage( - initTrigger1, + createMockInitTrigger(), triggeredAgent1.ref ) - val receivedTrigger1Init = - triggeredAgent1.expectMsgType[TriggerWithIdMessage] - - triggeredAgent1.send( + triggeredAgent1.expectInitAndComplete( simScheduler, - CompletionMessage( - receivedTrigger1Init.triggerId, - Some( - Seq( - ScheduleTriggerMessage( - ActivityStartTrigger(3600L), - triggeredAgent1.ref - ) - ) - ) - ) + resultEventListener, + Seq(3600L) ) resultEventListener.expectMsgType[InitComplete] @@ -485,10 +434,9 @@ class SimSchedulerSpec resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 1800L resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 2700L - expectTriggerAndComplete[ActivityStartTrigger]( + triggeredAgent1.expectAstAndComplete( simScheduler, resultEventListener, - triggeredAgent1, 3600L, Seq(7200L) ) @@ -504,10 +452,9 @@ class SimSchedulerSpec resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 5400L resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 6300L - expectTriggerAndComplete[ActivityStartTrigger]( + triggeredAgent1.expectAstAndComplete( simScheduler, resultEventListener, - triggeredAgent1, 7200L ) @@ -532,27 +479,15 @@ class SimSchedulerSpec val triggeredAgent1 = TestProbe() - val initTrigger1 = createMockInitTrigger() simScheduler ! ScheduleTriggerMessage( - initTrigger1, + createMockInitTrigger(), triggeredAgent1.ref ) - val receivedTrigger1Init = - triggeredAgent1.expectMsgType[TriggerWithIdMessage] - triggeredAgent1.send( + triggeredAgent1.expectInitAndComplete( simScheduler, - CompletionMessage( - receivedTrigger1Init.triggerId, - Some( - Seq( - ScheduleTriggerMessage( - ActivityStartTrigger(3600L), - triggeredAgent1.ref - ) - ) - ) - ) + resultEventListener, + Seq(3600L) ) resultEventListener.expectMsgType[InitComplete] @@ -571,10 +506,9 @@ class SimSchedulerSpec resultEventListener.expectMsg(Simulating(3600L, 3600L)) - expectTriggerAndComplete[ActivityStartTrigger]( + triggeredAgent1.expectAstAndComplete( simScheduler, resultEventListener, - triggeredAgent1, 3600L ) @@ -614,12 +548,10 @@ class SimSchedulerSpec resultEventListener.expectMsg(Initializing) - triggeredAgents.foreach { actor => - expectTriggerAndComplete[InitializeTrigger]( + triggeredAgents.foreach { + _.expectInitAndComplete( simScheduler, resultEventListener, - actor, - -1L, Seq(1L) ) } @@ -635,11 +567,10 @@ class SimSchedulerSpec // interact with the scheduler for each tick for (tick <- 1L to 9L) { - triggeredAgents.foreach { actor => - expectTriggerAndComplete[ActivityStartTrigger]( + triggeredAgents.foreach { + _.expectAstAndComplete( simScheduler, resultEventListener, - actor, tick, Seq(tick + 1) ) @@ -651,11 +582,10 @@ class SimSchedulerSpec // answer with completion for the last tick, schedule another tick to test if the simulation event terminates as // expected - triggeredAgents.foreach(actor => - expectTriggerAndComplete[ActivityStartTrigger]( + triggeredAgents.foreach( + _.expectAstAndComplete( simScheduler, resultEventListener, - actor, 10L, Seq(11L) ) @@ -672,54 +602,186 @@ class SimSchedulerSpec expectMsg(SimulationSuccessfulMessage) } - // TODO handle error cases - } + /* exceptional cases */ - private def expectTriggerAndComplete[T <: Trigger]( - simScheduler: TestActorRef[SimScheduler], - resultEventListener: TestProbe, - triggeredAgent: TestProbe, - expectedTick: Long, - newTicks: Seq[Long] = Seq.empty - )(implicit tag: ClassTag[T]): Unit = { - val receivedTrigger = - triggeredAgent.expectMsgType[TriggerWithIdMessage] - - receivedTrigger.trigger match { - case trigger: T => - trigger.tick shouldBe expectedTick - case unexpected => - fail(s"Received unexpected trigger $unexpected") - } - receivedTrigger.receiverActor shouldBe triggeredAgent.ref - - // when an agent is triggered, we can be sure that no result event - // is fired at least until the corresponding completion has been sent out - resultEventListener.expectNoMessage() - - val newTriggers = - Option.when(newTicks.nonEmpty)( - newTicks.map(tick => - ScheduleTriggerMessage( - ActivityStartTrigger(tick), - triggeredAgent.ref - ) - ) + "fail validation of StartScheduleMessage if pauseScheduleAtTick < nowInTicks" in { + val (simScheduler, resultEventListener) = setupScheduler() + + // observe if scheduler dies + val deathWatch = TestProbe() + deathWatch.watch(simScheduler) + + simScheduler ! InitSimMessage + resultEventListener.expectMsg(Initializing) + + val triggeredAgent1 = TestProbe() + + simScheduler ! ScheduleTriggerMessage( + createMockInitTrigger(), + triggeredAgent1.ref ) + triggeredAgent1.expectInitAndComplete( + simScheduler, + resultEventListener, + Seq(900L) + ) + + resultEventListener.expectMsgType[InitComplete] + + simScheduler ! StartScheduleMessage(Some(1800L)) + resultEventListener.expectMsg(Simulating(0L, 1800L)) + + // should still live + deathWatch.expectNoMessage() + + // send faulty StartScheduleMessage + simScheduler ! StartScheduleMessage(Some(0L)) + + // exception should be thrown, + deathWatch.expectTerminated(simScheduler) + + resultEventListener.expectNoMessage() + } + + "do nothing when receiving StartScheduleTrigger during initialization" in { + val (simScheduler, resultEventListener) = setupScheduler() + + // observe if scheduler dies + val deathWatch = TestProbe() + deathWatch.watch(simScheduler) - triggeredAgent.send( - simScheduler, - CompletionMessage( - receivedTrigger.triggerId, - newTriggers + simScheduler ! InitSimMessage + resultEventListener.expectMsg(Initializing) + + simScheduler ! StartScheduleMessage(None) + + resultEventListener.expectNoMessage() + // scheduler should not die, but just continue + deathWatch.expectNoMessage() + } + + "do nothing when receiving StartScheduleTrigger and already started" in { + val (simScheduler, resultEventListener) = setupScheduler() + + // observe if scheduler dies + val deathWatch = TestProbe() + deathWatch.watch(simScheduler) + + simScheduler ! InitSimMessage + resultEventListener.expectMsg(Initializing) + + val triggeredAgent1 = TestProbe() + + simScheduler ! ScheduleTriggerMessage( + createMockInitTrigger(), + triggeredAgent1.ref ) - ) + triggeredAgent1.expectInitAndComplete( + simScheduler, + resultEventListener, + Seq(900L) + ) + + resultEventListener.expectMsgType[InitComplete] + + simScheduler ! StartScheduleMessage(Some(1800L)) + resultEventListener.expectMsg(Simulating(0L, 1800L)) + + simScheduler ! StartScheduleMessage(None) + + resultEventListener.expectNoMessage() + // scheduler should not die, but just continue + deathWatch.expectNoMessage() + } + + // TODO handle more exceptional cases } - private def createMockInitTrigger(): InitializeTrigger = { +} + +object SimSchedulerSpec extends UnitSpec { + + def createMockInitTrigger(): InitializeTrigger = { val mockTrigger = mock[InitializeTrigger] doReturn(SimonaConstants.INIT_SIM_TICK).when(mockTrigger).tick mockTrigger } + class DummySupervisor extends Actor { + override def supervisorStrategy: SupervisorStrategy = OneForOneStrategy() { + case _: RuntimeException => SupervisorStrategy.stop + } + + override def receive: Receive = { case _ => } + } + + implicit class RichTriggeredAgent(private val triggeredAgent: TestProbe) { + + def expectInitAndComplete( + simScheduler: TestActorRef[SimScheduler], + resultEventListener: TestProbe, + newTicks: Seq[Long] = Seq.empty + ): Unit = + expectTriggerAndComplete[InitializeTrigger]( + simScheduler, + resultEventListener, + SimonaConstants.INIT_SIM_TICK, + newTicks + ) + + def expectAstAndComplete( + simScheduler: TestActorRef[SimScheduler], + resultEventListener: TestProbe, + expectedTick: Long, + newTicks: Seq[Long] = Seq.empty + ): Unit = + expectTriggerAndComplete[ActivityStartTrigger]( + simScheduler, + resultEventListener, + expectedTick, + newTicks + ) + + private def expectTriggerAndComplete[T <: Trigger]( + simScheduler: TestActorRef[SimScheduler], + resultEventListener: TestProbe, + expectedTick: Long, + newTicks: Seq[Long] = Seq.empty + )(implicit tag: ClassTag[T]): Unit = { + val receivedTrigger = + triggeredAgent.expectMsgType[TriggerWithIdMessage] + + receivedTrigger.trigger match { + case trigger: T => + trigger.tick shouldBe expectedTick + case unexpected => + fail(s"Received unexpected trigger $unexpected") + } + receivedTrigger.receiverActor shouldBe triggeredAgent.ref + + // when an agent is triggered, we can be sure that no result event + // is fired at least until the corresponding completion has been sent out + resultEventListener.expectNoMessage() + + val newTriggers = + Option.when(newTicks.nonEmpty)( + newTicks.map(tick => + ScheduleTriggerMessage( + ActivityStartTrigger(tick), + triggeredAgent.ref + ) + ) + ) + + triggeredAgent.send( + simScheduler, + CompletionMessage( + receivedTrigger.triggerId, + newTriggers + ) + ) + } + + } + } From 003e62df925cce3bb7229bfb664e9b72d7782e47 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Sun, 17 Jul 2022 19:49:16 +0200 Subject: [PATCH 3/5] Fixing counting failed power flows on error in SimScheduler --- .../edu/ie3/simona/scheduler/SimScheduler.scala | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main/scala/edu/ie3/simona/scheduler/SimScheduler.scala b/src/main/scala/edu/ie3/simona/scheduler/SimScheduler.scala index 8db29276e4..0b31c49f44 100644 --- a/src/main/scala/edu/ie3/simona/scheduler/SimScheduler.scala +++ b/src/main/scala/edu/ie3/simona/scheduler/SimScheduler.scala @@ -160,15 +160,15 @@ class SimScheduler( case PowerFlowFailedMessage => /* config dependant we either go into onErrorReceive and terminate when we have * all completion messages received or we just go on with the normal schedule*/ - val updateStateData = stateData.copy( + val updatedStateData = stateData.copy( runtime = stateData.runtime .copy(noOfFailedPF = stateData.runtime.noOfFailedPF + 1) ) if (stopOnFailedPowerFlow) { /* go to onError receive state */ - context become schedulerReceiveOnError(updateStateData) + context become schedulerReceiveOnError(updatedStateData) } else { - context become schedulerReceive(updateStateData) + context become schedulerReceive(updatedStateData) } /* received whenever a watched agent dies */ @@ -199,6 +199,15 @@ class SimScheduler( context become schedulerReceiveOnError(updatedStateData) + case PowerFlowFailedMessage => + /* config dependant we either go into onErrorReceive and terminate when we have + * all completion messages received or we just go on with the normal schedule*/ + val updatedStateData = stateData.copy( + runtime = stateData.runtime + .copy(noOfFailedPF = stateData.runtime.noOfFailedPF + 1) + ) + context become schedulerReceiveOnError(updatedStateData) + /* all unhandled messages */ case unhandledMessage => log.error( From a656db4d8db99c9e07b7f369771110222d619e3c Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Sun, 17 Jul 2022 19:50:03 +0200 Subject: [PATCH 4/5] Providing missing tests --- .../simona/scheduler/SimSchedulerSpec.scala | 206 ++++++++++++++++-- 1 file changed, 182 insertions(+), 24 deletions(-) diff --git a/src/test/scala/edu/ie3/simona/scheduler/SimSchedulerSpec.scala b/src/test/scala/edu/ie3/simona/scheduler/SimSchedulerSpec.scala index c571955f7d..23df7355f7 100644 --- a/src/test/scala/edu/ie3/simona/scheduler/SimSchedulerSpec.scala +++ b/src/test/scala/edu/ie3/simona/scheduler/SimSchedulerSpec.scala @@ -17,22 +17,14 @@ import edu.ie3.simona.ontology.trigger.Trigger.{ ActivityStartTrigger, InitializeTrigger } -import edu.ie3.simona.test.common.{ - ConfigTestData, - TestKitWithShutdown, - UnitSpec +import edu.ie3.simona.scheduler.SimSchedulerSpec.{ + DummySupervisor, + RichTriggeredAgent } +import edu.ie3.simona.test.common.{TestKitWithShutdown, UnitSpec} import edu.ie3.simona.util.SimonaConstants import org.mockito.Mockito.doReturn -import org.scalatest.PrivateMethodTester -import org.scalatest.matchers.should.Matchers -import org.scalatest.wordspec.AnyWordSpecLike import org.scalatestplus.mockito.MockitoSugar.mock -import edu.ie3.simona.scheduler.SimSchedulerSpec.{ - DummySupervisor, - RichTriggeredAgent, - createMockInitTrigger -} import scala.reflect.ClassTag @@ -43,17 +35,13 @@ class SimSchedulerSpec ConfigFactory .parseString( """ - |akka.loggers =["edu.ie3.simona.test.common.SilentTestEventListener"] + |akka.loggers=["edu.ie3.simona.test.common.SilentTestEventListener"] |akka.loglevel="debug" """.stripMargin ) ) ) - with ImplicitSender - with AnyWordSpecLike - with Matchers - with PrivateMethodTester - with ConfigTestData { + with ImplicitSender { private val defaultTimeConfig = SimonaConfig.Simona.Time( startDateTime = "2011-01-01 00:00:00", @@ -260,6 +248,9 @@ class SimSchedulerSpec Seq(900L) ) + // no CheckWindowPassed at tick 0 + resultEventListener.expectNoMessage() + triggeredAgent1.expectAstAndComplete( simScheduler, resultEventListener, @@ -471,7 +462,7 @@ class SimSchedulerSpec expectMsg(SimulationSuccessfulMessage) } - "stop correctly for a pauseScheduleAtTick if endTick - pauseScheduleAtTick = 1" in { + "pause and finish simulation correctly for a pauseScheduleAtTick if endTick - pauseScheduleAtTick = 1" in { val (simScheduler, resultEventListener) = setupScheduler() simScheduler ! InitSimMessage @@ -524,7 +515,7 @@ class SimSchedulerSpec expectMsg(SimulationSuccessfulMessage) } - "should work correctly if timeBinSize == 1 and readyCheckWindow == 1" in { + "work correctly if timeBinSize == 1 and readyCheckWindow == 1" in { val triggeredAgents = Range.inclusive(1, 5).map(i => TestProbe(s"SPA_$i")) val (simScheduler, resultEventListener) = setupScheduler( @@ -694,12 +685,155 @@ class SimSchedulerSpec deathWatch.expectNoMessage() } - // TODO handle more exceptional cases - } + "handle PowerFlow failures when stopOnFailedPowerFlow = false" in { + val (simScheduler, resultEventListener) = setupScheduler() -} + simScheduler ! InitSimMessage + resultEventListener.expectMsg(Initializing) -object SimSchedulerSpec extends UnitSpec { + val triggeredAgent1 = TestProbe() + + simScheduler ! ScheduleTriggerMessage( + createMockInitTrigger(), + triggeredAgent1.ref + ) + triggeredAgent1.expectInitAndComplete( + simScheduler, + resultEventListener, + Seq(0L) + ) + + resultEventListener.expectMsgType[InitComplete] + + simScheduler ! StartScheduleMessage(None) + resultEventListener.expectMsg(Simulating(0L, 3600L)) + + val receivedTrigger1 = triggeredAgent1.expectMsgType[TriggerWithIdMessage] + + triggeredAgent1.send(simScheduler, PowerFlowFailedMessage) + triggeredAgent1.send(simScheduler, PowerFlowFailedMessage) + triggeredAgent1.send(simScheduler, PowerFlowFailedMessage) + + resultEventListener.expectNoMessage() + + simScheduler ! CompletionMessage( + receivedTrigger1.triggerId, + None + ) + + resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 900L + resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 1800L + resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 2700L + resultEventListener.expectMsgType[CheckWindowPassed].tick shouldBe 3600L + + val doneMsg = resultEventListener.expectMsgType[Done] + doneMsg.tick shouldBe 3600L + doneMsg.noOfFailedPF shouldBe 3 + doneMsg.errorInSim shouldBe false + + expectMsg(SimulationSuccessfulMessage) + } + + "handle PowerFlow failures when stopOnFailedPowerFlow = true" in { + val (simScheduler, resultEventListener) = + setupScheduler(stopOnFailedPowerFlow = true) + + simScheduler ! InitSimMessage + resultEventListener.expectMsg(Initializing) + + val triggeredAgent1 = TestProbe() + + simScheduler ! ScheduleTriggerMessage( + createMockInitTrigger(), + triggeredAgent1.ref + ) + triggeredAgent1.expectInitAndComplete( + simScheduler, + resultEventListener, + Seq(0L) + ) + + resultEventListener.expectMsgType[InitComplete] + + simScheduler ! StartScheduleMessage(None) + resultEventListener.expectMsg(Simulating(0L, 3600L)) + + val receivedTrigger1 = triggeredAgent1.expectMsgType[TriggerWithIdMessage] + + triggeredAgent1.send(simScheduler, PowerFlowFailedMessage) + triggeredAgent1.send(simScheduler, PowerFlowFailedMessage) + + resultEventListener.expectNoMessage() + + simScheduler ! CompletionMessage( + receivedTrigger1.triggerId, + None + ) + + val doneMsg = resultEventListener.expectMsgType[Done] + doneMsg.tick shouldBe 3600L + doneMsg.noOfFailedPF shouldBe 2 + doneMsg.errorInSim shouldBe true + + expectMsg(SimulationFailureMessage) + } + + "terminate on child actor termination when initializing" in { + val (simScheduler, resultEventListener) = setupScheduler() + + simScheduler ! InitSimMessage + resultEventListener.expectMsg(Initializing) + + checkTerminationHandling(simScheduler, resultEventListener) + } + + "terminate on child actor termination when paused" in { + val (simScheduler, resultEventListener) = setupScheduler() + + simScheduler ! InitSimMessage + resultEventListener.expectMsg(Initializing) + + val triggeredAgent1 = TestProbe() + + simScheduler ! ScheduleTriggerMessage( + createMockInitTrigger(), + triggeredAgent1.ref + ) + triggeredAgent1.expectInitAndComplete( + simScheduler, + resultEventListener, + Seq(0L) + ) + + resultEventListener.expectMsgType[InitComplete] + + checkTerminationHandling(simScheduler, resultEventListener) + } + + "terminate on child actor termination when running" in { + val (simScheduler, resultEventListener) = setupScheduler(autostart = true) + + simScheduler ! InitSimMessage + resultEventListener.expectMsg(Initializing) + + val triggeredAgent1 = TestProbe() + + simScheduler ! ScheduleTriggerMessage( + createMockInitTrigger(), + triggeredAgent1.ref + ) + triggeredAgent1.expectInitAndComplete( + simScheduler, + resultEventListener, + Seq(0L) + ) + + resultEventListener.expectMsgType[InitComplete] + resultEventListener.expectMsg(Simulating(0L, 3600L)) + + checkTerminationHandling(simScheduler, resultEventListener) + } + } def createMockInitTrigger(): InitializeTrigger = { val mockTrigger = mock[InitializeTrigger] @@ -707,6 +841,30 @@ object SimSchedulerSpec extends UnitSpec { mockTrigger } + private def checkTerminationHandling( + simScheduler: TestActorRef[SimScheduler], + resultEventListener: TestProbe + ): Unit = { + // observe scheduler for stopping + val deathWatch = TestProbe() + deathWatch.watch(simScheduler) + + // give scheduler a child that dies + val dyingActor = TestProbe() + simScheduler.watch(dyingActor.ref) + + dyingActor.ref ! PoisonPill + + resultEventListener.expectMsgType[Error] + expectMsg(SimulationFailureMessage) + + // scheduler should have died + deathWatch.expectTerminated(simScheduler) + } +} + +object SimSchedulerSpec extends UnitSpec { + class DummySupervisor extends Actor { override def supervisorStrategy: SupervisorStrategy = OneForOneStrategy() { case _: RuntimeException => SupervisorStrategy.stop From d88e61f0f09b5fe3be6ea4a21d23d24eb1dd7185 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Sun, 17 Jul 2022 20:23:57 +0200 Subject: [PATCH 5/5] Adding to changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf10b92971..1e3d5fcc2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Adapt to new simonaAPI snapshot [#95](https://github.com/ie3-institute/simona/issues/95) - Update Sphinx to 4.5.0 as well as extensions [#214](https://github.com/ie3-institute/simona/issues/214) - Improved code quality in and around DBFS algorithm [#265](https://github.com/ie3-institute/simona/issues/265) +- Consolidated and enhanced SimScheduler tests [#285](https://github.com/ie3-institute/simona/issues/285) ### Fixed - Location of `vn_simona` test grid (was partially in Berlin and Dortmund) [#72](https://github.com/ie3-institute/simona/issues/72)