From 2bc0e9834a81738e3af24886d8daf7af490bcc59 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Tue, 30 Oct 2018 23:53:58 +0100 Subject: refactor: Add support for deferred and special behavior This change adds support for deferred and special behavior in the Behavior API. Additionally, the new Behavior DSL is implemented using the new primitives to make the tests cleaner. --- .../com/atlarge/odcsim/ActorSystemFactoryTest.kt | 10 +- .../kotlin/com/atlarge/odcsim/ActorSystemTest.kt | 226 ++++++++++++--------- 2 files changed, 133 insertions(+), 103 deletions(-) (limited to 'odcsim-core/src/test') diff --git a/odcsim-core/src/test/kotlin/com/atlarge/odcsim/ActorSystemFactoryTest.kt b/odcsim-core/src/test/kotlin/com/atlarge/odcsim/ActorSystemFactoryTest.kt index 266f9fba..3830f09e 100644 --- a/odcsim-core/src/test/kotlin/com/atlarge/odcsim/ActorSystemFactoryTest.kt +++ b/odcsim-core/src/test/kotlin/com/atlarge/odcsim/ActorSystemFactoryTest.kt @@ -24,6 +24,8 @@ package com.atlarge.odcsim +import com.atlarge.odcsim.dsl.empty +import com.atlarge.odcsim.dsl.setup import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows @@ -44,7 +46,7 @@ abstract class ActorSystemFactoryTest { fun `should create a system with correct name`() { val factory = createFactory() val name = "test" - val system = factory(object : Behavior {}, name) + val system = factory(Behavior.empty(), name) assertEquals(name, system.name) } @@ -55,11 +57,7 @@ abstract class ActorSystemFactoryTest { @Test fun `should create a system with correct root behavior`() { val factory = createFactory() - val system = factory(object : Behavior { - override fun receiveSignal(ctx: ActorContext, signal: Signal): Behavior { - throw UnsupportedOperationException() - } - }, "test") + val system = factory(Behavior.setup { throw UnsupportedOperationException() }, "test") assertThrows { system.run(until = 10.0) } } diff --git a/odcsim-core/src/test/kotlin/com/atlarge/odcsim/ActorSystemTest.kt b/odcsim-core/src/test/kotlin/com/atlarge/odcsim/ActorSystemTest.kt index 71bc645a..879fe35a 100644 --- a/odcsim-core/src/test/kotlin/com/atlarge/odcsim/ActorSystemTest.kt +++ b/odcsim-core/src/test/kotlin/com/atlarge/odcsim/ActorSystemTest.kt @@ -24,6 +24,11 @@ package com.atlarge.odcsim +import com.atlarge.odcsim.dsl.empty +import com.atlarge.odcsim.dsl.ignore +import com.atlarge.odcsim.dsl.receive +import com.atlarge.odcsim.dsl.receiveMessage +import com.atlarge.odcsim.dsl.setup import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertTrue @@ -49,7 +54,7 @@ abstract class ActorSystemTest { @Test fun `should have a name`() { val name = "test" - val system = factory(object : Behavior {}, name) + val system = factory(Behavior.empty(), name) assertEquals(name, system.name) } @@ -59,7 +64,7 @@ abstract class ActorSystemTest { */ @Test fun `should have a path`() { - val system = factory(object : Behavior {}, "test") + val system = factory(Behavior.empty(), "test") assertTrue(system.path is ActorPath.Root) } @@ -69,7 +74,7 @@ abstract class ActorSystemTest { */ @Test fun `should start at t=0`() { - val system = factory(object : Behavior {}, name = "test") + val system = factory(Behavior.empty(), name = "test") assertTrue(Math.abs(system.time) < DELTA) } @@ -79,7 +84,7 @@ abstract class ActorSystemTest { */ @Test fun `should not accept negative instants for running`() { - val system = factory(object : Behavior {}, name = "test") + val system = factory(Behavior.empty(), name = "test") assertThrows { system.run(-10.0) } } @@ -90,7 +95,7 @@ abstract class ActorSystemTest { @Test fun `should not jump backward in time`() { val until = 10.0 - val system = factory(object : Behavior {}, name = "test") + val system = factory(Behavior.empty(), name = "test") system.run(until = until) system.run(until = until - 0.5) @@ -103,7 +108,7 @@ abstract class ActorSystemTest { @Test fun `should jump forward in time`() { val until = 10.0 - val system = factory(object : Behavior {}, name = "test") + val system = factory(Behavior.empty(), name = "test") system.run(until = until) assertTrue(Math.abs(system.time - until) < DELTA) @@ -114,15 +119,12 @@ abstract class ActorSystemTest { */ @Test fun `should order messages at the instant by insertion time`() { - val behavior = object : Behavior { - override fun receive(ctx: ActorContext, msg: Int): Behavior { - assertEquals(1, msg) - return object : Behavior { - override fun receive(ctx: ActorContext, msg: Int): Behavior { - assertEquals(2, msg) - return this - } - } + val behavior = Behavior.receiveMessage { msg -> + assertEquals(1, msg) + + Behavior.receiveMessage { + assertEquals(2, it) + Behavior.ignore() } } val system = factory(behavior, name = "test") @@ -131,6 +133,14 @@ abstract class ActorSystemTest { system.run(until = 10.0) } + /** + * Test whether an [ActorSystem] will not initialize the root actor if the system has not been run yet. + */ + @Test + fun `should not initialize root actor if not run`() { + factory(Behavior.setup { TODO() }, name = "test") + } + @Nested @DisplayName("ActorRef") inner class ActorRefTest { @@ -139,7 +149,7 @@ abstract class ActorSystemTest { */ @Test fun `should disallow messages in the past`() { - val system = factory(object : Behavior {}, name = "test") + val system = factory(Behavior.empty(), name = "test") assertThrows { system.send(Unit, after = -1.0) } } } @@ -153,11 +163,9 @@ abstract class ActorSystemTest { */ @Test fun `should pre-start at t=0 if root`() { - val behavior = object : Behavior { - override fun receiveSignal(ctx: ActorContext, signal: Signal): Behavior { - assertTrue(Math.abs(ctx.time) < DELTA) - return this - } + val behavior = Behavior.setup { ctx -> + assertTrue(Math.abs(ctx.time) < DELTA) + Behavior.ignore() } val system = factory(behavior, "test") @@ -169,21 +177,12 @@ abstract class ActorSystemTest { */ @Test fun `should allow spawning of child actors`() { - val behavior = object : Behavior { - override fun receiveSignal(ctx: ActorContext, signal: Signal): Behavior { - throw UnsupportedOperationException("b") - } - } + val behavior = Behavior.setup { throw UnsupportedOperationException("b") } - val system = factory(object : Behavior { - override fun receiveSignal(ctx: ActorContext, signal: Signal): Behavior { - if (signal is PreStart) { - val ref = ctx.spawn(behavior, "child") - assertEquals("child", ref.path.name) - } - - return this - } + val system = factory(Behavior.setup { ctx -> + val ref = ctx.spawn(behavior, "child") + assertEquals("child", ref.path.name) + Behavior.ignore() }, name = "test") assertThrows { system.run(until = 10.0) } @@ -194,20 +193,12 @@ abstract class ActorSystemTest { */ @Test fun `should allow stopping of child actors`() { - val system = factory(object : Behavior { - override fun receiveSignal(ctx: ActorContext, signal: Signal): Behavior { - if (signal is PreStart) { - val ref = ctx.spawn(object : Behavior { - override fun receive(ctx: ActorContext, msg: Unit): Behavior { - throw UnsupportedOperationException() - } - }, "child") - assertTrue(ctx.stop(ref)) - assertEquals("child", ref.path.name) - } - - return this - } + val system = factory(Behavior.setup { ctx -> + val ref = ctx.spawn(Behavior.receiveMessage { throw UnsupportedOperationException() }, "child") + assertTrue(ctx.stop(ref)) + assertEquals("child", ref.path.name) + + Behavior.ignore() }, name = "test") system.run(until = 10.0) @@ -218,17 +209,15 @@ abstract class ActorSystemTest { */ @Test fun `should only be able to terminate child actors`() { - val system = factory(object : Behavior { - override fun receiveSignal(ctx: ActorContext, signal: Signal): Behavior { - val child1 = ctx.spawn(object : Behavior {}, "child-1") - ctx.spawn(object : Behavior { - override fun receiveSignal(ctx: ActorContext, signal: Signal): Behavior { - assertFalse(ctx.stop(child1)) - return this - } - }, "child-2") - return this - } + val system = factory(Behavior.setup { ctx -> + val child1 = ctx.spawn(Behavior.ignore(), "child-1") + + ctx.spawn(Behavior.setup { + assertFalse(it.stop(child1)) + Behavior.ignore() + }, "child-2") + + Behavior.ignore() }, name = "test") system.run() } @@ -238,13 +227,11 @@ abstract class ActorSystemTest { */ @Test fun `should not be able to stop an already terminated child`() { - val system = factory(object : Behavior { - override fun receiveSignal(ctx: ActorContext, signal: Signal): Behavior { - val child = ctx.spawn(object : Behavior {}, "child") - ctx.stop(child) - assertFalse(ctx.stop(child)) - return this - } + val system = factory(Behavior.setup { ctx -> + val child = ctx.spawn(Behavior.ignore(), "child") + ctx.stop(child) + assertFalse(ctx.stop(child)) + Behavior.ignore() }, name = "test") system.run() } @@ -254,53 +241,98 @@ abstract class ActorSystemTest { */ @Test fun `should terminate children of child when terminating it`() { - val system = factory(object : Behavior> { - lateinit var child: ActorRef + val system = factory(Behavior.setup> { ctx1 -> + val root = ctx1.self + val child = ctx1.spawn(Behavior.setup { + val child = ctx1.spawn(Behavior.receiveMessage { + throw IllegalStateException("DELIBERATE") + }, "child") + root.send(child) + Behavior.ignore() + }, "child") - override fun receive(ctx: ActorContext>, msg: ActorRef): Behavior> { - assertTrue(ctx.stop(child)) + + Behavior.receive { ctx2, msg -> + assertTrue(ctx2.stop(child)) msg.send(Unit) // This actor should be stopped now and not receive the message anymore - return this + Behavior.stopped() } - override fun receiveSignal(ctx: ActorContext>, signal: Signal): Behavior> { - val root = ctx.self - child = ctx.spawn(object : Behavior { - override fun receiveSignal(ctx: ActorContext, signal: Signal): Behavior { - if (signal is PreStart) { - val child = ctx.spawn(object : Behavior { - override fun receive(ctx: ActorContext, msg: Unit): Behavior { - throw IllegalStateException() - } - }, "child") - root.send(child) - } - return this - } - }, "child") - return this - } }, name = "test") + system.run() } /** - * Test whether the reference to the actor itself is valid. + * Test whether [Behavior.Companion.same] works correctly. */ @Test - fun `should have reference to itself`() { - val behavior = object : Behavior { - override fun receive(ctx: ActorContext, msg: Unit): Behavior { - throw UnsupportedOperationException() - } - override fun receiveSignal(ctx: ActorContext, signal: Signal): Behavior { + fun `should keep same behavior on same`() { + var counter = 0 + + val behavior = Behavior.setup { ctx -> + counter++ ctx.self.send(Unit) - return this + + Behavior.receiveMessage { + counter++ + Behavior.same() } } + val system = factory(behavior, "test") + system.run() + assertEquals(2, counter) + } + + /** + * Test whether the reference to the actor itself is valid. + */ + @Test + fun `should have reference to itself`() { + val behavior: Behavior = Behavior.setup { ctx -> + ctx.self.send(Unit) + Behavior.receiveMessage { throw UnsupportedOperationException() } + } + val system = factory(behavior, "test") assertThrows { system.run() } } + + /** + * Test whether we cannot start an actor with the [Behavior.Companion.same] behavior. + */ + @Test + fun `should not start with same behavior`() { + val system = factory(Behavior.same(), "test") + assertThrows { system.run() } + } + + /** + * Test whether we cannot start an actor with the [Behavior.Companion.unhandled] behavior. + */ + @Test + fun `should not start with unhandled behavior`() { + val system = factory(Behavior.unhandled(), "test") + assertThrows { system.run() } + } + + /** + * Test whether we cannot start an actor with deferred unhandled behavior. + */ + @Test + fun `should not start with deferred unhandled behavior`() { + val system = factory(Behavior.setup { Behavior.unhandled() }, "test") + assertThrows { system.run() } + } + + /** + * Test whether we can start an actor with the [Behavior.Companion.stopped] behavior. + */ + @Test + fun `should start with stopped behavior`() { + val system = factory(Behavior.stopped(), "test") + system.run() + } } } -- cgit v1.2.3