diff options
Diffstat (limited to 'odcsim-core')
4 files changed, 328 insertions, 6 deletions
diff --git a/odcsim-core/src/main/kotlin/com/atlarge/odcsim/ActorContext.kt b/odcsim-core/src/main/kotlin/com/atlarge/odcsim/ActorContext.kt index 5a16c4d1..4dea9897 100644 --- a/odcsim-core/src/main/kotlin/com/atlarge/odcsim/ActorContext.kt +++ b/odcsim-core/src/main/kotlin/com/atlarge/odcsim/ActorContext.kt @@ -43,11 +43,11 @@ interface ActorContext<in T : Any> { /** * Spawn a child actor from the given [Behavior] and with the specified name. * - * @param name The name of the child actor to spawn. * @param behavior The behavior of the child actor to spawn. + * @param name The name of the child actor to spawn. * @return A reference to the child that has/will be spawned. */ - fun <U : Any> spawn(name: String, behavior: Behavior<U>): ActorRef<U> + fun <U : Any> spawn(behavior: Behavior<U>, name: String): ActorRef<U> /** * Request the specified child actor to be stopped in asynchronous fashion. @@ -55,5 +55,5 @@ interface ActorContext<in T : Any> { * @param child The reference to the child actor to stop. * @return `true` if the ref points to a child actor, otherwise `false`. */ - fun <U : Any> stop(child: ActorRef<U>): Boolean + fun stop(child: ActorRef<*>): Boolean } diff --git a/odcsim-core/src/test/kotlin/com/atlarge/odcsim/ActorPathTest.kt b/odcsim-core/src/test/kotlin/com/atlarge/odcsim/ActorPathTest.kt new file mode 100644 index 00000000..023d3efd --- /dev/null +++ b/odcsim-core/src/test/kotlin/com/atlarge/odcsim/ActorPathTest.kt @@ -0,0 +1,86 @@ +/* + * MIT License + * + * Copyright (c) 2018 atlarge-research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.atlarge.odcsim + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +/** + * A test suite for the [ActorPath] class. + */ +@DisplayName("ActorPath") +class ActorPathTest { + /** + * Test whether an [ActorPath.Root] may only start with a slash. + */ + @Test + fun `root node may only start with a slash`() { + ActorPath.Root() // Assert slash at start + assertThrows<IllegalArgumentException> { ActorPath.Root("abc/") } + } + + /** + * Test whether an [ActorPath.Child] disallows names with a slash. + */ + @Test + fun `child node should not allow name with a slash`() { + assertThrows<IllegalArgumentException> { ActorPath.Child(ActorPath.Root(), "/") } + } + + /** + * Test whether a root node can have a custom name. + */ + @Test + fun `root node can have a custom name`() { + val name = "user" + assertEquals(name, ActorPath.Root(name).name) + } + + /** + * Test whether a child node can be created on a root. + */ + @Test + fun `child node can be created on a root`() { + val root = ActorPath.Root(name = "/user") + val child = root.child("child") + + assertEquals(root, child.parent) + assertEquals("child", child.name) + } + + /** + * Test whether a child node can be created on a child. + */ + @Test + fun `child node can be created on a child`() { + val root = ActorPath.Root(name = "/user").child("child") + val child = root.child("child") + + assertEquals(root, child.parent) + assertEquals("child", child.name) + } +} 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 a374548c..266f9fba 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,10 @@ package com.atlarge.odcsim +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + /** * A conformance test suite for implementors of the [ActorSystemFactory] interface. */ @@ -32,4 +36,31 @@ abstract class ActorSystemFactoryTest { * Create an [ActorSystemFactory] instance to test. */ abstract fun createFactory(): ActorSystemFactory + + /** + * Test whether the factory will create an [ActorSystem] with correct name. + */ + @Test + fun `should create a system with correct name`() { + val factory = createFactory() + val name = "test" + val system = factory(object : Behavior<Unit> {}, name) + + assertEquals(name, system.name) + } + + /** + * Test whether the factory will create an [ActorSystem] with valid root behavior. + */ + @Test + fun `should create a system with correct root behavior`() { + val factory = createFactory() + val system = factory(object : Behavior<Unit> { + override fun receiveSignal(ctx: ActorContext<Unit>, signal: Signal): Behavior<Unit> { + throw UnsupportedOperationException() + } + }, "test") + + assertThrows<UnsupportedOperationException> { 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 62df356b..71bc645a 100644 --- a/odcsim-core/src/test/kotlin/com/atlarge/odcsim/ActorSystemTest.kt +++ b/odcsim-core/src/test/kotlin/com/atlarge/odcsim/ActorSystemTest.kt @@ -25,11 +25,12 @@ package com.atlarge.odcsim import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.Assumptions.assumeTrue +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows -import java.lang.IllegalArgumentException const val DELTA: Double = 0.0001 @@ -54,6 +55,16 @@ abstract class ActorSystemTest { } /** + * Test whether the created [ActorSystem] has a path. + */ + @Test + fun `should have a path`() { + val system = factory(object : Behavior<Unit> {}, "test") + + assertTrue(system.path is ActorPath.Root) + } + + /** * Test whether creating an [ActorSystem] sets the initial time at 0. */ @Test @@ -94,8 +105,202 @@ abstract class ActorSystemTest { val until = 10.0 val system = factory(object : Behavior<Unit> {}, name = "test") - assumeTrue(Math.abs(system.time) < DELTA) system.run(until = until) assertTrue(Math.abs(system.time - until) < DELTA) } + + /** + * Test whether an [ActorSystem] will jump forward in time when asking to run until a specified instant. + */ + @Test + fun `should order messages at the instant by insertion time`() { + val behavior = object : Behavior<Int> { + override fun receive(ctx: ActorContext<Int>, msg: Int): Behavior<Int> { + assertEquals(1, msg) + return object : Behavior<Int> { + override fun receive(ctx: ActorContext<Int>, msg: Int): Behavior<Int> { + assertEquals(2, msg) + return this + } + } + } + } + val system = factory(behavior, name = "test") + system.send(1, after = 1.0) + system.send(2, after = 1.0) + system.run(until = 10.0) + } + + @Nested + @DisplayName("ActorRef") + inner class ActorRefTest { + /** + * Test whether an [ActorSystem] disallows sending messages in the past. + */ + @Test + fun `should disallow messages in the past`() { + val system = factory(object : Behavior<Unit> {}, name = "test") + assertThrows<IllegalArgumentException> { system.send(Unit, after = -1.0) } + } + } + + + @Nested + @DisplayName("Actor") + inner class Actor { + /** + * Test whether the pre-start time of the root actor is at 0. + */ + @Test + fun `should pre-start at t=0 if root`() { + val behavior = object : Behavior<Unit> { + override fun receiveSignal(ctx: ActorContext<Unit>, signal: Signal): Behavior<Unit> { + assertTrue(Math.abs(ctx.time) < DELTA) + return this + } + } + + val system = factory(behavior, "test") + system.run() + } + + /** + * Test whether a child actor can be created from an actor. + */ + @Test + fun `should allow spawning of child actors`() { + val behavior = object : Behavior<Unit> { + override fun receiveSignal(ctx: ActorContext<Unit>, signal: Signal): Behavior<Unit> { + throw UnsupportedOperationException("b") + } + } + + val system = factory(object : Behavior<Unit> { + override fun receiveSignal(ctx: ActorContext<Unit>, signal: Signal): Behavior<Unit> { + if (signal is PreStart) { + val ref = ctx.spawn(behavior, "child") + assertEquals("child", ref.path.name) + } + + return this + } + }, name = "test") + + assertThrows<UnsupportedOperationException> { system.run(until = 10.0) } + } + + /** + * Test whether a child actor can be stopped from an actor. + */ + @Test + fun `should allow stopping of child actors`() { + val system = factory(object : Behavior<Unit> { + override fun receiveSignal(ctx: ActorContext<Unit>, signal: Signal): Behavior<Unit> { + if (signal is PreStart) { + val ref = ctx.spawn(object : Behavior<Unit> { + override fun receive(ctx: ActorContext<Unit>, msg: Unit): Behavior<Unit> { + throw UnsupportedOperationException() + } + }, "child") + assertTrue(ctx.stop(ref)) + assertEquals("child", ref.path.name) + } + + return this + } + }, name = "test") + + system.run(until = 10.0) + } + + /** + * Test whether only the parent of a child can terminate it. + */ + @Test + fun `should only be able to terminate child actors`() { + val system = factory(object : Behavior<Unit> { + override fun receiveSignal(ctx: ActorContext<Unit>, signal: Signal): Behavior<Unit> { + val child1 = ctx.spawn(object : Behavior<Unit> {}, "child-1") + ctx.spawn(object : Behavior<Unit> { + override fun receiveSignal(ctx: ActorContext<Unit>, signal: Signal): Behavior<Unit> { + assertFalse(ctx.stop(child1)) + return this + } + }, "child-2") + return this + } + }, name = "test") + system.run() + } + + /** + * Test whether terminating an already terminated child fails. + */ + @Test + fun `should not be able to stop an already terminated child`() { + val system = factory(object : Behavior<Unit> { + override fun receiveSignal(ctx: ActorContext<Unit>, signal: Signal): Behavior<Unit> { + val child = ctx.spawn(object : Behavior<Unit> {}, "child") + ctx.stop(child) + assertFalse(ctx.stop(child)) + return this + } + }, name = "test") + system.run() + } + + /** + * Test whether termination of a child also results in termination of its children. + */ + @Test + fun `should terminate children of child when terminating it`() { + val system = factory(object : Behavior<ActorRef<Unit>> { + lateinit var child: ActorRef<Unit> + + override fun receive(ctx: ActorContext<ActorRef<Unit>>, msg: ActorRef<Unit>): Behavior<ActorRef<Unit>> { + assertTrue(ctx.stop(child)) + msg.send(Unit) // This actor should be stopped now and not receive the message anymore + return this + } + + override fun receiveSignal(ctx: ActorContext<ActorRef<Unit>>, signal: Signal): Behavior<ActorRef<Unit>> { + val root = ctx.self + child = ctx.spawn(object : Behavior<Unit> { + override fun receiveSignal(ctx: ActorContext<Unit>, signal: Signal): Behavior<Unit> { + if (signal is PreStart) { + val child = ctx.spawn(object : Behavior<Unit> { + override fun receive(ctx: ActorContext<Unit>, msg: Unit): Behavior<Unit> { + 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 + fun `should have reference to itself`() { + val behavior = object : Behavior<Unit> { + override fun receive(ctx: ActorContext<Unit>, msg: Unit): Behavior<Unit> { + throw UnsupportedOperationException() + } + override fun receiveSignal(ctx: ActorContext<Unit>, signal: Signal): Behavior<Unit> { + ctx.self.send(Unit) + return this + } + } + + val system = factory(behavior, "test") + assertThrows<UnsupportedOperationException> { system.run() } + } + } } |
