From 1f96b21c037e1cc0e6d7b6da8c3d7ab4f4287b6f Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Thu, 12 Sep 2019 04:25:32 -0400 Subject: [PATCH 01/12] Stub coroutines-interop module --- autodispose-coroutines-interop/Module.md | 9 +++++ autodispose-coroutines-interop/build.gradle | 37 +++++++++++++++++++ .../gradle.properties | 20 ++++++++++ build.gradle | 2 +- gradle/dependencies.gradle | 1 + settings.gradle | 1 + 6 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 autodispose-coroutines-interop/Module.md create mode 100755 autodispose-coroutines-interop/build.gradle create mode 100755 autodispose-coroutines-interop/gradle.properties diff --git a/autodispose-coroutines-interop/Module.md b/autodispose-coroutines-interop/Module.md new file mode 100644 index 000000000..8d836d7f6 --- /dev/null +++ b/autodispose-coroutines-interop/Module.md @@ -0,0 +1,9 @@ +# Module autodispose-courtines-interop + +Extension functions to interop `ScopeProvider`/`Completable` and `CoroutineScope`, as well as +`autoDispose(CoroutineScope)` extension functions on RxJava types. + +# Package com.uber.autodispose.coroutinesinterop + +Extension functions to interop `ScopeProvider`/`Completable` and `CoroutineScope`, as well as +`autoDispose(CoroutineScope)` extension functions on RxJava types. diff --git a/autodispose-coroutines-interop/build.gradle b/autodispose-coroutines-interop/build.gradle new file mode 100755 index 000000000..f0f9b6e83 --- /dev/null +++ b/autodispose-coroutines-interop/build.gradle @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2019. Uber Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +plugins { + id 'com.android.lint' + id 'ru.vyarus.animalsniffer' +} + +lintOptions { + abortOnError true + warningsAsErrors true +} + +dependencies { + api deps.rx.java + api project(':autodispose') + api deps.kotlin.coroutines + + lintChecks project(':static-analysis:autodispose-lint') + + testImplementation project(':test-utils') +} + +apply from: rootProject.file('gradle/gradle-mvn-push.gradle') diff --git a/autodispose-coroutines-interop/gradle.properties b/autodispose-coroutines-interop/gradle.properties new file mode 100755 index 000000000..1c37d81b9 --- /dev/null +++ b/autodispose-coroutines-interop/gradle.properties @@ -0,0 +1,20 @@ +# +# Copyright (C) 2019. Uber Technologies +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +POM_NAME=AutoDispose (Coroutines Interop) +POM_ARTIFACT_ID=autodispose-coroutines-interop +POM_PACKAGING=jar +AUTOMATIC_MODULE_NAME=com.uber.autodispose.coroutinesinterop diff --git a/build.gradle b/build.gradle index bd33adf5e..670761c71 100755 --- a/build.gradle +++ b/build.gradle @@ -98,7 +98,7 @@ subprojects { boolean isMixedSourceSet = project.name in mixedSourcesArtifacts boolean isAndroidLibrary = project.path.startsWith(":android:") boolean isLint = project.path.endsWith("-lint") - boolean isKotlin = project.path.endsWith("-ktx") || isLint || isMixedSourceSet + boolean isKotlin = project.path.endsWith("-ktx") || isLint || isMixedSourceSet || project.path.endsWith("coroutines-interop") boolean isSample = project.name == "sample" boolean isJavaLibrary = !isAndroidLibrary && !isKotlin && !isSample || (isMixedSourceSet && !isAndroidLibrary) boolean usesErrorProne = !isKotlin && !isSample || isMixedSourceSet diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 0a4001e62..92ab1d1cc 100755 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -62,6 +62,7 @@ def build = [ ] def kotlin = [ + coroutines: "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.1", stdlib: "org.jetbrains.kotlin:kotlin-stdlib:${versions.kotlin}" ] diff --git a/settings.gradle b/settings.gradle index 402c8159c..ef336e025 100755 --- a/settings.gradle +++ b/settings.gradle @@ -60,6 +60,7 @@ if (System.getenv("ANDROID_HOME") != null) { include ':sample' } include ':autodispose' +include ':autodispose-coroutines-interop' include ':autodispose-lifecycle' include ':autodispose-rxlifecycle' include ':autodispose-rxlifecycle3' From 536a66f7de6ab45f52334574aaefdb10ff4a5eb0 Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Thu, 12 Sep 2019 04:31:12 -0400 Subject: [PATCH 02/12] Implement coroutines interop Resolves #371 --- .../AutoDisposeCoroutinesInterop.kt | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 autodispose-coroutines-interop/src/main/kotlin/com/uber/autodispose/coroutinesinterop/AutoDisposeCoroutinesInterop.kt diff --git a/autodispose-coroutines-interop/src/main/kotlin/com/uber/autodispose/coroutinesinterop/AutoDisposeCoroutinesInterop.kt b/autodispose-coroutines-interop/src/main/kotlin/com/uber/autodispose/coroutinesinterop/AutoDisposeCoroutinesInterop.kt new file mode 100644 index 000000000..752af0473 --- /dev/null +++ b/autodispose-coroutines-interop/src/main/kotlin/com/uber/autodispose/coroutinesinterop/AutoDisposeCoroutinesInterop.kt @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2019. Uber Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@file:Suppress("NOTHING_TO_INLINE", "unused") + +package com.uber.autodispose.coroutinesinterop + +import com.uber.autodispose.CompletableSubscribeProxy +import com.uber.autodispose.FlowableSubscribeProxy +import com.uber.autodispose.MaybeSubscribeProxy +import com.uber.autodispose.ObservableSubscribeProxy +import com.uber.autodispose.ScopeProvider +import com.uber.autodispose.SingleSubscribeProxy +import com.uber.autodispose.autoDispose +import io.reactivex.Completable +import io.reactivex.CompletableSource +import io.reactivex.Flowable +import io.reactivex.Maybe +import io.reactivex.Observable +import io.reactivex.Single +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlin.coroutines.CoroutineContext + +/** Extension that proxies to the normal [autoDispose] extension function with a [ScopeProvider]. */ +inline fun Flowable.autoDispose(scope: CoroutineScope): FlowableSubscribeProxy { + return autoDispose(scope.asScopeProvider()) +} + +/** Extension that proxies to the normal [autoDispose] extension function with a [ScopeProvider]. */ +inline fun Observable.autoDispose(scope: CoroutineScope): ObservableSubscribeProxy { + return autoDispose(scope.asScopeProvider()) +} + +/** Extension that proxies to the normal [autoDispose] extension function with a [ScopeProvider]. */ +inline fun Single.autoDispose(scope: CoroutineScope): SingleSubscribeProxy { + return autoDispose(scope.asScopeProvider()) +} + +/** Extension that proxies to the normal [autoDispose] extension function with a [ScopeProvider]. */ +inline fun Maybe.autoDispose(scope: CoroutineScope): MaybeSubscribeProxy { + return autoDispose(scope.asScopeProvider()) +} + +/** Extension that proxies to the normal [autoDispose] extension function with a [ScopeProvider]. */ +inline fun Completable.autoDispose(scope: CoroutineScope): CompletableSubscribeProxy { + return autoDispose(scope.asScopeProvider()) +} + +/** + * @return a [ScopeProvider] representation of this [CoroutineScope]. This scope will complete when + * [this] coroutine scope completes. + */ +fun CoroutineScope.asScopeProvider(): ScopeProvider = ScopeProvider { asUndeferredCompletable() } + +/** + * @return a [Completable] representation of this [CoroutineScope]. This will complete when [this] + * coroutine scope completes. Note that the returned [Completable] is deferred. + */ +fun CoroutineScope.asCompletable(): Completable { + return Completable.defer { asUndeferredCompletable() } +} + +private fun CoroutineScope.asUndeferredCompletable(): Completable { + return Completable.create { emitter -> + val job = coroutineContext[Job] ?: error( + "Scope cannot be created because it does not have a job: ${this@asUndeferredCompletable}") + job.invokeOnCompletion { + when (it) { + null, is CancellationException -> emitter.onComplete() + else -> emitter.onError(it) + } + } + } +} + +/** + * @return a [CoroutineScope] representation of this [ScopeProvider]. This scope will cancel when + * [this] scope provider completes. + */ +fun ScopeProvider.asCoroutineScope(context: CoroutineContext): CoroutineScope { + return requestScope().asCoroutineScope(context) +} + +/** + * @return a [CoroutineScope] representation of this [CompletableSource]. This scope will cancel + * when [this] scope provider completes. + */ +fun CompletableSource.asCoroutineScope(context: CoroutineContext): CoroutineScope { + val scope = CoroutineScope(SupervisorJob() + context) + + // Bind to the scope, so if the scope is manually canceled before our scope provider emits, we + // clean up here. + Completable.wrap(this) + .autoDispose(scope) + .subscribe({ scope.cancel() }) { e -> scope.cancel("OnError", e) } + + return scope +} From 0c66c51bf9333a93d9de258d02e8e68797056559 Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Thu, 12 Sep 2019 04:33:00 -0400 Subject: [PATCH 03/12] Stub tests --- .../coroutinesintrop/AutoDisposeCoroutinesInterop.kt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 autodispose-coroutines-interop/src/test/kotlin/com/uber/autodispose/coroutinesintrop/AutoDisposeCoroutinesInterop.kt diff --git a/autodispose-coroutines-interop/src/test/kotlin/com/uber/autodispose/coroutinesintrop/AutoDisposeCoroutinesInterop.kt b/autodispose-coroutines-interop/src/test/kotlin/com/uber/autodispose/coroutinesintrop/AutoDisposeCoroutinesInterop.kt new file mode 100644 index 000000000..3fbad1ae9 --- /dev/null +++ b/autodispose-coroutines-interop/src/test/kotlin/com/uber/autodispose/coroutinesintrop/AutoDisposeCoroutinesInterop.kt @@ -0,0 +1,9 @@ +package com.uber.autodispose.coroutinesintrop + +class AutoDisposeCoroutinesInterop { + // TODO Each rx type extension + // TODO ScopeProvider -> Scope + // TODO Completable -> Scope + // TODO Scope -> ScopeProvider + // TODO Scope -> Completable +} From a532a69b4f528b1395f51c2ae1b211cfa4b0a413 Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Thu, 12 Sep 2019 04:36:19 -0400 Subject: [PATCH 04/12] Remove lint checks These come transitively --- autodispose-coroutines-interop/build.gradle | 8 -------- 1 file changed, 8 deletions(-) diff --git a/autodispose-coroutines-interop/build.gradle b/autodispose-coroutines-interop/build.gradle index f0f9b6e83..0f24d41c8 100755 --- a/autodispose-coroutines-interop/build.gradle +++ b/autodispose-coroutines-interop/build.gradle @@ -15,22 +15,14 @@ */ plugins { - id 'com.android.lint' id 'ru.vyarus.animalsniffer' } -lintOptions { - abortOnError true - warningsAsErrors true -} - dependencies { api deps.rx.java api project(':autodispose') api deps.kotlin.coroutines - lintChecks project(':static-analysis:autodispose-lint') - testImplementation project(':test-utils') } From d186f8f63ae4f1793b3b4ac83805f43bcf587d55 Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sat, 14 Sep 2019 04:38:02 -0400 Subject: [PATCH 05/12] Implement tests --- .../AutoDisposeCoroutinesInterop.kt | 243 +++++++++++++++++- 1 file changed, 238 insertions(+), 5 deletions(-) diff --git a/autodispose-coroutines-interop/src/test/kotlin/com/uber/autodispose/coroutinesintrop/AutoDisposeCoroutinesInterop.kt b/autodispose-coroutines-interop/src/test/kotlin/com/uber/autodispose/coroutinesintrop/AutoDisposeCoroutinesInterop.kt index 3fbad1ae9..131a8a689 100644 --- a/autodispose-coroutines-interop/src/test/kotlin/com/uber/autodispose/coroutinesintrop/AutoDisposeCoroutinesInterop.kt +++ b/autodispose-coroutines-interop/src/test/kotlin/com/uber/autodispose/coroutinesintrop/AutoDisposeCoroutinesInterop.kt @@ -1,9 +1,242 @@ package com.uber.autodispose.coroutinesintrop +import com.google.common.truth.Truth.assertThat +import com.uber.autodispose.TestScopeProvider +import com.uber.autodispose.coroutinesinterop.asCompletable +import com.uber.autodispose.coroutinesinterop.asCoroutineScope +import com.uber.autodispose.coroutinesinterop.asScopeProvider +import com.uber.autodispose.coroutinesinterop.autoDispose +import com.uber.autodispose.test.RecordingObserver +import io.reactivex.Completable +import io.reactivex.processors.PublishProcessor +import io.reactivex.subjects.CompletableSubject +import io.reactivex.subjects.MaybeSubject +import io.reactivex.subjects.PublishSubject +import io.reactivex.subjects.SingleSubject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.cancel +import kotlinx.coroutines.ensureActive +import kotlinx.coroutines.isActive +import org.junit.Test + class AutoDisposeCoroutinesInterop { - // TODO Each rx type extension - // TODO ScopeProvider -> Scope - // TODO Completable -> Scope - // TODO Scope -> ScopeProvider - // TODO Scope -> Completable + + @Test + fun flowable() { + val job = Job() + val scope = CoroutineScope(job) + val source = PublishProcessor.create() + val o = source.autoDispose(scope).test() + o.assertSubscribed() + + assertThat(source.hasSubscribers()).isTrue() + scope.ensureActive() + + source.onNext(1) + o.assertValue(1) + + source.onNext(2) + + assertThat(source.hasSubscribers()).isTrue() + scope.ensureActive() + o.assertValues(1, 2) + + scope.cancel() + source.onNext(3) + + // Nothing new + o.assertValues(1, 2) + + // Unsubscribed + assertThat(source.hasSubscribers()).isFalse() + assertThat(scope.isActive).isFalse() + } + + @Test + fun observable() { + val job = Job() + val scope = CoroutineScope(job) + val o = RecordingObserver(LOGGER) + val source = PublishSubject.create() + source.autoDispose(scope).subscribe(o) + o.takeSubscribe() + + assertThat(source.hasObservers()).isTrue() + scope.ensureActive() + + source.onNext(1) + assertThat(o.takeNext()).isEqualTo(1) + + source.onNext(2) + + assertThat(source.hasObservers()).isTrue() + scope.ensureActive() + assertThat(o.takeNext()).isEqualTo(2) + + scope.cancel() + source.onNext(3) + + o.assertNoMoreEvents() + assertThat(source.hasObservers()).isFalse() + assertThat(scope.isActive).isFalse() + } + + @Test + fun maybe() { + val job = Job() + val scope = CoroutineScope(job) + val o = RecordingObserver(LOGGER) + val source = MaybeSubject.create() + source.autoDispose(scope).subscribe(o) + o.takeSubscribe() + + assertThat(source.hasObservers()).isTrue() + scope.ensureActive() + + scope.cancel() + + // All disposed + assertThat(source.hasObservers()).isFalse() + assertThat(scope.isActive).isFalse() + + // No one is listening + source.onSuccess(3) + o.assertNoMoreEvents() + } + + @Test + fun single() { + val job = Job() + val scope = CoroutineScope(job) + val o = RecordingObserver(LOGGER) + val source = SingleSubject.create() + source.autoDispose(scope).subscribe(o) + o.takeSubscribe() + + assertThat(source.hasObservers()).isTrue() + scope.ensureActive() + + scope.cancel() + + // All disposed + assertThat(source.hasObservers()).isFalse() + assertThat(scope.isActive).isFalse() + + // No one is listening + source.onSuccess(3) + o.assertNoMoreEvents() + } + + @Test + fun completable() { + val job = Job() + val scope = CoroutineScope(job) + val o = RecordingObserver(LOGGER) + val source = CompletableSubject.create() + source.autoDispose(scope).subscribe(o) + o.takeSubscribe() + + assertThat(source.hasObservers()).isTrue() + scope.ensureActive() + + scope.cancel() + + // All disposed + assertThat(source.hasObservers()).isFalse() + assertThat(scope.isActive).isFalse() + + // No one is listening + source.onComplete() + o.assertNoMoreEvents() + } + + @Test + fun scopeProviderToScope() { + val provider = TestScopeProvider.create() + val job = Job() + val scope = provider.asCoroutineScope(job) + scope.ensureActive() + assertThat(job.isActive).isTrue() + provider.emit() + assertThat(scope.isActive).isFalse() + assertThat(job.isActive).isFalse() + } + + @Test + fun completableToScope() { + val completableSubject = CompletableSubject.create() + val job = Job() + val scope = completableSubject.asCoroutineScope(job) + scope.ensureActive() + assertThat(job.isActive).isTrue() + completableSubject.onComplete() + assertThat(scope.isActive).isFalse() + assertThat(job.isActive).isFalse() + } + + @Test + fun completableToScopeError() { + val completableSubject = CompletableSubject.create() + val job = Job() + val scope = completableSubject.asCoroutineScope(job) + scope.ensureActive() + assertThat(job.isActive).isTrue() + val error = RuntimeException() + completableSubject.onError(error) + assertThat(scope.isActive).isFalse() + assertThat(job.isActive).isFalse() + } + + @Test + fun scopeToProvider() { + val job = Job() + val scope = CoroutineScope(job) + val provider = scope.asScopeProvider() + val providerObserver = Completable.wrap(provider.requestScope()).test() + providerObserver.assertNotTerminated() + scope.cancel() + providerObserver.assertComplete() + } + + @Test + fun scopeToProviderError() { + val job = Job() + val scope = CoroutineScope(job) + val provider = scope.asScopeProvider() + val providerObserver = Completable.wrap(provider.requestScope()).test() + providerObserver.assertNotTerminated() + val error = RuntimeException() + scope.cancel("OnError", error) + providerObserver.assertComplete() + } + + @Test + fun scopeToCompletable() { + val job = Job() + val scope = CoroutineScope(job) + val completable = scope.asCompletable() + val observer = completable.test() + observer.assertNotTerminated() + scope.cancel() + observer.assertComplete() + } + + @Test + fun scopeToCompletableError() { + val job = Job() + val scope = CoroutineScope(job) + val completable = scope.asCompletable() + val observer = completable.test() + observer.assertNotTerminated() + val error = RuntimeException() + scope.cancel("OnError", error) + observer.assertComplete() + } + + companion object { + private val LOGGER = { message: String -> + println(AutoDisposeCoroutinesInterop::class.java.simpleName + ": " + message) + } + } } From 5782de472bf4f8aaaf964b1401ca397e09e7d41f Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sat, 14 Sep 2019 04:45:53 -0400 Subject: [PATCH 06/12] Add default to asCoroutineScope instead --- .../AutoDisposeCoroutinesInterop.kt | 10 +++++++--- .../AutoDisposeCoroutinesInterop.kt | 15 +++------------ 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/autodispose-coroutines-interop/src/main/kotlin/com/uber/autodispose/coroutinesinterop/AutoDisposeCoroutinesInterop.kt b/autodispose-coroutines-interop/src/main/kotlin/com/uber/autodispose/coroutinesinterop/AutoDisposeCoroutinesInterop.kt index 752af0473..438e4c0b7 100644 --- a/autodispose-coroutines-interop/src/main/kotlin/com/uber/autodispose/coroutinesinterop/AutoDisposeCoroutinesInterop.kt +++ b/autodispose-coroutines-interop/src/main/kotlin/com/uber/autodispose/coroutinesinterop/AutoDisposeCoroutinesInterop.kt @@ -90,19 +90,23 @@ private fun CoroutineScope.asUndeferredCompletable(): Completable { } /** + * @param context an optional [CoroutineContext] to use for this scope. Default is a new + * [SupervisorJob]. * @return a [CoroutineScope] representation of this [ScopeProvider]. This scope will cancel when * [this] scope provider completes. */ -fun ScopeProvider.asCoroutineScope(context: CoroutineContext): CoroutineScope { +fun ScopeProvider.asCoroutineScope(context: CoroutineContext = SupervisorJob()): CoroutineScope { return requestScope().asCoroutineScope(context) } /** + * @param context an optional [CoroutineContext] to use for this scope. Default is a new + * [SupervisorJob]. * @return a [CoroutineScope] representation of this [CompletableSource]. This scope will cancel * when [this] scope provider completes. */ -fun CompletableSource.asCoroutineScope(context: CoroutineContext): CoroutineScope { - val scope = CoroutineScope(SupervisorJob() + context) +fun CompletableSource.asCoroutineScope(context: CoroutineContext = SupervisorJob()): CoroutineScope { + val scope = CoroutineScope(context) // Bind to the scope, so if the scope is manually canceled before our scope provider emits, we // clean up here. diff --git a/autodispose-coroutines-interop/src/test/kotlin/com/uber/autodispose/coroutinesintrop/AutoDisposeCoroutinesInterop.kt b/autodispose-coroutines-interop/src/test/kotlin/com/uber/autodispose/coroutinesintrop/AutoDisposeCoroutinesInterop.kt index 131a8a689..bc4c7872c 100644 --- a/autodispose-coroutines-interop/src/test/kotlin/com/uber/autodispose/coroutinesintrop/AutoDisposeCoroutinesInterop.kt +++ b/autodispose-coroutines-interop/src/test/kotlin/com/uber/autodispose/coroutinesintrop/AutoDisposeCoroutinesInterop.kt @@ -154,38 +154,29 @@ class AutoDisposeCoroutinesInterop { @Test fun scopeProviderToScope() { val provider = TestScopeProvider.create() - val job = Job() - val scope = provider.asCoroutineScope(job) + val scope = provider.asCoroutineScope() scope.ensureActive() - assertThat(job.isActive).isTrue() provider.emit() assertThat(scope.isActive).isFalse() - assertThat(job.isActive).isFalse() } @Test fun completableToScope() { val completableSubject = CompletableSubject.create() - val job = Job() - val scope = completableSubject.asCoroutineScope(job) + val scope = completableSubject.asCoroutineScope() scope.ensureActive() - assertThat(job.isActive).isTrue() completableSubject.onComplete() assertThat(scope.isActive).isFalse() - assertThat(job.isActive).isFalse() } @Test fun completableToScopeError() { val completableSubject = CompletableSubject.create() - val job = Job() - val scope = completableSubject.asCoroutineScope(job) + val scope = completableSubject.asCoroutineScope() scope.ensureActive() - assertThat(job.isActive).isTrue() val error = RuntimeException() completableSubject.onError(error) assertThat(scope.isActive).isFalse() - assertThat(job.isActive).isFalse() } @Test From 938179bd96a56c9fd18cd1dc106fdd29512e1f54 Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sat, 14 Sep 2019 05:00:33 -0400 Subject: [PATCH 07/12] Implement asCoroutineScope with bodies Super nice high order function with receiver API --- .../AutoDisposeCoroutinesInterop.kt | 68 +++++++ .../AutoDisposeCoroutinesInterop.kt | 172 +++++++++--------- 2 files changed, 156 insertions(+), 84 deletions(-) diff --git a/autodispose-coroutines-interop/src/main/kotlin/com/uber/autodispose/coroutinesinterop/AutoDisposeCoroutinesInterop.kt b/autodispose-coroutines-interop/src/main/kotlin/com/uber/autodispose/coroutinesinterop/AutoDisposeCoroutinesInterop.kt index 438e4c0b7..c798e909f 100644 --- a/autodispose-coroutines-interop/src/main/kotlin/com/uber/autodispose/coroutinesinterop/AutoDisposeCoroutinesInterop.kt +++ b/autodispose-coroutines-interop/src/main/kotlin/com/uber/autodispose/coroutinesinterop/AutoDisposeCoroutinesInterop.kt @@ -116,3 +116,71 @@ fun CompletableSource.asCoroutineScope(context: CoroutineContext = SupervisorJob return scope } + +/** + * @param context an optional [CoroutineContext] to use for this scope. Default is a new + * [SupervisorJob]. + * @param body a body with [AutoDisposeCoroutineScope] available. + * @return a [CoroutineScope] representation of this [CompletableSource]. This scope will cancel + * when [this] scope provider completes. + */ +fun ScopeProvider.asCoroutineScope( + context: CoroutineContext = SupervisorJob(), + body: AutoDisposeCoroutineScope.() -> Unit +) { + requestScope().asCoroutineScope(context, body) +} + +/** + * @param context an optional [CoroutineContext] to use for this scope. Default is a new + * [SupervisorJob]. + * @param body a body with [AutoDisposeCoroutineScope] available. + * @return a [CoroutineScope] representation of this [CompletableSource]. This scope will cancel + * when [this] scope provider completes. + */ +fun CompletableSource.asCoroutineScope( + context: CoroutineContext = SupervisorJob(), + body: AutoDisposeCoroutineScope.() -> Unit +) { + val scope = asCoroutineScope(context) + RealAutoDisposeCoroutineScope(scope).body() +} + +internal class RealAutoDisposeCoroutineScope(private val scope: CoroutineScope) : AutoDisposeCoroutineScope { + override fun Flowable.autoDispose(): FlowableSubscribeProxy { + return autoDispose(scope.asScopeProvider()) + } + + override fun Observable.autoDispose(): ObservableSubscribeProxy { + return autoDispose(scope.asScopeProvider()) + } + + override fun Single.autoDispose(): SingleSubscribeProxy { + return autoDispose(scope.asScopeProvider()) + } + + override fun Maybe.autoDispose(): MaybeSubscribeProxy { + return autoDispose(scope.asScopeProvider()) + } + + override fun Completable.autoDispose(): CompletableSubscribeProxy { + return autoDispose(scope.asScopeProvider()) + } +} + +interface AutoDisposeCoroutineScope { + /** Extension that proxies to the normal [autoDispose] extension function with a [ScopeProvider]. */ + fun Flowable.autoDispose(): FlowableSubscribeProxy + + /** Extension that proxies to the normal [autoDispose] extension function with a [ScopeProvider]. */ + fun Observable.autoDispose(): ObservableSubscribeProxy + + /** Extension that proxies to the normal [autoDispose] extension function with a [ScopeProvider]. */ + fun Single.autoDispose(): SingleSubscribeProxy + + /** Extension that proxies to the normal [autoDispose] extension function with a [ScopeProvider]. */ + fun Maybe.autoDispose(): MaybeSubscribeProxy + + /** Extension that proxies to the normal [autoDispose] extension function with a [ScopeProvider]. */ + fun Completable.autoDispose(): CompletableSubscribeProxy +} diff --git a/autodispose-coroutines-interop/src/test/kotlin/com/uber/autodispose/coroutinesintrop/AutoDisposeCoroutinesInterop.kt b/autodispose-coroutines-interop/src/test/kotlin/com/uber/autodispose/coroutinesintrop/AutoDisposeCoroutinesInterop.kt index bc4c7872c..f31570e02 100644 --- a/autodispose-coroutines-interop/src/test/kotlin/com/uber/autodispose/coroutinesintrop/AutoDisposeCoroutinesInterop.kt +++ b/autodispose-coroutines-interop/src/test/kotlin/com/uber/autodispose/coroutinesintrop/AutoDisposeCoroutinesInterop.kt @@ -5,7 +5,6 @@ import com.uber.autodispose.TestScopeProvider import com.uber.autodispose.coroutinesinterop.asCompletable import com.uber.autodispose.coroutinesinterop.asCoroutineScope import com.uber.autodispose.coroutinesinterop.asScopeProvider -import com.uber.autodispose.coroutinesinterop.autoDispose import com.uber.autodispose.test.RecordingObserver import io.reactivex.Completable import io.reactivex.processors.PublishProcessor @@ -24,131 +23,136 @@ class AutoDisposeCoroutinesInterop { @Test fun flowable() { - val job = Job() - val scope = CoroutineScope(job) + val scopeSource = CompletableSubject.create() val source = PublishProcessor.create() - val o = source.autoDispose(scope).test() - o.assertSubscribed() + scopeSource.asCoroutineScope { + val o = source.autoDispose().test() + o.assertSubscribed() - assertThat(source.hasSubscribers()).isTrue() - scope.ensureActive() + assertThat(source.hasSubscribers()).isTrue() + assertThat(scopeSource.hasObservers()).isTrue() - source.onNext(1) - o.assertValue(1) + source.onNext(1) + o.assertValue(1) - source.onNext(2) + source.onNext(2) - assertThat(source.hasSubscribers()).isTrue() - scope.ensureActive() - o.assertValues(1, 2) + assertThat(source.hasSubscribers()).isTrue() + assertThat(scopeSource.hasObservers()).isTrue() + o.assertValues(1, 2) - scope.cancel() - source.onNext(3) + scopeSource.onComplete() + source.onNext(3) - // Nothing new - o.assertValues(1, 2) + // Nothing new + o.assertValues(1, 2) - // Unsubscribed - assertThat(source.hasSubscribers()).isFalse() - assertThat(scope.isActive).isFalse() + // Unsubscribed + assertThat(source.hasSubscribers()).isFalse() + assertThat(scopeSource.hasObservers()).isFalse() + } } @Test fun observable() { - val job = Job() - val scope = CoroutineScope(job) - val o = RecordingObserver(LOGGER) - val source = PublishSubject.create() - source.autoDispose(scope).subscribe(o) - o.takeSubscribe() + val scopeSource = CompletableSubject.create() + scopeSource.asCoroutineScope { + val o = RecordingObserver(LOGGER) + val source = PublishSubject.create() + source.autoDispose().subscribe(o) + o.takeSubscribe() - assertThat(source.hasObservers()).isTrue() - scope.ensureActive() + assertThat(source.hasObservers()).isTrue() + assertThat(scopeSource.hasObservers()) - source.onNext(1) - assertThat(o.takeNext()).isEqualTo(1) + source.onNext(1) + assertThat(o.takeNext()).isEqualTo(1) - source.onNext(2) + source.onNext(2) - assertThat(source.hasObservers()).isTrue() - scope.ensureActive() - assertThat(o.takeNext()).isEqualTo(2) + assertThat(source.hasObservers()).isTrue() + assertThat(scopeSource.hasObservers()) + assertThat(o.takeNext()).isEqualTo(2) - scope.cancel() - source.onNext(3) + scopeSource.onComplete() + source.onNext(3) - o.assertNoMoreEvents() - assertThat(source.hasObservers()).isFalse() - assertThat(scope.isActive).isFalse() + o.assertNoMoreEvents() + assertThat(source.hasObservers()).isFalse() + assertThat(scopeSource.hasObservers()).isFalse() + } } @Test fun maybe() { - val job = Job() - val scope = CoroutineScope(job) - val o = RecordingObserver(LOGGER) - val source = MaybeSubject.create() - source.autoDispose(scope).subscribe(o) - o.takeSubscribe() + val scopeSource = CompletableSubject.create() + scopeSource.asCoroutineScope { + val o = RecordingObserver(LOGGER) + val source = MaybeSubject.create() + source.autoDispose().subscribe(o) + o.takeSubscribe() - assertThat(source.hasObservers()).isTrue() - scope.ensureActive() + assertThat(source.hasObservers()).isTrue() + assertThat(scopeSource.hasObservers()) - scope.cancel() + scopeSource.onComplete() - // All disposed - assertThat(source.hasObservers()).isFalse() - assertThat(scope.isActive).isFalse() + // All disposed + assertThat(source.hasObservers()).isFalse() + assertThat(scopeSource.hasObservers()).isFalse() - // No one is listening - source.onSuccess(3) - o.assertNoMoreEvents() + // No one is listening + source.onSuccess(3) + o.assertNoMoreEvents() + } } @Test fun single() { - val job = Job() - val scope = CoroutineScope(job) - val o = RecordingObserver(LOGGER) - val source = SingleSubject.create() - source.autoDispose(scope).subscribe(o) - o.takeSubscribe() + val scopeSource = CompletableSubject.create() + scopeSource.asCoroutineScope { + val o = RecordingObserver(LOGGER) + val source = SingleSubject.create() + source.autoDispose().subscribe(o) + o.takeSubscribe() - assertThat(source.hasObservers()).isTrue() - scope.ensureActive() + assertThat(source.hasObservers()).isTrue() + assertThat(scopeSource.hasObservers()) - scope.cancel() + scopeSource.onComplete() - // All disposed - assertThat(source.hasObservers()).isFalse() - assertThat(scope.isActive).isFalse() + // All disposed + assertThat(source.hasObservers()).isFalse() + assertThat(scopeSource.hasObservers()).isFalse() - // No one is listening - source.onSuccess(3) - o.assertNoMoreEvents() + // No one is listening + source.onSuccess(3) + o.assertNoMoreEvents() + } } @Test fun completable() { - val job = Job() - val scope = CoroutineScope(job) - val o = RecordingObserver(LOGGER) - val source = CompletableSubject.create() - source.autoDispose(scope).subscribe(o) - o.takeSubscribe() + val scopeSource = CompletableSubject.create() + scopeSource.asCoroutineScope { + val o = RecordingObserver(LOGGER) + val source = CompletableSubject.create() + source.autoDispose().subscribe(o) + o.takeSubscribe() - assertThat(source.hasObservers()).isTrue() - scope.ensureActive() + assertThat(source.hasObservers()).isTrue() + assertThat(scopeSource.hasObservers()) - scope.cancel() + scopeSource.onComplete() - // All disposed - assertThat(source.hasObservers()).isFalse() - assertThat(scope.isActive).isFalse() + // All disposed + assertThat(source.hasObservers()).isFalse() + assertThat(scopeSource.hasObservers()).isFalse() - // No one is listening - source.onComplete() - o.assertNoMoreEvents() + // No one is listening + source.onComplete() + o.assertNoMoreEvents() + } } @Test From 109deeabde5de6f5a85a277b7d4fc7900e0e71b7 Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sat, 14 Sep 2019 05:01:45 -0400 Subject: [PATCH 08/12] Spotless --- .../AutoDisposeCoroutinesInterop.kt | 10 +++++----- .../AutoDisposeCoroutinesInterop.kt | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/autodispose-coroutines-interop/src/main/kotlin/com/uber/autodispose/coroutinesinterop/AutoDisposeCoroutinesInterop.kt b/autodispose-coroutines-interop/src/main/kotlin/com/uber/autodispose/coroutinesinterop/AutoDisposeCoroutinesInterop.kt index c798e909f..592654de7 100644 --- a/autodispose-coroutines-interop/src/main/kotlin/com/uber/autodispose/coroutinesinterop/AutoDisposeCoroutinesInterop.kt +++ b/autodispose-coroutines-interop/src/main/kotlin/com/uber/autodispose/coroutinesinterop/AutoDisposeCoroutinesInterop.kt @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -125,8 +125,8 @@ fun CompletableSource.asCoroutineScope(context: CoroutineContext = SupervisorJob * when [this] scope provider completes. */ fun ScopeProvider.asCoroutineScope( - context: CoroutineContext = SupervisorJob(), - body: AutoDisposeCoroutineScope.() -> Unit + context: CoroutineContext = SupervisorJob(), + body: AutoDisposeCoroutineScope.() -> Unit ) { requestScope().asCoroutineScope(context, body) } @@ -139,8 +139,8 @@ fun ScopeProvider.asCoroutineScope( * when [this] scope provider completes. */ fun CompletableSource.asCoroutineScope( - context: CoroutineContext = SupervisorJob(), - body: AutoDisposeCoroutineScope.() -> Unit + context: CoroutineContext = SupervisorJob(), + body: AutoDisposeCoroutineScope.() -> Unit ) { val scope = asCoroutineScope(context) RealAutoDisposeCoroutineScope(scope).body() diff --git a/autodispose-coroutines-interop/src/test/kotlin/com/uber/autodispose/coroutinesintrop/AutoDisposeCoroutinesInterop.kt b/autodispose-coroutines-interop/src/test/kotlin/com/uber/autodispose/coroutinesintrop/AutoDisposeCoroutinesInterop.kt index f31570e02..2a5ebfac8 100644 --- a/autodispose-coroutines-interop/src/test/kotlin/com/uber/autodispose/coroutinesintrop/AutoDisposeCoroutinesInterop.kt +++ b/autodispose-coroutines-interop/src/test/kotlin/com/uber/autodispose/coroutinesintrop/AutoDisposeCoroutinesInterop.kt @@ -1,3 +1,18 @@ +/* + * Copyright (C) 2019. Uber Technologies + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.uber.autodispose.coroutinesintrop import com.google.common.truth.Truth.assertThat From d9c62958e137522c3d274f967b1c0262f36f42dd Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sat, 14 Sep 2019 05:04:34 -0400 Subject: [PATCH 09/12] Add in README --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 399851725..d4be806b9 100755 --- a/README.md +++ b/README.md @@ -188,6 +188,9 @@ use D8 (Android Gradle Plugin 3.2+) or desugar as needed (depending on the build Kotlin extensions are bundled with almost every artifact. +For coroutines - there is an `autodispose-coroutines-interop` artifact for interoperability between +`CoroutineScope` and `ScopeProvider`/`Completable` types. + ##### RxLifecycle As of 0.4.0 there is an RxLifecycle interop module under `autodispose-rxlifecycle`. This is for interop From d1483023a28f82a2b813fbe60fef4e2bbd84f917 Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sat, 14 Sep 2019 17:25:40 -0400 Subject: [PATCH 10/12] Make RealAutoDisposeCoroutineScope private --- .../AutoDisposeCoroutinesInterop.kt | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/autodispose-coroutines-interop/src/main/kotlin/com/uber/autodispose/coroutinesinterop/AutoDisposeCoroutinesInterop.kt b/autodispose-coroutines-interop/src/main/kotlin/com/uber/autodispose/coroutinesinterop/AutoDisposeCoroutinesInterop.kt index 592654de7..1511e87da 100644 --- a/autodispose-coroutines-interop/src/main/kotlin/com/uber/autodispose/coroutinesinterop/AutoDisposeCoroutinesInterop.kt +++ b/autodispose-coroutines-interop/src/main/kotlin/com/uber/autodispose/coroutinesinterop/AutoDisposeCoroutinesInterop.kt @@ -146,7 +146,24 @@ fun CompletableSource.asCoroutineScope( RealAutoDisposeCoroutineScope(scope).body() } -internal class RealAutoDisposeCoroutineScope(private val scope: CoroutineScope) : AutoDisposeCoroutineScope { +interface AutoDisposeCoroutineScope { + /** Extension that proxies to the normal [autoDispose] extension function with a [ScopeProvider]. */ + fun Flowable.autoDispose(): FlowableSubscribeProxy + + /** Extension that proxies to the normal [autoDispose] extension function with a [ScopeProvider]. */ + fun Observable.autoDispose(): ObservableSubscribeProxy + + /** Extension that proxies to the normal [autoDispose] extension function with a [ScopeProvider]. */ + fun Single.autoDispose(): SingleSubscribeProxy + + /** Extension that proxies to the normal [autoDispose] extension function with a [ScopeProvider]. */ + fun Maybe.autoDispose(): MaybeSubscribeProxy + + /** Extension that proxies to the normal [autoDispose] extension function with a [ScopeProvider]. */ + fun Completable.autoDispose(): CompletableSubscribeProxy +} + +private class RealAutoDisposeCoroutineScope(private val scope: CoroutineScope) : AutoDisposeCoroutineScope { override fun Flowable.autoDispose(): FlowableSubscribeProxy { return autoDispose(scope.asScopeProvider()) } @@ -167,20 +184,3 @@ internal class RealAutoDisposeCoroutineScope(private val scope: CoroutineScope) return autoDispose(scope.asScopeProvider()) } } - -interface AutoDisposeCoroutineScope { - /** Extension that proxies to the normal [autoDispose] extension function with a [ScopeProvider]. */ - fun Flowable.autoDispose(): FlowableSubscribeProxy - - /** Extension that proxies to the normal [autoDispose] extension function with a [ScopeProvider]. */ - fun Observable.autoDispose(): ObservableSubscribeProxy - - /** Extension that proxies to the normal [autoDispose] extension function with a [ScopeProvider]. */ - fun Single.autoDispose(): SingleSubscribeProxy - - /** Extension that proxies to the normal [autoDispose] extension function with a [ScopeProvider]. */ - fun Maybe.autoDispose(): MaybeSubscribeProxy - - /** Extension that proxies to the normal [autoDispose] extension function with a [ScopeProvider]. */ - fun Completable.autoDispose(): CompletableSubscribeProxy -} From 3279d21c306c3421b3beb8e9761b1af2edd67c18 Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sat, 14 Sep 2019 18:38:20 -0400 Subject: [PATCH 11/12] Revert AutoDisposeContext, will move to another PR --- .../AutoDisposeCoroutinesInterop.kt | 68 ------- .../AutoDisposeCoroutinesInterop.kt | 172 +++++++++--------- 2 files changed, 84 insertions(+), 156 deletions(-) diff --git a/autodispose-coroutines-interop/src/main/kotlin/com/uber/autodispose/coroutinesinterop/AutoDisposeCoroutinesInterop.kt b/autodispose-coroutines-interop/src/main/kotlin/com/uber/autodispose/coroutinesinterop/AutoDisposeCoroutinesInterop.kt index 1511e87da..320017d64 100644 --- a/autodispose-coroutines-interop/src/main/kotlin/com/uber/autodispose/coroutinesinterop/AutoDisposeCoroutinesInterop.kt +++ b/autodispose-coroutines-interop/src/main/kotlin/com/uber/autodispose/coroutinesinterop/AutoDisposeCoroutinesInterop.kt @@ -116,71 +116,3 @@ fun CompletableSource.asCoroutineScope(context: CoroutineContext = SupervisorJob return scope } - -/** - * @param context an optional [CoroutineContext] to use for this scope. Default is a new - * [SupervisorJob]. - * @param body a body with [AutoDisposeCoroutineScope] available. - * @return a [CoroutineScope] representation of this [CompletableSource]. This scope will cancel - * when [this] scope provider completes. - */ -fun ScopeProvider.asCoroutineScope( - context: CoroutineContext = SupervisorJob(), - body: AutoDisposeCoroutineScope.() -> Unit -) { - requestScope().asCoroutineScope(context, body) -} - -/** - * @param context an optional [CoroutineContext] to use for this scope. Default is a new - * [SupervisorJob]. - * @param body a body with [AutoDisposeCoroutineScope] available. - * @return a [CoroutineScope] representation of this [CompletableSource]. This scope will cancel - * when [this] scope provider completes. - */ -fun CompletableSource.asCoroutineScope( - context: CoroutineContext = SupervisorJob(), - body: AutoDisposeCoroutineScope.() -> Unit -) { - val scope = asCoroutineScope(context) - RealAutoDisposeCoroutineScope(scope).body() -} - -interface AutoDisposeCoroutineScope { - /** Extension that proxies to the normal [autoDispose] extension function with a [ScopeProvider]. */ - fun Flowable.autoDispose(): FlowableSubscribeProxy - - /** Extension that proxies to the normal [autoDispose] extension function with a [ScopeProvider]. */ - fun Observable.autoDispose(): ObservableSubscribeProxy - - /** Extension that proxies to the normal [autoDispose] extension function with a [ScopeProvider]. */ - fun Single.autoDispose(): SingleSubscribeProxy - - /** Extension that proxies to the normal [autoDispose] extension function with a [ScopeProvider]. */ - fun Maybe.autoDispose(): MaybeSubscribeProxy - - /** Extension that proxies to the normal [autoDispose] extension function with a [ScopeProvider]. */ - fun Completable.autoDispose(): CompletableSubscribeProxy -} - -private class RealAutoDisposeCoroutineScope(private val scope: CoroutineScope) : AutoDisposeCoroutineScope { - override fun Flowable.autoDispose(): FlowableSubscribeProxy { - return autoDispose(scope.asScopeProvider()) - } - - override fun Observable.autoDispose(): ObservableSubscribeProxy { - return autoDispose(scope.asScopeProvider()) - } - - override fun Single.autoDispose(): SingleSubscribeProxy { - return autoDispose(scope.asScopeProvider()) - } - - override fun Maybe.autoDispose(): MaybeSubscribeProxy { - return autoDispose(scope.asScopeProvider()) - } - - override fun Completable.autoDispose(): CompletableSubscribeProxy { - return autoDispose(scope.asScopeProvider()) - } -} diff --git a/autodispose-coroutines-interop/src/test/kotlin/com/uber/autodispose/coroutinesintrop/AutoDisposeCoroutinesInterop.kt b/autodispose-coroutines-interop/src/test/kotlin/com/uber/autodispose/coroutinesintrop/AutoDisposeCoroutinesInterop.kt index 2a5ebfac8..fa37c6cb4 100644 --- a/autodispose-coroutines-interop/src/test/kotlin/com/uber/autodispose/coroutinesintrop/AutoDisposeCoroutinesInterop.kt +++ b/autodispose-coroutines-interop/src/test/kotlin/com/uber/autodispose/coroutinesintrop/AutoDisposeCoroutinesInterop.kt @@ -20,6 +20,7 @@ import com.uber.autodispose.TestScopeProvider import com.uber.autodispose.coroutinesinterop.asCompletable import com.uber.autodispose.coroutinesinterop.asCoroutineScope import com.uber.autodispose.coroutinesinterop.asScopeProvider +import com.uber.autodispose.coroutinesinterop.autoDispose import com.uber.autodispose.test.RecordingObserver import io.reactivex.Completable import io.reactivex.processors.PublishProcessor @@ -38,136 +39,131 @@ class AutoDisposeCoroutinesInterop { @Test fun flowable() { - val scopeSource = CompletableSubject.create() + val job = Job() + val scope = CoroutineScope(job) val source = PublishProcessor.create() - scopeSource.asCoroutineScope { - val o = source.autoDispose().test() - o.assertSubscribed() + val o = source.autoDispose(scope).test() + o.assertSubscribed() - assertThat(source.hasSubscribers()).isTrue() - assertThat(scopeSource.hasObservers()).isTrue() + assertThat(source.hasSubscribers()).isTrue() + scope.ensureActive() - source.onNext(1) - o.assertValue(1) + source.onNext(1) + o.assertValue(1) - source.onNext(2) + source.onNext(2) - assertThat(source.hasSubscribers()).isTrue() - assertThat(scopeSource.hasObservers()).isTrue() - o.assertValues(1, 2) + assertThat(source.hasSubscribers()).isTrue() + scope.ensureActive() + o.assertValues(1, 2) - scopeSource.onComplete() - source.onNext(3) + scope.cancel() + source.onNext(3) - // Nothing new - o.assertValues(1, 2) + // Nothing new + o.assertValues(1, 2) - // Unsubscribed - assertThat(source.hasSubscribers()).isFalse() - assertThat(scopeSource.hasObservers()).isFalse() - } + // Unsubscribed + assertThat(source.hasSubscribers()).isFalse() + assertThat(scope.isActive).isFalse() } @Test fun observable() { - val scopeSource = CompletableSubject.create() - scopeSource.asCoroutineScope { - val o = RecordingObserver(LOGGER) - val source = PublishSubject.create() - source.autoDispose().subscribe(o) - o.takeSubscribe() + val job = Job() + val scope = CoroutineScope(job) + val o = RecordingObserver(LOGGER) + val source = PublishSubject.create() + source.autoDispose(scope).subscribe(o) + o.takeSubscribe() - assertThat(source.hasObservers()).isTrue() - assertThat(scopeSource.hasObservers()) + assertThat(source.hasObservers()).isTrue() + scope.ensureActive() - source.onNext(1) - assertThat(o.takeNext()).isEqualTo(1) + source.onNext(1) + assertThat(o.takeNext()).isEqualTo(1) - source.onNext(2) + source.onNext(2) - assertThat(source.hasObservers()).isTrue() - assertThat(scopeSource.hasObservers()) - assertThat(o.takeNext()).isEqualTo(2) + assertThat(source.hasObservers()).isTrue() + scope.ensureActive() + assertThat(o.takeNext()).isEqualTo(2) - scopeSource.onComplete() - source.onNext(3) + scope.cancel() + source.onNext(3) - o.assertNoMoreEvents() - assertThat(source.hasObservers()).isFalse() - assertThat(scopeSource.hasObservers()).isFalse() - } + o.assertNoMoreEvents() + assertThat(source.hasObservers()).isFalse() + assertThat(scope.isActive).isFalse() } @Test fun maybe() { - val scopeSource = CompletableSubject.create() - scopeSource.asCoroutineScope { - val o = RecordingObserver(LOGGER) - val source = MaybeSubject.create() - source.autoDispose().subscribe(o) - o.takeSubscribe() + val job = Job() + val scope = CoroutineScope(job) + val o = RecordingObserver(LOGGER) + val source = MaybeSubject.create() + source.autoDispose(scope).subscribe(o) + o.takeSubscribe() - assertThat(source.hasObservers()).isTrue() - assertThat(scopeSource.hasObservers()) + assertThat(source.hasObservers()).isTrue() + scope.ensureActive() - scopeSource.onComplete() + scope.cancel() - // All disposed - assertThat(source.hasObservers()).isFalse() - assertThat(scopeSource.hasObservers()).isFalse() + // All disposed + assertThat(source.hasObservers()).isFalse() + assertThat(scope.isActive).isFalse() - // No one is listening - source.onSuccess(3) - o.assertNoMoreEvents() - } + // No one is listening + source.onSuccess(3) + o.assertNoMoreEvents() } @Test fun single() { - val scopeSource = CompletableSubject.create() - scopeSource.asCoroutineScope { - val o = RecordingObserver(LOGGER) - val source = SingleSubject.create() - source.autoDispose().subscribe(o) - o.takeSubscribe() + val job = Job() + val scope = CoroutineScope(job) + val o = RecordingObserver(LOGGER) + val source = SingleSubject.create() + source.autoDispose(scope).subscribe(o) + o.takeSubscribe() - assertThat(source.hasObservers()).isTrue() - assertThat(scopeSource.hasObservers()) + assertThat(source.hasObservers()).isTrue() + scope.ensureActive() - scopeSource.onComplete() + scope.cancel() - // All disposed - assertThat(source.hasObservers()).isFalse() - assertThat(scopeSource.hasObservers()).isFalse() + // All disposed + assertThat(source.hasObservers()).isFalse() + assertThat(scope.isActive).isFalse() - // No one is listening - source.onSuccess(3) - o.assertNoMoreEvents() - } + // No one is listening + source.onSuccess(3) + o.assertNoMoreEvents() } @Test fun completable() { - val scopeSource = CompletableSubject.create() - scopeSource.asCoroutineScope { - val o = RecordingObserver(LOGGER) - val source = CompletableSubject.create() - source.autoDispose().subscribe(o) - o.takeSubscribe() + val job = Job() + val scope = CoroutineScope(job) + val o = RecordingObserver(LOGGER) + val source = CompletableSubject.create() + source.autoDispose(scope).subscribe(o) + o.takeSubscribe() - assertThat(source.hasObservers()).isTrue() - assertThat(scopeSource.hasObservers()) + assertThat(source.hasObservers()).isTrue() + scope.ensureActive() - scopeSource.onComplete() + scope.cancel() - // All disposed - assertThat(source.hasObservers()).isFalse() - assertThat(scopeSource.hasObservers()).isFalse() + // All disposed + assertThat(source.hasObservers()).isFalse() + assertThat(scope.isActive).isFalse() - // No one is listening - source.onComplete() - o.assertNoMoreEvents() - } + // No one is listening + source.onComplete() + o.assertNoMoreEvents() } @Test From d34efded70da7d3ee03a17ff38f4cfe7020a8105 Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Sat, 14 Sep 2019 18:38:33 -0400 Subject: [PATCH 12/12] Rename to AutoDisposeCoroutinesInteropTest --- ...routinesInterop.kt => AutoDisposeCoroutinesInteropTest.kt} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename autodispose-coroutines-interop/src/test/kotlin/com/uber/autodispose/coroutinesintrop/{AutoDisposeCoroutinesInterop.kt => AutoDisposeCoroutinesInteropTest.kt} (98%) diff --git a/autodispose-coroutines-interop/src/test/kotlin/com/uber/autodispose/coroutinesintrop/AutoDisposeCoroutinesInterop.kt b/autodispose-coroutines-interop/src/test/kotlin/com/uber/autodispose/coroutinesintrop/AutoDisposeCoroutinesInteropTest.kt similarity index 98% rename from autodispose-coroutines-interop/src/test/kotlin/com/uber/autodispose/coroutinesintrop/AutoDisposeCoroutinesInterop.kt rename to autodispose-coroutines-interop/src/test/kotlin/com/uber/autodispose/coroutinesintrop/AutoDisposeCoroutinesInteropTest.kt index fa37c6cb4..ac1c1be78 100644 --- a/autodispose-coroutines-interop/src/test/kotlin/com/uber/autodispose/coroutinesintrop/AutoDisposeCoroutinesInterop.kt +++ b/autodispose-coroutines-interop/src/test/kotlin/com/uber/autodispose/coroutinesintrop/AutoDisposeCoroutinesInteropTest.kt @@ -35,7 +35,7 @@ import kotlinx.coroutines.ensureActive import kotlinx.coroutines.isActive import org.junit.Test -class AutoDisposeCoroutinesInterop { +class AutoDisposeCoroutinesInteropTest { @Test fun flowable() { @@ -242,7 +242,7 @@ class AutoDisposeCoroutinesInterop { companion object { private val LOGGER = { message: String -> - println(AutoDisposeCoroutinesInterop::class.java.simpleName + ": " + message) + println(AutoDisposeCoroutinesInteropTest::class.java.simpleName + ": " + message) } } }