Fix test rule and add exception handler test

This commit is contained in:
tys
2022-04-22 15:07:37 -07:00
parent 6dc4a205c4
commit 5c8fc2dfae
7 changed files with 91 additions and 76 deletions
@@ -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() {
@@ -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()
}
}
@@ -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)
}
}
@@ -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,
@@ -9,23 +9,23 @@ import java.util.*
import kotlin.reflect.KProperty
val ScopeProvider.defaultScope by LazyWithReceiver<ScopeProvider, CoroutineScope> {
asCoroutineScope(SupervisorJob() + scopeName(this, "defaultScope") + RibDispatchers.Default)
asCoroutineScope( defaults(this, "defaultScope") + RibDispatchers.Default)
}
val ScopeProvider.mainScope by LazyWithReceiver<ScopeProvider, CoroutineScope> {
asCoroutineScope(SupervisorJob() + scopeName(this, "mainScope") + RibDispatchers.Main.immediate)
asCoroutineScope(defaults(this, "mainScope") + RibDispatchers.Main.immediate)
}
val ScopeProvider.ioScope by LazyWithReceiver<ScopeProvider, CoroutineScope> {
asCoroutineScope(SupervisorJob() + scopeName(this, "ioScope") + RibDispatchers.IO)
asCoroutineScope(defaults(this, "ioScope") + RibDispatchers.IO)
}
val ScopeProvider.unconfinedScope by LazyWithReceiver<ScopeProvider, CoroutineScope> {
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<This,Return>(val initializer:This.()->Return)
internal class LazyWithReceiver<This,Return>(val initializer:This.()->Return)
{
private val values = WeakHashMap<This,Return>()
@@ -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)
@@ -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<Presenter, Router<*>>()
interactor.attach()
val job = interactor.mainScope.launch {
@@ -42,7 +28,7 @@ class RibScopesTest {
assertThat(job.isActive).isFalse()
}
@Test
@Test()
fun testScopeCaching() {
val interactor1 = FakeInteractor<Presenter, Router<*>>()
@@ -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<Presenter, Router<*>>()
interactor.attach()
interactor.mainScope.launch {
throw RuntimeException("mainScope failed")
}
handler.cleanupTestCoroutines()
}
}