diff --git a/arrow-libs/core/arrow-autoclose/src/commonTest/kotlin/arrow/AutoCloseTest.kt b/arrow-libs/core/arrow-autoclose/src/commonTest/kotlin/arrow/AutoCloseTest.kt index 9a9b4e3bedd..7019f7cc8cc 100644 --- a/arrow-libs/core/arrow-autoclose/src/commonTest/kotlin/arrow/AutoCloseTest.kt +++ b/arrow-libs/core/arrow-autoclose/src/commonTest/kotlin/arrow/AutoCloseTest.kt @@ -3,6 +3,7 @@ package arrow import arrow.atomic.AtomicBoolean import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.shouldBe +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.toList import kotlinx.coroutines.test.runTest @@ -13,50 +14,50 @@ class AutoCloseTest { @Test fun canInstallResource() = runTest { - var throwable: Throwable? = RuntimeException("Dummy exception") - var wasActive = false + val promise = CompletableDeferred() + val wasActive = CompletableDeferred() val res = Resource() autoCloseScope { val r = autoClose({ res }) { r, e -> - throwable = e + require(promise.complete(e)) r.shutdown() } - wasActive = r.isActive() + require(wasActive.complete(r.isActive())) } - throwable shouldBe null - wasActive shouldBe true + promise.shouldHaveCompleted() shouldBe null + wasActive.shouldHaveCompleted() shouldBe true res.isActive() shouldBe false } @Test fun canHandleWithFailingAutoClose() = runTest { - var throwable: Throwable? = RuntimeException("Dummy exception") - var wasActive = false + val promise = CompletableDeferred() + val wasActive = CompletableDeferred() val error = RuntimeException("BOOM!") val res = Resource() shouldThrow { autoCloseScope { val r = autoClose({ res }) { r, e -> - throwable = e + require(promise.complete(e)) r.shutdown() } - wasActive = r.isActive() + require(wasActive.complete(r.isActive())) throw error } } shouldBe error - throwable shouldBe error - wasActive shouldBe true + promise.shouldHaveCompleted() shouldBe error + wasActive.shouldHaveCompleted() shouldBe true res.isActive() shouldBe false } @Test fun addsSuppressedErrors() = runTest { - var throwable: Throwable? = RuntimeException("Dummy exception") - var wasActive = false + val promise = CompletableDeferred() + val wasActive = CompletableDeferred() val error = RuntimeException("BOOM!") val error2 = RuntimeException("BOOM 2!") val error3 = RuntimeException("BOOM 3!") @@ -65,33 +66,33 @@ class AutoCloseTest { val e = shouldThrow { autoCloseScope { val r = autoClose({ res }) { r, e -> - throwable = e + require(promise.complete(e)) r.shutdown() throw error2 } autoClose({ Resource() }) { _, _ -> throw error3 } - wasActive = r.isActive() + require(wasActive.complete(r.isActive())) throw error } } e shouldBe error e.suppressedExceptions shouldBe listOf(error3, error2) - throwable shouldBe error - wasActive shouldBe true + promise.shouldHaveCompleted() shouldBe error + wasActive.shouldHaveCompleted() shouldBe true res.isActive() shouldBe false } @Test fun handlesAcquireFailure() = runTest { - var throwable: Throwable? = RuntimeException("Dummy exception") + val promise = CompletableDeferred() val error = RuntimeException("BOOM!") val error2 = RuntimeException("BOOM 2!") val e = shouldThrow { autoCloseScope { autoClose({ Resource() }) { r, e -> - throwable = e + require(promise.complete(e)) r.shutdown() throw error2 } @@ -100,54 +101,54 @@ class AutoCloseTest { } e shouldBe error e.suppressedExceptions shouldBe listOf(error2) - throwable shouldBe error + promise.shouldHaveCompleted() shouldBe error } @Test fun canInstallAutoCloseable() = runTest { - var wasActive = false + val wasActive = CompletableDeferred() val res = Resource() autoCloseScope { val r = install(res) - wasActive = r.isActive() + require(wasActive.complete(r.isActive())) } - wasActive shouldBe true + wasActive.shouldHaveCompleted() shouldBe true res.isActive() shouldBe false } @Test fun closeTheAutoScopeOnCancellation() = runTest { - var wasActive = false + val wasActive = CompletableDeferred() val res = Resource() shouldThrow { autoCloseScope { val r = install(res) - wasActive = r.isActive() + require(wasActive.complete(r.isActive())) throw CancellationException("BOOM!") } }.message shouldBe "BOOM!" - wasActive shouldBe true + wasActive.shouldHaveCompleted() shouldBe true res.isActive() shouldBe false } @Test fun closeTheAutoScopeOnNonLocalReturn() = runTest { - var wasActive = false + val wasActive = CompletableDeferred() val res = Resource() run { autoCloseScope { val r = install(res) - wasActive = r.isActive() + require(wasActive.complete(r.isActive())) return@run } } - wasActive shouldBe true + wasActive.shouldHaveCompleted() shouldBe true res.isActive() shouldBe false } @@ -202,4 +203,9 @@ class AutoCloseTest { shutdown() } } + + private suspend fun CompletableDeferred.shouldHaveCompleted(): T { + isCompleted shouldBe true + return await() + } } diff --git a/arrow-libs/fx/arrow-fx-coroutines/src/commonTest/kotlin/arrow/fx/coroutines/Generators.kt b/arrow-libs/fx/arrow-fx-coroutines/src/commonTest/kotlin/arrow/fx/coroutines/Generators.kt index 6066f313945..61a2565028b 100644 --- a/arrow-libs/fx/arrow-fx-coroutines/src/commonTest/kotlin/arrow/fx/coroutines/Generators.kt +++ b/arrow-libs/fx/arrow-fx-coroutines/src/commonTest/kotlin/arrow/fx/coroutines/Generators.kt @@ -5,6 +5,7 @@ import io.kotest.assertions.fail import io.kotest.matchers.Matcher import io.kotest.matchers.MatcherResult import io.kotest.matchers.equalityMatcher +import io.kotest.matchers.shouldBe import io.kotest.property.Arb import io.kotest.property.arbitrary.choice import io.kotest.property.arbitrary.choose @@ -154,3 +155,8 @@ inline fun assertThrowable(executable: () -> A): Throwable { return if (a is Throwable) a else fail("Expected an exception but found: $a") } + +suspend fun CompletableDeferred.shouldHaveCompleted(): T { + isCompleted shouldBe true + return await() +} diff --git a/arrow-libs/fx/arrow-fx-coroutines/src/commonTest/kotlin/arrow/fx/coroutines/ResourceAutoCloseTest.kt b/arrow-libs/fx/arrow-fx-coroutines/src/commonTest/kotlin/arrow/fx/coroutines/ResourceAutoCloseTest.kt index 8d3d5d8d104..7a6f38bbfe6 100644 --- a/arrow-libs/fx/arrow-fx-coroutines/src/commonTest/kotlin/arrow/fx/coroutines/ResourceAutoCloseTest.kt +++ b/arrow-libs/fx/arrow-fx-coroutines/src/commonTest/kotlin/arrow/fx/coroutines/ResourceAutoCloseTest.kt @@ -50,13 +50,13 @@ class ResourceAutoCloseTest { @Test fun autoClosableIsNonCancellable() = runTest { val t = AutoCloseableTest() - lateinit var exit: ExitCase + val exit = CompletableDeferred() val waitingToBeCancelled = CompletableDeferred() val cancelled = CompletableDeferred() val job = launch { resourceScope { - onRelease { exit = it } + onRelease { require(exit.complete(it)) } autoCloseable { waitingToBeCancelled.complete(Unit) cancelled.await() @@ -72,7 +72,7 @@ class ResourceAutoCloseTest { job.join() t.didClose.get() shouldBe true - exit + exit.shouldHaveCompleted() .shouldBeTypeOf() .exception .message shouldBe "BOOM!" diff --git a/arrow-libs/fx/arrow-fx-coroutines/src/commonTest/kotlin/arrow/fx/coroutines/ResourceTest.kt b/arrow-libs/fx/arrow-fx-coroutines/src/commonTest/kotlin/arrow/fx/coroutines/ResourceTest.kt index cd6fb63c85f..5ac980fbd46 100644 --- a/arrow-libs/fx/arrow-fx-coroutines/src/commonTest/kotlin/arrow/fx/coroutines/ResourceTest.kt +++ b/arrow-libs/fx/arrow-fx-coroutines/src/commonTest/kotlin/arrow/fx/coroutines/ResourceTest.kt @@ -69,11 +69,11 @@ class ResourceTest { @Test fun resourceReleasedWithComplete() = runTest { checkAll(10, Arb.int()) { n -> - lateinit var p : ExitCase + val p = CompletableDeferred() resourceScope { - install({ n }) { _, ex -> p = ex } + install({ n }) { _, ex -> require(p.complete(ex)) } } - p shouldBe ExitCase.Completed + p.shouldHaveCompleted() shouldBe ExitCase.Completed } } @@ -126,16 +126,16 @@ class ResourceTest { @Test fun resourceCloseFromEither() = runTest { - lateinit var exit: ExitCase + val exit = CompletableDeferred() either { resourceScope { install({ 1 }) { _, ex -> - exit = ex + require(exit.complete(ex)) } raise("error") } } shouldBe "error".left() - exit.shouldBeTypeOf() + exit.shouldHaveCompleted().shouldBeTypeOf() } private val depth = 10 @@ -149,16 +149,16 @@ class ResourceTest { @Test fun closeTheScopeOnCancellation() = runTest { - var exit: ExitCase? = null + val exit = CompletableDeferred() shouldThrow { resourceScope { - install({ }) { _, ex -> exit = ex } + install({ }) { _, ex -> require(exit.complete(ex)) } throw CancellationException("BOOM!") } }.message shouldBe "BOOM!" - exit + exit.shouldHaveCompleted() .shouldBeTypeOf() .exception .message shouldBe "BOOM!" @@ -166,7 +166,7 @@ class ResourceTest { @Test fun installIsNonCancellable() = runTest { - lateinit var exit: ExitCase + val exit = CompletableDeferred() val waitingToBeCancelled = CompletableDeferred() val cancelled = CompletableDeferred() @@ -176,7 +176,7 @@ class ResourceTest { waitingToBeCancelled.complete(Unit) cancelled.await() }) { _, ex -> - exit = ex + require(exit.complete(ex)) } yield() } @@ -186,7 +186,7 @@ class ResourceTest { cancelled.complete(Unit) job.join() - exit + exit.shouldHaveCompleted() .shouldBeTypeOf() .exception .message shouldBe "BOOM!" @@ -499,25 +499,25 @@ class ResourceTest { @Test fun parZipErrorInUse() = runTestUsingDefaultDispatcher { checkAll(10, Arb.int(), Arb.int(), Arb.throwable()) { a, b, throwable -> - lateinit var releasedA: Pair - lateinit var releasedB: Pair + val releasedA = CompletableDeferred>() + val releasedB = CompletableDeferred>() shouldThrow { resourceScope { parZip({ - install({ a }) { aa: Int, ex: ExitCase -> releasedA = aa to ex } + install({ a }) { aa: Int, ex: ExitCase -> require(releasedA.complete(aa to ex)) } }, { - install({ b }) { bb: Int, ex: ExitCase -> releasedB = bb to ex } + install({ b }) { bb: Int, ex: ExitCase -> require(releasedB.complete(bb to ex)) } }) { _, _ -> } throw throwable } } shouldBe throwable - val (aa, exA) = releasedA + val (aa, exA) = releasedA.shouldHaveCompleted() aa shouldBe a exA.shouldBeTypeOf() - val (bb, exB) = releasedB + val (bb, exB) = releasedB.shouldHaveCompleted() bb shouldBe b exB.shouldBeTypeOf() } @@ -526,25 +526,25 @@ class ResourceTest { @Test fun parZipCancellationInUse() = runTestUsingDefaultDispatcher { checkAll(10, Arb.int(), Arb.int()) { a, b -> - lateinit var releasedA: Pair - lateinit var releasedB: Pair + val releasedA = CompletableDeferred>() + val releasedB = CompletableDeferred>() shouldThrow { resourceScope { parZip({ - install({ a }) { aa: Int, ex: ExitCase -> releasedA = aa to ex } + install({ a }) { aa: Int, ex: ExitCase -> require(releasedA.complete(aa to ex)) } }, { - install({ b }) { bb: Int, ex: ExitCase -> releasedB = bb to ex } + install({ b }) { bb: Int, ex: ExitCase -> require(releasedB.complete(bb to ex)) } }) { _, _ -> } throw CancellationException("") } } - val (aa, exA) = releasedA.shouldNotBeNull() + val (aa, exA) = releasedA.shouldHaveCompleted() aa shouldBe a exA.shouldBeTypeOf() - val (bb, exB) = releasedB.shouldNotBeNull() + val (bb, exB) = releasedB.shouldHaveCompleted() bb shouldBe b exB.shouldBeTypeOf() } @@ -553,40 +553,40 @@ class ResourceTest { @Test fun resourceAsFlow() = runTest { checkAll(10, Arb.int()) { n -> - lateinit var released: ExitCase - val r = resource({ n }, { _, ex -> released = ex }) + val released = CompletableDeferred() + val r = resource({ n }, { _, ex -> require(released.complete(ex)) }) r.asFlow().map { it + 1 }.toList() shouldBe listOf(n + 1) - released shouldBe ExitCase.Completed + released.shouldHaveCompleted() shouldBe ExitCase.Completed } } @Test fun resourceAsFlowFail() = runTest { checkAll(10, Arb.int(), Arb.throwable()) { n, throwable -> - lateinit var released: ExitCase - val r = resource({ n }, { _, ex -> released = ex }) + val released = CompletableDeferred() + val r = resource({ n }, { _, ex -> require(released.complete(ex)) }) shouldThrow { r.asFlow().collect { throw throwable } } shouldBe throwable - released.shouldBeTypeOf().failure shouldBe throwable + released.shouldHaveCompleted().shouldBeTypeOf().failure shouldBe throwable } } @Test fun resourceAsFlowCancel() = runTest { checkAll(10, Arb.int()) { n -> - lateinit var released: ExitCase - val r = resource({ n }, { _, ex -> released = ex }) + val released = CompletableDeferred() + val r = resource({ n }, { _, ex -> require(released.complete(ex)) }) shouldThrow { r.asFlow().collect { throw CancellationException("") } } - released.shouldBeTypeOf() + released.shouldHaveCompleted().shouldBeTypeOf() } } @@ -594,13 +594,13 @@ class ResourceTest { @Test fun allocateWorks() = runTest { checkAll(10, Arb.int()) { seed -> - var released: ExitCase? = null - val (allocated, release) = resource({ seed }) { _, exitCase -> released = exitCase } + val released = CompletableDeferred() + val (allocated, release) = resource({ seed }) { _, exitCase -> require(released.complete(exitCase)) } .allocate() allocated shouldBe seed release(ExitCase.Completed) - released shouldBe ExitCase.Completed + released.shouldHaveCompleted() shouldBe ExitCase.Completed } } @@ -612,10 +612,10 @@ class ResourceTest { Arb.string().map(::RuntimeException), Arb.string().map(::IllegalStateException) ) { seed, original, suppressed -> - var released: ExitCase? = null + val released = CompletableDeferred() val (allocated, release) = resource({ seed }) { _, exitCase -> - released = exitCase + require(released.complete(exitCase)) throw suppressed }.allocate() @@ -630,7 +630,7 @@ class ResourceTest { exception shouldBe original exception.suppressedExceptions.firstOrNull().shouldNotBeNull() shouldBe suppressed - released.shouldBeTypeOf() + released.shouldHaveCompleted().shouldBeTypeOf() } } @@ -642,10 +642,10 @@ class ResourceTest { Arb.string().map { CancellationException(it, null) }, Arb.string().map(::IllegalStateException) ) { seed, cancellation, suppressed -> - var released: ExitCase? = null + val released = CompletableDeferred() val (allocated, release) = resource({ seed }) { _, exitCase -> - released = exitCase + require(released.complete(exitCase)) throw suppressed }.allocate() @@ -660,7 +660,7 @@ class ResourceTest { exception shouldBe cancellation exception.suppressedExceptions.firstOrNull().shouldNotBeNull() shouldBe suppressed - released.shouldBeTypeOf() + released.shouldHaveCompleted().shouldBeTypeOf() } } @@ -673,11 +673,11 @@ class ResourceTest { Arb.string().map(::IllegalStateException), Arb.string().map(::IllegalStateException), ) { seed, original, suppressed1, suppressed2 -> - var released: ExitCase? = null + val released = CompletableDeferred() val (allocate, release) = resource { onRelease { exitCase -> - released = exitCase + require(released.complete(exitCase)) throw suppressed1 } onClose { throw suppressed2 } @@ -695,7 +695,7 @@ class ResourceTest { exception shouldBe original exception.suppressedExceptions shouldBe listOf(suppressed2, suppressed1) - released shouldBe ExitCase(original) + released.shouldHaveCompleted() shouldBe ExitCase(original) } } @@ -753,8 +753,8 @@ class ResourceTest { @Test fun addsSuppressedErrors() = runTest { - var exitCase: ExitCase? = null - var wasActive = false + val exitCase = CompletableDeferred() + val wasActive = CompletableDeferred() val error = RuntimeException("BOOM!") val error2 = RuntimeException("BOOM 2!") val error3 = RuntimeException("BOOM 3!") @@ -763,27 +763,27 @@ class ResourceTest { val e = shouldThrow { resourceScope { val r = install({ res }) { r, e -> - exitCase = e + require(exitCase.complete(e)) r.shutdown() throw error2 } onClose { throw error3 } - wasActive = r.isActive() + require(wasActive.complete(r.isActive())) throw error } } e shouldBe error e.suppressedExceptions shouldBe listOf(error3, error2) - exitCase shouldBe ExitCase(error) - wasActive shouldBe true + exitCase.shouldHaveCompleted() shouldBe ExitCase(error) + wasActive.shouldHaveCompleted() shouldBe true res.isActive() shouldBe false } @Test fun addsSuppressedErrorsFromReleasers() = runTest { - var exitCase: ExitCase? = null - var wasActive = false + val exitCase = CompletableDeferred() + val wasActive = CompletableDeferred() val error = RuntimeException("BOOM!") val error2 = RuntimeException("BOOM 2!") val error3 = RuntimeException("BOOM 3!") @@ -792,20 +792,20 @@ class ResourceTest { val e = shouldThrow { resourceScope { val r = install({ res }) { r, e -> - exitCase = e + require(exitCase.complete(e)) r.shutdown() throw error2 } onClose { throw error3 } - wasActive = r.isActive() + require(wasActive.complete(r.isActive())) onRelease { throw error } } } e shouldBe error e.suppressedExceptions shouldBe listOf(error3, error2) - exitCase shouldBe ExitCase.Completed - wasActive shouldBe true + exitCase.shouldHaveCompleted() shouldBe ExitCase.Completed + wasActive.shouldHaveCompleted() shouldBe true res.isActive() shouldBe false } }