summaryrefslogtreecommitdiff
path: root/odcsim-core
diff options
context:
space:
mode:
authorFabian Mastenbroek <mail.fabianm@gmail.com>2018-11-21 23:19:32 +0100
committerFabian Mastenbroek <mail.fabianm@gmail.com>2019-05-13 20:26:45 +0200
commitff49cd6c9079264e04d2efa85b03a024d0e00cca (patch)
treebc925038f730e784c7d8ed3c0cbe0e07178b0417 /odcsim-core
parente1530fee893b5e9ceebf5e07b490c1c82da2e687 (diff)
feat: Add consistent exception handling in actor systems
This change makes exception handling in actor systems consistent. Implementors should not propagate unhandled exceptions in actors to the (one of the) threads running the engine, but instead should stop the offending actor and continue running. Supervision of actors should be implemented within actor behavior instead. Since BehaviorInterpreter will propagate exceptions thrown by an actor, consumers of that API can use it to catch unhandled exceptions in an actor.
Diffstat (limited to 'odcsim-core')
-rw-r--r--odcsim-core/build.gradle1
-rw-r--r--odcsim-core/src/test/kotlin/com/atlarge/odcsim/ActorSystemTest.kt73
-rw-r--r--odcsim-core/src/test/kotlin/com/atlarge/odcsim/BehaviorTest.kt78
3 files changed, 118 insertions, 34 deletions
diff --git a/odcsim-core/build.gradle b/odcsim-core/build.gradle
index 3ae40a0d..e4e61296 100644
--- a/odcsim-core/build.gradle
+++ b/odcsim-core/build.gradle
@@ -37,6 +37,7 @@ dependencies {
testImplementation "org.junit.jupiter:junit-jupiter-api:$junit_jupiter_version"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junit_jupiter_version"
testImplementation "org.junit.platform:junit-platform-launcher:$junit_platform_version"
+ testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.0.0"
}
/* Create configuration for test suite used by implementors */
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 492dc686..3385767b 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,7 @@
package com.atlarge.odcsim
+import com.atlarge.odcsim.Behavior.Companion.same
import com.atlarge.odcsim.dsl.empty
import com.atlarge.odcsim.dsl.ignore
import com.atlarge.odcsim.dsl.receive
@@ -134,6 +135,23 @@ abstract class ActorSystemTest {
}
/**
+ * Test whether an [ActorSystem] will not process messages in the queue after the deadline.
+ */
+ @Test
+ fun `should not process messages after deadline`() {
+ var counter = 0
+ val behavior = Behavior.receiveMessage<Unit> { _ ->
+ counter++
+ same()
+ }
+ val system = factory(behavior, name = "test")
+ system.send(Unit, after = 3.0)
+ system.send(Unit, after = 1.0)
+ system.run(until = 2.0)
+ assertEquals(1, counter)
+ }
+
+ /**
* Test whether an [ActorSystem] will not initialize the root actor if the system has not been run yet.
*/
@Test
@@ -176,7 +194,8 @@ abstract class ActorSystemTest {
*/
@Test
fun `should allow spawning of child actors`() {
- val behavior = Behavior.setup<Unit> { throw UnsupportedOperationException("b") }
+ var spawned = false
+ val behavior = Behavior.setup<Unit> { spawned = true; Behavior.empty() }
val system = factory(Behavior.setup<Unit> { ctx ->
val ref = ctx.spawn(behavior, "child")
@@ -184,7 +203,8 @@ abstract class ActorSystemTest {
Behavior.ignore()
}, name = "test")
- assertThrows<UnsupportedOperationException> { system.run(until = 10.0) }
+ system.run(until = 10.0)
+ assertTrue(spawned)
}
/**
@@ -287,13 +307,15 @@ abstract class ActorSystemTest {
*/
@Test
fun `should have reference to itself`() {
+ var flag = false
val behavior: Behavior<Unit> = Behavior.setup { ctx ->
ctx.self.send(Unit)
- Behavior.receiveMessage { throw UnsupportedOperationException() }
+ Behavior.receiveMessage { flag = true; same() }
}
val system = factory(behavior, "test")
- assertThrows<UnsupportedOperationException> { system.run() }
+ system.run()
+ assertTrue(flag)
}
/**
@@ -306,24 +328,6 @@ abstract class ActorSystemTest {
}
/**
- * 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<Unit>(), "test")
- assertThrows<IllegalArgumentException> { 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<Unit> { Behavior.unhandled() }, "test")
- assertThrows<IllegalArgumentException> { system.run() }
- }
-
- /**
* Test whether we can start an actor with the [Behavior.Companion.stopped] behavior.
*/
@Test
@@ -332,22 +336,23 @@ abstract class ActorSystemTest {
system.run()
}
- /**
- * Test whether deferred behavior that returns [Behavior.Companion.same] fails.
- */
- @Test
- fun `should not allow setup to return same`() {
- val system = factory(Behavior.setup<Unit> { Behavior.same() }, "test")
- assertThrows<IllegalArgumentException> { system.run() }
- }
/**
- * Test whether deferred behavior that returns [Behavior.Companion.unhandled] fails.
+ * Test whether an actor that is crashed cannot receive more messages.
*/
@Test
- fun `should not allow setup to return unhandled`() {
- val system = factory(Behavior.setup<Unit> { Behavior.unhandled() }, "test")
- assertThrows<IllegalArgumentException> { system.run() }
+ fun `should stop if it crashes`() {
+ var counter = 0
+ val system = factory(Behavior.receiveMessage<Unit> {
+ counter++
+ throw IllegalArgumentException("STAGED")
+ }, "test")
+
+ system.send(Unit)
+ system.send(Unit)
+
+ system.run()
+ assertEquals(1, counter)
}
}
}
diff --git a/odcsim-core/src/test/kotlin/com/atlarge/odcsim/BehaviorTest.kt b/odcsim-core/src/test/kotlin/com/atlarge/odcsim/BehaviorTest.kt
new file mode 100644
index 00000000..6f97e428
--- /dev/null
+++ b/odcsim-core/src/test/kotlin/com/atlarge/odcsim/BehaviorTest.kt
@@ -0,0 +1,78 @@
+/*
+ * 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 com.atlarge.odcsim.dsl.setup
+import com.atlarge.odcsim.internal.BehaviorInterpreter
+import com.nhaarman.mockitokotlin2.mock
+import org.junit.jupiter.api.DisplayName
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+
+/**
+ * Test suite for [Behavior] and [BehaviorInterpreter].
+ */
+@DisplayName("Behavior")
+class BehaviorTest {
+ /**
+ * Test whether we cannot start an actor with the [Behavior.Companion.unhandled] behavior.
+ */
+ @Test
+ fun `should not start with unhandled behavior`() {
+ val ctx = mock<ActorContext<Unit>>()
+ val interpreter = BehaviorInterpreter(Behavior.unhandled<Unit>())
+ assertThrows<IllegalArgumentException> { interpreter.start(ctx) }
+ }
+
+ /**
+ * Test whether we cannot start an actor with deferred unhandled behavior.
+ */
+ @Test
+ fun `should not start with deferred unhandled behavior`() {
+ val ctx = mock<ActorContext<Unit>>()
+ val interpreter = BehaviorInterpreter(Behavior.setup<Unit> { Behavior.unhandled() })
+ assertThrows<IllegalArgumentException> { interpreter.start(ctx) }
+ }
+
+ /**
+ * Test whether deferred behavior that returns [Behavior.Companion.same] fails.
+ */
+ @Test
+ fun `should not allow setup to return same`() {
+ val ctx = mock<ActorContext<Unit>>()
+ val interpreter = BehaviorInterpreter(Behavior.setup<Unit> { Behavior.same() })
+ assertThrows<IllegalArgumentException> { interpreter.start(ctx) }
+ }
+
+ /**
+ * Test whether deferred behavior that returns [Behavior.Companion.unhandled] fails.
+ */
+ @Test
+ fun `should not allow setup to return unhandled`() {
+ val ctx = mock<ActorContext<Unit>>()
+ val interpreter = BehaviorInterpreter(Behavior.setup<Unit> { Behavior.unhandled() })
+ assertThrows<IllegalArgumentException> { interpreter.start(ctx) }
+ }
+}