Explorar o código

[core/testFramework] Add assertNoCoroutineSuspension

Him188 %!s(int64=2) %!d(string=hai) anos
pai
achega
0ea5503f3b

+ 92 - 0
mirai-core-utils/src/commonTest/kotlin/net/mamoe/mirai/utils/testFramework/AssertNoCoroutineSuspension.kt

@@ -0,0 +1,92 @@
+/*
+ * Copyright 2019-2023 Mamoe Technologies and contributors.
+ *
+ * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
+ * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
+ *
+ * https://github.com/mamoe/mirai/blob/dev/LICENSE
+ */
+
+package net.mamoe.mirai.utils.testFramework
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.test.runTest
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
+import kotlin.coroutines.resume
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFails
+import kotlin.test.fail
+
+suspend inline fun <R> assertNoCoroutineSuspension(
+    crossinline block: suspend () -> R,
+): R {
+    contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
+    return withContext(Dispatchers.Default.limitedParallelism(1)) {
+        val job = launch(start = CoroutineStart.UNDISPATCHED) {
+            yield()
+            fail("Expected no coroutine suspension")
+        }
+        val ret = block()
+        job.cancel()
+        ret
+    }
+}
+
+suspend inline fun <R> assertCoroutineSuspends(
+    crossinline block: suspend () -> R,
+): R {
+    contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
+
+    return withContext(Dispatchers.Default.limitedParallelism(1)) {
+        val job = launch(start = CoroutineStart.UNDISPATCHED) {
+            yield() // goto block
+        }
+        val ret = block()
+        kotlin.test.assertTrue("Expected coroutine suspension") { job.isCompleted }
+        job.cancel()
+        ret
+    }
+}
+
+class AssertCoroutineSuspensionTest {
+    @Test
+    fun `assertNoCoroutineSuspension no suspension`() = runTest {
+        assertNoCoroutineSuspension {}
+    }
+
+    @Test
+    fun `assertNoCoroutineSuspension suspend cancellable`() = runTest {
+        assertFails {
+            assertNoCoroutineSuspension {
+                suspendCancellableCoroutine<Unit> { }
+            }
+        }.run {
+            assertEquals("Expected no coroutine suspension", message)
+        }
+    }
+
+    @Test
+    fun `assertCoroutineSuspends suspend`() = runTest {
+        assertCoroutineSuspends {
+            suspendCancellableCoroutine {
+                // resume after suspendCancellableCoroutine returns to create a suspension
+                launch(start = CoroutineStart.UNDISPATCHED) {
+                    yield()
+                    it.resume(Unit)
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `assertCoroutineSuspends no suspension`() = runTest {
+        assertFails {
+            assertCoroutineSuspends {}
+        }.run {
+            assertEquals("Expected coroutine suspension", message)
+        }
+    }
+
+}