From 402c280d58b97af5e412c189d59ca58f83c861a0 Mon Sep 17 00:00:00 2001 From: Jonathan Lagneaux Date: Thu, 26 Oct 2023 14:25:41 +0200 Subject: [PATCH] Refactor BracketCaseTest to use kotlin test (#3237) Closes #3186 Co-authored-by: Alejandro Serrano --- .../arrow/fx/coroutines/BracketCaseTest.kt | 609 +++++++++--------- 1 file changed, 315 insertions(+), 294 deletions(-) diff --git a/arrow-libs/fx/arrow-fx-coroutines/src/commonTest/kotlin/arrow/fx/coroutines/BracketCaseTest.kt b/arrow-libs/fx/arrow-fx-coroutines/src/commonTest/kotlin/arrow/fx/coroutines/BracketCaseTest.kt index 5892715e859..62cd84c51bb 100644 --- a/arrow-libs/fx/arrow-fx-coroutines/src/commonTest/kotlin/arrow/fx/coroutines/BracketCaseTest.kt +++ b/arrow-libs/fx/arrow-fx-coroutines/src/commonTest/kotlin/arrow/fx/coroutines/BracketCaseTest.kt @@ -1,7 +1,6 @@ package arrow.fx.coroutines import arrow.core.Either -import io.kotest.core.spec.style.StringSpec import io.kotest.matchers.should import io.kotest.matchers.shouldBe import io.kotest.matchers.types.shouldBeInstanceOf @@ -12,358 +11,380 @@ import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.async import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.test.runTest +import kotlin.test.Test import kotlin.time.ExperimentalTime @ExperimentalTime -class BracketCaseTest : StringSpec({ - "Immediate acquire bracketCase finishes successfully" { - checkAll(Arb.int(), Arb.int()) { a, b -> - var once = true - bracketCase( - acquire = { a }, - use = { aa -> Pair(aa, b) }, - release = { _, _ -> - require(once) - once = false - } - ) shouldBe Pair(a, b) - } +class BracketCaseTest { + @Test + fun immediateAcquireBracketCaseFinishesSuccessfully() = runTest { + checkAll(Arb.int(), Arb.int()) { a, b -> + var once = true + bracketCase( + acquire = { a }, + use = { aa -> Pair(aa, b) }, + release = { _, _ -> + require(once) + once = false + } + ) shouldBe Pair(a, b) } + } - "Suspended acquire bracketCase finishes successfully" { - checkAll(Arb.int(), Arb.int()) { a, b -> - var once = true - bracketCase( - acquire = { a.suspend() }, - use = { aa -> Pair(aa, b) }, - release = { _, _ -> - require(once) - once = false - } - ) shouldBe Pair(a, b) - } + @Test + fun suspendedAcquireBracketCaseFinishedSuccessfully() = runTest { + checkAll(Arb.int(), Arb.int()) { a, b -> + var once = true + bracketCase( + acquire = { a.suspend() }, + use = { aa -> Pair(aa, b) }, + release = { _, _ -> + require(once) + once = false + } + ) shouldBe Pair(a, b) } + } - "Immediate error in acquire stays the same error" { - checkAll(Arb.throwable()) { e -> - Either.catch { - bracketCase( - acquire = { throw e }, - use = { 5 }, - release = { _, _ -> Unit } - ) - } should leftException(e) - } + @Test + fun immediateErrorInAcquireStaysTheSameError() = runTest { + checkAll(Arb.throwable()) { e -> + Either.catch { + bracketCase( + acquire = { throw e }, + use = { 5 }, + release = { _, _ -> Unit } + ) + } should leftException(e) } + } - "Suspend error in acquire stays the same error" { - checkAll(Arb.throwable()) { e -> - Either.catch { - bracketCase( - acquire = { e.suspend() }, - use = { 5 }, - release = { _, _ -> Unit } - ) - } should leftException(e) - } + @Test + fun suspendErrorInAcquireStaysTheSameError() = runTest { + checkAll(Arb.throwable()) { e -> + Either.catch { + bracketCase( + acquire = { e.suspend() }, + use = { 5 }, + release = { _, _ -> Unit } + ) + } should leftException(e) } + } - "Immediate use bracketCase finishes successfully" { - checkAll(Arb.int(), Arb.int()) { a, b -> - var once = true - bracketCase( - acquire = { a }, - use = { aa -> Pair(aa, b).suspend() }, - release = { _, _ -> - require(once) - once = false - } - ) shouldBe Pair(a, b) - } + @Test + fun immediateUseBracketCaseFinishedSuccessfully() = runTest { + checkAll(Arb.int(), Arb.int()) { a, b -> + var once = true + bracketCase( + acquire = { a }, + use = { aa -> Pair(aa, b).suspend() }, + release = { _, _ -> + require(once) + once = false + } + ) shouldBe Pair(a, b) } + } - "Suspended use bracketCase finishes successfully" { - checkAll(Arb.int(), Arb.int()) { a, b -> - var once = true - bracketCase( - acquire = { a }, - use = { aa -> Pair(aa, b).suspend() }, - release = { _, _ -> - require(once) - once = false + @Test + fun suspendedUseBracketCaseFinishesSuccessfully() = runTest { + checkAll(Arb.int(), Arb.int()) { a, b -> + var once = true + bracketCase( + acquire = { a }, + use = { aa -> Pair(aa, b).suspend() }, + release = { _, _ -> + require(once) + once = false + } + ) shouldBe Pair(a, b) + } + } + + @Test + fun bracketCaseMustRunReleaseTaskOnUseImmediateError() = runTest { + checkAll(Arb.int(), Arb.throwable()) { i, e -> + val promise = CompletableDeferred() + + Either.catch { + bracketCase( + acquire = { i }, + use = { throw e }, + release = { _, ex -> + require(promise.complete(ex)) { "Release should only be called once, called again with $ex" } } - ) shouldBe Pair(a, b) + ) } - } - "bracketCase must run release task on use immediate error" { - checkAll(Arb.int(), Arb.throwable()) { i, e -> - val promise = CompletableDeferred() - - Either.catch { - bracketCase( - acquire = { i }, - use = { throw e }, - release = { _, ex -> - require(promise.complete(ex)) { "Release should only be called once, called again with $ex" } - } - ) - } + promise.await() shouldBe ExitCase.Failure(e) + } + } - promise.await() shouldBe ExitCase.Failure(e) + @Test + fun bracketCaseMustRunReleaseTaskOnUseSuspendedError() = runTest { + checkAll(Arb.int(), Arb.throwable()) { x, e -> + val promise = CompletableDeferred>() + + Either.catch { + bracketCase( + acquire = { x }, + use = { e.suspend() }, + release = { xx, ex -> + require(promise.complete(Pair(xx, ex))) { "Release should only be called once, called again with $ex" } + } + ) } + + promise.await() shouldBe Pair(x, ExitCase.Failure(e)) } + } - "bracketCase must run release task on use suspended error" { - checkAll(Arb.int(), Arb.throwable()) { x, e -> - val promise = CompletableDeferred>() - - Either.catch { - bracketCase( - acquire = { x }, - use = { e.suspend() }, - release = { xx, ex -> - require(promise.complete(Pair(xx, ex))) { "Release should only be called once, called again with $ex" } - } - ) - } + @Test + fun bracketCaseMustAlwaysRunImmediateRelease() = runTest { + checkAll(Arb.int()) { x -> + val promise = CompletableDeferred>() - promise.await() shouldBe Pair(x, ExitCase.Failure(e)) + Either.catch { + bracketCase( + acquire = { x }, + use = { it }, + release = { xx, ex -> + require(promise.complete(Pair(xx, ex))) { "Release should only be called once, called again with $ex" } + } + ) } + + promise.await() shouldBe Pair(x, ExitCase.Completed) } + } - "bracketCase must always run immediate release" { - checkAll(Arb.int()) { x -> - val promise = CompletableDeferred>() - - Either.catch { - bracketCase( - acquire = { x }, - use = { it }, - release = { xx, ex -> - require(promise.complete(Pair(xx, ex))) { "Release should only be called once, called again with $ex" } - } - ) - } + @Test + fun bracketCaseMustAlwaysRunSuspendedRelease() = runTest { + checkAll(Arb.int()) { x -> + val promise = CompletableDeferred>() - promise.await() shouldBe Pair(x, ExitCase.Completed) + Either.catch { + bracketCase( + acquire = { x }, + use = { it }, + release = { xx, ex -> + require(promise.complete(Pair(xx, ex))) { "Release should only be called once, called again with $ex" } + .suspend() + } + ) } + + promise.await() shouldBe Pair(x, ExitCase.Completed) } + } - "bracketCase must always run suspended release" { - checkAll(Arb.int()) { x -> - val promise = CompletableDeferred>() - - Either.catch { - bracketCase( - acquire = { x }, - use = { it }, - release = { xx, ex -> - require(promise.complete(Pair(xx, ex))) { "Release should only be called once, called again with $ex" } - .suspend() - } - ) - } + @Test + fun bracketCaseMustAlwaysRunImmediateReleaseError() = runTest { + checkAll(Arb.int(), Arb.throwable()) { n, e -> + Either.catch { + bracketCase( + acquire = { n }, + use = { it }, + release = { _, _ -> throw e } + ) + } should leftException(e) + } + } - promise.await() shouldBe Pair(x, ExitCase.Completed) - } + @Test + fun bracketCaseMustAlwaysRunSuspendedReleaseError() = runTest { + checkAll(Arb.int(), Arb.throwable()) { n, e -> + Either.catch { + bracketCase( + acquire = { n }, + use = { it }, + release = { _, _ -> e.suspend() } + ) + } should leftException(e) } + } - "bracketCase must always run immediate release error" { - checkAll(Arb.int(), Arb.throwable()) { n, e -> - Either.catch { - bracketCase( - acquire = { n }, - use = { it }, - release = { _, _ -> throw e } - ) - } should leftException(e) - } + operator fun Throwable.plus(other: Throwable): Throwable = + apply { addSuppressed(other) } + + @Test + fun bracketCaseMustComposeImmediateUseAndImmediateReleaseError() = runTest { + checkAll(Arb.int(), Arb.throwable(), Arb.throwable()) { n, e, e2 -> + Either.catch { + bracketCase( + acquire = { n }, + use = { throw e }, + release = { _, _ -> throw e2 } + ) + } shouldBe Either.Left(e + e2) } + } - "bracketCase must always run suspended release error" { - checkAll(Arb.int(), Arb.throwable()) { n, e -> - Either.catch { - bracketCase( - acquire = { n }, - use = { it }, - release = { _, _ -> e.suspend() } - ) - } should leftException(e) - } + @Test + fun bracketCaseMustComposeSuspendUseAndImmediateReleaseError() = runTest { + checkAll(Arb.int(), Arb.throwable(), Arb.throwable()) { n, e, e2 -> + Either.catch { + bracketCase( + acquire = { n }, + use = { e.suspend() }, + release = { _, _ -> throw e2 } + ) + } shouldBe Either.Left(e + e2) } - - operator fun Throwable.plus(other: Throwable): Throwable = - apply { addSuppressed(other) } - - "bracketCase must compose immediate use & immediate release error" { - checkAll(Arb.int(), Arb.throwable(), Arb.throwable()) { n, e, e2 -> - Either.catch { - bracketCase( - acquire = { n }, - use = { throw e }, - release = { _, _ -> throw e2 } - ) - } shouldBe Either.Left(e + e2) - } + } + + @Test + fun bracketCaseMustComposeImmediateUseAndSuspendReleaseError() = runTest { + checkAll(Arb.int(), Arb.throwable(), Arb.throwable()) { n, e, e2 -> + Either.catch { + bracketCase( + acquire = { n }, + use = { throw e }, + release = { _, _ -> e2.suspend() } + ) + } shouldBe Either.Left(e + e2) } + } - "bracketCase must compose suspend use & immediate release error" { - checkAll(Arb.int(), Arb.throwable(), Arb.throwable()) { n, e, e2 -> - Either.catch { - bracketCase( - acquire = { n }, - use = { e.suspend() }, - release = { _, _ -> throw e2 } - ) - } shouldBe Either.Left(e + e2) - } + @Test + fun bracketCaseMustComposeSuspendUseAndSuspendReleaseError() = runTest { + checkAll(Arb.int(), Arb.throwable(), Arb.throwable()) { n, e, e2 -> + Either.catch { + bracketCase( + acquire = { n }, + use = { e.suspend() }, + release = { _, _ -> e2.suspend() } + ) + } shouldBe Either.Left(e + e2) } + } - "bracketCase must compose immediate use & suspend release error" { - checkAll(Arb.int(), Arb.throwable(), Arb.throwable()) { n, e, e2 -> - Either.catch { - bracketCase( - acquire = { n }, - use = { throw e }, - release = { _, _ -> e2.suspend() } - ) - } shouldBe Either.Left(e + e2) - } + @Test + fun cancelOnBracketCaseReleaseWithImmediateAcquire() = runTest { + val start = CompletableDeferred() + val exit = CompletableDeferred() + + val f = async { + bracketCase( + acquire = { }, + use = { + // Signal that fiber is running + start.complete(Unit) + awaitCancellation() + }, + release = { _, exitCase -> + require(exit.complete(exitCase)) { "Release should only be called once, called again with $exitCase" } + } + ) } - "bracketCase must compose suspend use & suspend release error" { - checkAll(Arb.int(), Arb.throwable(), Arb.throwable()) { n, e, e2 -> - Either.catch { - bracketCase( - acquire = { n }, - use = { e.suspend() }, - release = { _, _ -> e2.suspend() } - ) - } shouldBe Either.Left(e + e2) - } + // Wait until the fiber is started before cancelling + start.await() + f.cancel() + exit.await().shouldBeInstanceOf() + } + + @Test + fun cancelOnBracketCaseReleasesWithSuspendingAcquire() = runTest { + val start = CompletableDeferred() + val exit = CompletableDeferred() + + val f = async { + bracketCase( + acquire = { Unit.suspend() }, + use = { + // Signal that fiber is running + start.complete(Unit) + awaitCancellation() + }, + release = { _, exitCase -> + require(exit.complete(exitCase)) { "Release should only be called once, called again with $exitCase" } + } + ) } - "cancel on bracketCase releases with immediate acquire" { - val start = CompletableDeferred() - val exit = CompletableDeferred() + // Wait until the fiber is started before cancelling + start.await() + f.cancel() + exit.await().shouldBeInstanceOf() + } - val f = async { - bracketCase( - acquire = { Unit }, - use = { - // Signal that fiber is running - start.complete(Unit) - awaitCancellation() - }, - release = { _, exitCase -> - require(exit.complete(exitCase)) { "Release should only be called once, called again with $exitCase" } - } - ) - } + @Test + fun cancelOnBracketCaseDoesNotInvokeAfterFinishing() = runTest { + val start = CompletableDeferred() + val exit = CompletableDeferred() + + val f = async { + bracketCase( + acquire = { Unit }, + use = { Unit.suspend() }, + release = { _, exitCase -> + require(exit.complete(exitCase)) { "Release should only be called once, called again with $exitCase" } + } + ) - // Wait until the fiber is started before cancelling - start.await() - f.cancel() - exit.await().shouldBeInstanceOf() + // Signal that fiber can be cancelled running + start.complete(Unit) + awaitCancellation() } - "cancel on bracketCase releases with suspending acquire" { - val start = CompletableDeferred() - val exit = CompletableDeferred() + // Wait until the fiber is started before cancelling + start.await() + f.cancel() + exit.await() shouldBe ExitCase.Completed + } + + @Test + fun acquireOnBracketCaseIsNotCancellable() = runTest { + checkAll(Arb.int(), Arb.int()) { x, y -> + val mVar = Channel(1).apply { send(x) } + val latch = CompletableDeferred() + val p = CompletableDeferred() - val f = async { + val fiber = async { bracketCase( - acquire = { Unit.suspend() }, - use = { - // Signal that fiber is running - start.complete(Unit) - awaitCancellation() + acquire = { + latch.complete(Unit) + // This should be uncancellable, and suspends until capacity 1 is received + mVar.send(y) }, - release = { _, exitCase -> - require(exit.complete(exitCase)) { "Release should only be called once, called again with $exitCase" } - } + use = { awaitCancellation() }, + release = { _, exitCase -> require(p.complete(exitCase)) } ) } - // Wait until the fiber is started before cancelling - start.await() - f.cancel() - exit.await().shouldBeInstanceOf() + // Wait until acquire started + latch.await() + async { fiber.cancel() } + + mVar.receive() shouldBe x + mVar.receive() shouldBe y + p.await().shouldBeInstanceOf() } + } - "cancel on bracketCase doesn't invoke after finishing" { - val start = CompletableDeferred() - val exit = CompletableDeferred() + @Test + fun releaseOnBracketCaseIsNotCancellable() = runTest { + checkAll(Arb.int(), Arb.int()) { x, y -> + val mVar = Channel(1).apply { send(x) } + val latch = CompletableDeferred() - val f = async { + val fiber = async { bracketCase( - acquire = { Unit }, - use = { Unit.suspend() }, - release = { _, exitCase -> - require(exit.complete(exitCase)) { "Release should only be called once, called again with $exitCase" } - } + acquire = { latch.complete(Unit) }, + use = { awaitCancellation() }, + release = { _, _ -> mVar.send(y) } ) - - // Signal that fiber can be cancelled running - start.complete(Unit) - awaitCancellation() - } - - // Wait until the fiber is started before cancelling - start.await() - f.cancel() - exit.await() shouldBe ExitCase.Completed - } - - "acquire on bracketCase is not cancellable" { - checkAll(Arb.int(), Arb.int()) { x, y -> - val mVar = Channel(1).apply { send(x) } - val latch = CompletableDeferred() - val p = CompletableDeferred() - - val fiber = async { - bracketCase( - acquire = { - latch.complete(Unit) - // This should be uncancellable, and suspends until capacity 1 is received - mVar.send(y) - }, - use = { awaitCancellation() }, - release = { _, exitCase -> require(p.complete(exitCase)) } - ) - } - - // Wait until acquire started - latch.await() - async { fiber.cancel() } - - mVar.receive() shouldBe x - mVar.receive() shouldBe y - p.await().shouldBeInstanceOf() } - } - "release on bracketCase is not cancellable" { - checkAll(Arb.int(), Arb.int()) { x, y -> - val mVar = Channel(1).apply { send(x) } - val latch = CompletableDeferred() - - val fiber = async { - bracketCase( - acquire = { latch.complete(Unit) }, - use = { awaitCancellation() }, - release = { _, _ -> mVar.send(y) } - ) - } - - latch.await() - async { fiber.cancel() } + latch.await() + async { fiber.cancel() } - mVar.receive() shouldBe x - // If release was cancelled this hangs since the buffer is empty - mVar.receive() shouldBe y - } + mVar.receive() shouldBe x + // If release was cancelled this hangs since the buffer is empty + mVar.receive() shouldBe y } } -) +}