From d61ff7cb4f43d71d2170cdd25ceab2e3edcb81fc Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Fri, 4 Jan 2019 23:03:50 -0500 Subject: Coroutine tests (#185) * Add some coroutine tests for implicit cancellation * Create util test and new helper methods * Remove coroutinescope extension from withcontext * Update dependencies --- .../main/groovy/ca/allanwang/kau/Versions.groovy | 8 +-- .../kotlin/ca/allanwang/kau/utils/ContextUtils.kt | 27 ---------- .../ca/allanwang/kau/utils/CoroutineUtils.kt | 60 ++++++++++++++++++++++ .../ca/allanwang/kau/kotlin/CoroutineTest.kt | 58 +++++++++++++++++++++ docs/Changelog.md | 1 + sample/src/main/res/xml/kau_changelog.xml | 2 +- 6 files changed, 125 insertions(+), 31 deletions(-) create mode 100644 core/src/main/kotlin/ca/allanwang/kau/utils/CoroutineUtils.kt create mode 100644 core/src/test/kotlin/ca/allanwang/kau/kotlin/CoroutineTest.kt diff --git a/buildSrc/src/main/groovy/ca/allanwang/kau/Versions.groovy b/buildSrc/src/main/groovy/ca/allanwang/kau/Versions.groovy index e6f3cd7..4afec82 100644 --- a/buildSrc/src/main/groovy/ca/allanwang/kau/Versions.groovy +++ b/buildSrc/src/main/groovy/ca/allanwang/kau/Versions.groovy @@ -27,7 +27,7 @@ class Versions { static def kotlin = '1.3.11' // https://github.com/Kotlin/kotlinx.coroutines/releases - static def coroutines = '1.0.1' + static def coroutines = '1.1.0' // https://github.com/mikepenz/AboutLibraries/releases static def aboutLibraries = '6.2.0' @@ -72,6 +72,8 @@ class Versions { static def playPublishPlugin = '1.2.2' // https://github.com/KeepSafe/dexcount-gradle-plugin/releases - static def dexCountPlugin = '0.8.3' - static def gitVersionPlugin = '0.4.4' + static def dexCountPlugin = '0.8.5' + + // https://github.com/gladed/gradle-android-git-version/releases + static def gitVersionPlugin = '0.4.7' } \ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt index 09ad4ea..fc8049d 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt @@ -26,7 +26,6 @@ import android.content.pm.PackageManager import android.graphics.drawable.Drawable import android.net.Uri import android.os.Bundle -import android.os.Handler import android.os.Looper import android.util.TypedValue import android.view.View @@ -47,36 +46,10 @@ import androidx.core.content.ContextCompat import ca.allanwang.kau.R import ca.allanwang.kau.logging.KL import com.afollestad.materialdialogs.MaterialDialog -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.android.asCoroutineDispatcher -import kotlin.coroutines.CoroutineContext /** * Created by Allan Wang on 2017-06-03. */ -object ContextHelper : CoroutineScope { - - val looper = Looper.getMainLooper() - - val handler = Handler(looper) - - /** - * Creating dispatcher from main handler to avoid IO - * See https://github.com/Kotlin/kotlinx.coroutines/issues/878 - */ - val dispatcher = handler.asCoroutineDispatcher("kau-main") - - override val coroutineContext: CoroutineContext get() = dispatcher -} - -/** - * Most context items implement [CoroutineScope] by default. - * We will add a fallback just in case. - * It is expected that the scope returned always has the Android main dispatcher as part of the context. - */ -internal inline val Context.ctxCoroutine: CoroutineScope - get() = this as? CoroutineScope ?: ContextHelper - fun Context.runOnUiThread(f: Context.() -> Unit) { if (ContextHelper.looper === Looper.myLooper()) f() else ContextHelper.handler.post { f() } } diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/CoroutineUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/CoroutineUtils.kt new file mode 100644 index 0000000..032c407 --- /dev/null +++ b/core/src/main/kotlin/ca/allanwang/kau/utils/CoroutineUtils.kt @@ -0,0 +1,60 @@ +package ca.allanwang.kau.utils + +import android.content.Context +import android.os.Handler +import android.os.Looper +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.android.asCoroutineDispatcher +import kotlinx.coroutines.async +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext + +object ContextHelper : CoroutineScope { + + val looper = Looper.getMainLooper() + + val handler = Handler(looper) + + /** + * Creating dispatcher from main handler to avoid IO + * See https://github.com/Kotlin/kotlinx.coroutines/issues/878 + */ + val dispatcher = handler.asCoroutineDispatcher("kau-main") + + override val coroutineContext: CoroutineContext get() = dispatcher +} + +/** + * Most context items implement [CoroutineScope] by default. + * We will add a fallback just in case. + * It is expected that the scope returned always has the Android main dispatcher as part of the context. + */ +internal inline val Context.ctxCoroutine: CoroutineScope + get() = this as? CoroutineScope ?: ContextHelper + +/** + * Calls [launch] with an explicit dispatcher for Android's main thread + */ +fun CoroutineScope.launchMain( + context: CoroutineContext = EmptyCoroutineContext, + start: CoroutineStart = CoroutineStart.DEFAULT, + block: suspend CoroutineScope.() -> Unit +) = launch(ContextHelper.dispatcher + context, start, block) + +/** + * Calls [async] with an explicit dispatcher for Android's main thread + */ +fun CoroutineScope.asyncMain( + context: CoroutineContext = EmptyCoroutineContext, + start: CoroutineStart = CoroutineStart.DEFAULT, + block: suspend CoroutineScope.() -> Unit +) = async(ContextHelper.dispatcher + context, start, block) + +/** + * Calls [withContext] with an explicit dispatcher for Android's main thread + */ +suspend fun withMainContext(block: suspend CoroutineScope.() -> T) = + withContext(ContextHelper.dispatcher, block) diff --git a/core/src/test/kotlin/ca/allanwang/kau/kotlin/CoroutineTest.kt b/core/src/test/kotlin/ca/allanwang/kau/kotlin/CoroutineTest.kt new file mode 100644 index 0000000..91d0174 --- /dev/null +++ b/core/src/test/kotlin/ca/allanwang/kau/kotlin/CoroutineTest.kt @@ -0,0 +1,58 @@ +package ca.allanwang.kau.kotlin + +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.fail + +/** + * Tests geared towards coroutines + */ +class CoroutineTest { + + /** + * If a job is cancelled, then a switch to a new context will not run + */ + @Test + fun implicitCancellationBefore() { + val job = Job() + var id = 0 + try { + runBlocking(job) { + id++ + job.cancel() + withContext(Dispatchers.IO) { + fail("Context switch should not be reached") + } + } + } catch (ignore: CancellationException) { + } finally { + assertEquals(1, id, "Launcher never executed") + } + } + + /** + * If a job is cancelled, then a switch from a new context will not run + */ + @Test + fun implicitCancellationAfter() { + val job = Job() + var id = 0 + try { + runBlocking(job) { + withContext(Dispatchers.IO) { + id++ + job.cancel() + } + fail("Post context switch should not be reached") + } + } catch (ignore: CancellationException) { + } finally { + assertEquals(1, id, "Context switch never executed") + } + } +} \ No newline at end of file diff --git a/docs/Changelog.md b/docs/Changelog.md index e6d51d2..7291753 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -5,6 +5,7 @@ * :core: Add default CoroutineScope implementation to KauBaseActivity * :core: Remove zip class. Coroutines and join can be used as an alternative * :core: Delete flyweight implementation. Kotlin already has getOrPut +* :core: Introduce ContextHelper, where you can get the default looper, handler, and dispatcher for Android * :mediapicker: Use video preloading instead of full async loading ## v4.0.0-alpha01 diff --git a/sample/src/main/res/xml/kau_changelog.xml b/sample/src/main/res/xml/kau_changelog.xml index 0283d7f..570a0b9 100644 --- a/sample/src/main/res/xml/kau_changelog.xml +++ b/sample/src/main/res/xml/kau_changelog.xml @@ -11,10 +11,10 @@ + - -- cgit v1.2.3