From 5c8fc2dfae0b01a34d2d4ce55fa1702be18ca742 Mon Sep 17 00:00:00 2001 From: tys Date: Thu, 21 Apr 2022 08:39:57 -0700 Subject: [PATCH] Fix test rule and add exception handler test --- .../uber/rib/compose/ComposeApplication.kt | 4 -- .../com/uber/rib/core/RibDispatchersRule.kt | 43 ++++++++++--------- .../com/uber/rib/core/RibCoroutinesConfig.kt | 40 +++++++++++++++++ .../com/uber/rib/core/RibDispatchers.kt | 28 ++---------- .../kotlin/com/uber/rib/core/RibScopes.kt | 12 +++--- .../com/uber/rib/core/RibDispatchersTest.kt | 2 +- .../kotlin/com/uber/rib/core/RibScopesTest.kt | 38 ++++++++-------- 7 files changed, 91 insertions(+), 76 deletions(-) create mode 100644 android/libraries/rib-coroutines/src/main/kotlin/com/uber/rib/core/RibCoroutinesConfig.kt diff --git a/android/demos/compose/src/main/kotlin/com/uber/rib/compose/ComposeApplication.kt b/android/demos/compose/src/main/kotlin/com/uber/rib/compose/ComposeApplication.kt index feb53bfb..00743aff 100644 --- a/android/demos/compose/src/main/kotlin/com/uber/rib/compose/ComposeApplication.kt +++ b/android/demos/compose/src/main/kotlin/com/uber/rib/compose/ComposeApplication.kt @@ -22,11 +22,7 @@ import com.facebook.flipper.core.FlipperClient import com.facebook.flipper.plugins.inspector.DescriptorMapping import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin import com.facebook.soloader.SoLoader -import com.uber.rib.core.RibDispatchersConfig -import com.uber.rib.core.RibDispatchersProvider import com.uber.rib.flipper.RibTreePlugin -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.MainCoroutineDispatcher class ComposeApplication : Application() { diff --git a/android/libraries/rib-coroutines-test/src/main/kotlin/com/uber/rib/core/RibDispatchersRule.kt b/android/libraries/rib-coroutines-test/src/main/kotlin/com/uber/rib/core/RibDispatchersRule.kt index 5e510821..9f6ea019 100644 --- a/android/libraries/rib-coroutines-test/src/main/kotlin/com/uber/rib/core/RibDispatchersRule.kt +++ b/android/libraries/rib-coroutines-test/src/main/kotlin/com/uber/rib/core/RibDispatchersRule.kt @@ -1,32 +1,33 @@ package com.uber.rib.core import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestCoroutineDispatcher import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.setMain -import org.junit.rules.TestRule +import org.junit.rules.TestWatcher import org.junit.runner.Description -import org.junit.runners.model.Statement -class RibDispatchersRule : TestRule { - @OptIn(ExperimentalCoroutinesApi::class) - override fun apply(base: Statement, description: Description) = object : Statement() { - override fun evaluate() { - Dispatchers.setMain(TestCoroutineDispatcher()) - RibDispatchersConfig.delegate = DefaultRibDispatcherProvider( - Default = TestCoroutineDispatcher(), - Main = Dispatchers.Main, - IO = TestCoroutineDispatcher(), - Unconfined = TestCoroutineDispatcher()) +class RibDispatchersRule : TestWatcher() { - Dispatchers.Main - try { - base.evaluate() - } finally { - Dispatchers.resetMain() - RibDispatchersConfig.reset() - } - } + override fun starting(description: Description) { + val defaultDispatcher = TestCoroutineDispatcher() + val mainDispatcher = TestCoroutineDispatcher() + val ioDispatcher = TestCoroutineDispatcher() + val unconfinedDispatcher = TestCoroutineDispatcher() + + Dispatchers.setMain(mainDispatcher) + + val mainDispatcherProxy = Dispatchers.Main + + RibCoroutinesConfig.dispatchers = DefaultRibDispatcherProvider( + Default = defaultDispatcher, + Main = mainDispatcherProxy, + IO = ioDispatcher, + Unconfined = unconfinedDispatcher) + } + + override fun finished(description: Description) { + Dispatchers.resetMain() + RibCoroutinesConfig.reset() } } \ No newline at end of file diff --git a/android/libraries/rib-coroutines/src/main/kotlin/com/uber/rib/core/RibCoroutinesConfig.kt b/android/libraries/rib-coroutines/src/main/kotlin/com/uber/rib/core/RibCoroutinesConfig.kt new file mode 100644 index 00000000..49192b2d --- /dev/null +++ b/android/libraries/rib-coroutines/src/main/kotlin/com/uber/rib/core/RibCoroutinesConfig.kt @@ -0,0 +1,40 @@ +package com.uber.rib.core + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.CoroutineExceptionHandler + +object RibCoroutinesConfig { + /** + * Specify [RibDispatchersProvider] that provide default [CoroutineDispatcher]'s for Rib based scopes. + * Defaults to standard [Dispatchers]. + * Useful in areas where injecting Dispatchers is not ideal, such as Test. + */ + @JvmStatic + var dispatchers = createDefaultDispatchers() + + /** + * Specify [CoroutineExceptionHandler] to be used with Rib based scopes. + * Defaults to throwing exception. + * Useful for specifying additional information before passed to [Thread.UncaughtExceptionHandler]. + */ + @JvmStatic + var exceptionHandler = createDefaultExceptionHandler() + + /** + * Resets delegate to default [RibDispatchersProvider]. + * Should be called after tests + */ + @JvmStatic + fun reset() { + this.dispatchers = createDefaultDispatchers() + this.exceptionHandler = createDefaultExceptionHandler() + } + + private fun createDefaultDispatchers() = DefaultRibDispatcherProvider() + + private fun createDefaultExceptionHandler() = CoroutineExceptionHandler { _, throwable -> + throw (throwable) + } + +} \ No newline at end of file diff --git a/android/libraries/rib-coroutines/src/main/kotlin/com/uber/rib/core/RibDispatchers.kt b/android/libraries/rib-coroutines/src/main/kotlin/com/uber/rib/core/RibDispatchers.kt index 2a251a31..b3aeb0d5 100644 --- a/android/libraries/rib-coroutines/src/main/kotlin/com/uber/rib/core/RibDispatchers.kt +++ b/android/libraries/rib-coroutines/src/main/kotlin/com/uber/rib/core/RibDispatchers.kt @@ -5,30 +5,10 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.MainCoroutineDispatcher object RibDispatchers : RibDispatchersProvider { - override val Default get() = RibDispatchersConfig.delegate.Default - override val Main get() = RibDispatchersConfig.delegate.Main - override val IO get() = RibDispatchersConfig.delegate.IO - override val Unconfined get() = RibDispatchersConfig.delegate.Unconfined -} - -object RibDispatchersConfig { - /** - * Delegate used to override default Dispatchers. - * Useful in areas where injecting Dispatchers is not ideal, such as Test. - */ - @JvmStatic - var delegate = createDefaultRibDispatchers() - - /** - * Resets delegate to default [RibDispatchersProvider]. - * Should be called after tests - */ - @JvmStatic - fun reset() { - this.delegate = createDefaultRibDispatchers() - } - - private fun createDefaultRibDispatchers() = DefaultRibDispatcherProvider() + override val Default get() = RibCoroutinesConfig.dispatchers.Default + override val Main get() = RibCoroutinesConfig.dispatchers.Main + override val IO get() = RibCoroutinesConfig.dispatchers.IO + override val Unconfined get() = RibCoroutinesConfig.dispatchers.Unconfined } class DefaultRibDispatcherProvider(override val Default : CoroutineDispatcher = Dispatchers.Default, diff --git a/android/libraries/rib-coroutines/src/main/kotlin/com/uber/rib/core/RibScopes.kt b/android/libraries/rib-coroutines/src/main/kotlin/com/uber/rib/core/RibScopes.kt index 47fab497..fe1e8ad6 100755 --- a/android/libraries/rib-coroutines/src/main/kotlin/com/uber/rib/core/RibScopes.kt +++ b/android/libraries/rib-coroutines/src/main/kotlin/com/uber/rib/core/RibScopes.kt @@ -9,23 +9,23 @@ import java.util.* import kotlin.reflect.KProperty val ScopeProvider.defaultScope by LazyWithReceiver { - asCoroutineScope(SupervisorJob() + scopeName(this, "defaultScope") + RibDispatchers.Default) + asCoroutineScope( defaults(this, "defaultScope") + RibDispatchers.Default) } val ScopeProvider.mainScope by LazyWithReceiver { - asCoroutineScope(SupervisorJob() + scopeName(this, "mainScope") + RibDispatchers.Main.immediate) + asCoroutineScope(defaults(this, "mainScope") + RibDispatchers.Main.immediate) } val ScopeProvider.ioScope by LazyWithReceiver { - asCoroutineScope(SupervisorJob() + scopeName(this, "ioScope") + RibDispatchers.IO) + asCoroutineScope(defaults(this, "ioScope") + RibDispatchers.IO) } val ScopeProvider.unconfinedScope by LazyWithReceiver { - asCoroutineScope(SupervisorJob() + scopeName(this, "unconfinedScope") + RibDispatchers.Unconfined) + asCoroutineScope(defaults(this, "unconfinedScope") + RibDispatchers.Unconfined) } -private fun scopeName(scopeProvider : ScopeProvider, scopeName: String) = CoroutineName("${scopeProvider::class.simpleName}:${scopeName}") +private fun defaults(scopeProvider : ScopeProvider, scopeName: String) = SupervisorJob() + RibCoroutinesConfig.exceptionHandler + CoroutineName("${scopeProvider::class.simpleName}:${scopeName}") -private class LazyWithReceiver(val initializer:This.()->Return) +internal class LazyWithReceiver(val initializer:This.()->Return) { private val values = WeakHashMap() diff --git a/android/libraries/rib-coroutines/src/test/kotlin/com/uber/rib/core/RibDispatchersTest.kt b/android/libraries/rib-coroutines/src/test/kotlin/com/uber/rib/core/RibDispatchersTest.kt index 40acfcc4..2db500d8 100644 --- a/android/libraries/rib-coroutines/src/test/kotlin/com/uber/rib/core/RibDispatchersTest.kt +++ b/android/libraries/rib-coroutines/src/test/kotlin/com/uber/rib/core/RibDispatchersTest.kt @@ -25,7 +25,7 @@ class RibDispatchersTest { assertThat(RibDispatchers.Main).isNotEqualTo(testDispatcher.Main) assertThat(RibDispatchers.Unconfined).isNotEqualTo(testDispatcher.Unconfined) - RibDispatchersConfig.delegate = testDispatcher + RibCoroutinesConfig.dispatchers = testDispatcher assertThat(RibDispatchers.Default).isEqualTo(testDispatcher.Default) assertThat(RibDispatchers.IO).isEqualTo(testDispatcher.IO) diff --git a/android/libraries/rib-coroutines/src/test/kotlin/com/uber/rib/core/RibScopesTest.kt b/android/libraries/rib-coroutines/src/test/kotlin/com/uber/rib/core/RibScopesTest.kt index 6d9f4b8b..9f5c9d31 100644 --- a/android/libraries/rib-coroutines/src/test/kotlin/com/uber/rib/core/RibScopesTest.kt +++ b/android/libraries/rib-coroutines/src/test/kotlin/com/uber/rib/core/RibScopesTest.kt @@ -2,34 +2,20 @@ package com.uber.rib.core import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.* -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.setMain +import kotlinx.coroutines.test.* import org.junit.After import org.junit.Before +import org.junit.Rule import org.junit.Test +import java.lang.RuntimeException @OptIn(ExperimentalCoroutinesApi::class) class RibScopesTest { - @Before - fun setup() { - Dispatchers.setMain(TestCoroutineDispatcher()) - RibDispatchersConfig.delegate = DefaultRibDispatcherProvider( - Default = TestCoroutineDispatcher(), - Main = Dispatchers.Main, - IO = TestCoroutineDispatcher(), - Unconfined = TestCoroutineDispatcher()) - } - - @After - fun teardown() { - Dispatchers.resetMain() - RibDispatchersConfig.reset() - } + @get:Rule var rule = RibDispatchersRule() @Test - fun testScopeLifecycle() = runBlocking { + fun testScopeLifecycle() = runBlockingTest { val interactor = FakeInteractor>() interactor.attach() val job = interactor.mainScope.launch { @@ -42,7 +28,7 @@ class RibScopesTest { assertThat(job.isActive).isFalse() } - @Test + @Test() fun testScopeCaching() { val interactor1 = FakeInteractor>() @@ -58,5 +44,17 @@ class RibScopesTest { assertThat(interactor1mainScope1).isNotEqualTo(interactor2mainScope1) } + @Test(expected = RuntimeException::class) + fun testUncaughtHandler() = runBlockingTest { + val handler = TestCoroutineExceptionHandler() + RibCoroutinesConfig.exceptionHandler = handler + val interactor = FakeInteractor>() + interactor.attach() + + interactor.mainScope.launch { + throw RuntimeException("mainScope failed") + } + handler.cleanupTestCoroutines() + } } \ No newline at end of file