diff --git a/CHANGES.md b/CHANGES.md
index 510c35accf..611e9c9c74 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,15 @@
# Change log for kotlinx.coroutines
+## Version 1.5.2
+
+* Kotlin is updated to 1.5.30.
+* New native targets for Apple Silicon are introduced.
+* Fixed a bug when `onUndeliveredElement` was incorrectly called on a properly received elements on JS (#2826).
+* Fixed `Dispatchers.Default` on React Native, it now fully relies on `setTimeout` instead of stub `process.nextTick`. Thanks to @Legion2 (#2843).
+* Optimizations of `Mutex` implementation (#2581).
+* `Mutex` implementation is made completely lock-free as stated (#2590).
+* Various documentation and guides improvements. Thanks to @MasoodFallahpoor and @Pihanya.
+
## Version 1.5.1
* Atomic `update`, `getAndUpdate`, and `updateAndGet` operations of `MutableStateFlow` (#2720).
diff --git a/README.md b/README.md
index 6437ac72f6..6a13f07aa3 100644
--- a/README.md
+++ b/README.md
@@ -2,12 +2,12 @@
[![official JetBrains project](https://jb.gg/badges/official.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
[![GitHub license](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat)](https://www.apache.org/licenses/LICENSE-2.0)
-[![Download](https://img.shields.io/maven-central/v/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.5.1)](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.5.1/pom)
-[![Kotlin](https://img.shields.io/badge/kotlin-1.5.20-blue.svg?logo=kotlin)](http://kotlinlang.org)
+[![Download](https://img.shields.io/maven-central/v/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.5.2)](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.5.2/pom)
+[![Kotlin](https://img.shields.io/badge/kotlin-1.5.30-blue.svg?logo=kotlin)](http://kotlinlang.org)
[![Slack channel](https://img.shields.io/badge/chat-slack-green.svg?logo=slack)](https://kotlinlang.slack.com/messages/coroutines/)
Library support for Kotlin coroutines with [multiplatform](#multiplatform) support.
-This is a companion version for the Kotlin `1.5.20` release.
+This is a companion version for the Kotlin `1.5.30` release.
```kotlin
suspend fun main() = coroutineScope {
@@ -83,7 +83,7 @@ Add dependencies (you can also add other modules that you need):
org.jetbrains.kotlinx
kotlinx-coroutines-core
- 1.5.1
+ 1.5.2
```
@@ -91,7 +91,7 @@ And make sure that you use the latest Kotlin version:
```xml
- 1.5.20
+ 1.5.30
```
@@ -101,7 +101,7 @@ Add dependencies (you can also add other modules that you need):
```groovy
dependencies {
- implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1'
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
}
```
@@ -109,7 +109,7 @@ And make sure that you use the latest Kotlin version:
```groovy
buildscript {
- ext.kotlin_version = '1.5.20'
+ ext.kotlin_version = '1.5.30'
}
```
@@ -127,7 +127,7 @@ Add dependencies (you can also add other modules that you need):
```groovy
dependencies {
- implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2")
}
```
@@ -147,7 +147,7 @@ Add [`kotlinx-coroutines-android`](ui/kotlinx-coroutines-android)
module as a dependency when using `kotlinx.coroutines` on Android:
```groovy
-implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1'
+implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'
```
This gives you access to the Android [Dispatchers.Main]
@@ -180,7 +180,7 @@ In common code that should get compiled for different platforms, you can add a d
```groovy
commonMain {
dependencies {
- implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2")
}
}
```
@@ -192,7 +192,7 @@ Platform-specific dependencies are recommended to be used only for non-multiplat
#### JS
Kotlin/JS version of `kotlinx.coroutines` is published as
-[`kotlinx-coroutines-core-js`](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-js/1.5.1/jar)
+[`kotlinx-coroutines-core-js`](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-js/1.5.2/jar)
(follow the link to get the dependency declaration snippet) and as [`kotlinx-coroutines-core`](https://www.npmjs.com/package/kotlinx-coroutines-core) NPM package.
#### Native
diff --git a/docs/topics/exception-handling.md b/docs/topics/exception-handling.md
index 3facd51a22..35e645f4b6 100644
--- a/docs/topics/exception-handling.md
+++ b/docs/topics/exception-handling.md
@@ -364,6 +364,7 @@ only downwards. This can easily be demonstrated using the following example:
import kotlinx.coroutines.*
fun main() = runBlocking {
+//sampleStart
val supervisor = SupervisorJob()
with(CoroutineScope(coroutineContext + supervisor)) {
// launch the first child -- its exception is ignored for this example (don't do this in practice!)
@@ -389,8 +390,10 @@ fun main() = runBlocking {
supervisor.cancel()
secondChild.join()
}
+//sampleEnd
}
```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-supervision-01.kt).
>
@@ -418,6 +421,7 @@ import kotlin.coroutines.*
import kotlinx.coroutines.*
fun main() = runBlocking {
+//sampleStart
try {
supervisorScope {
val child = launch {
@@ -436,8 +440,10 @@ fun main() = runBlocking {
} catch(e: AssertionError) {
println("Caught an assertion error")
}
+//sampleEnd
}
```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-supervision-02.kt).
>
@@ -468,6 +474,7 @@ import kotlin.coroutines.*
import kotlinx.coroutines.*
fun main() = runBlocking {
+//sampleStart
val handler = CoroutineExceptionHandler { _, exception ->
println("CoroutineExceptionHandler got $exception")
}
@@ -479,8 +486,10 @@ fun main() = runBlocking {
println("The scope is completing")
}
println("The scope is completed")
+//sampleEnd
}
```
+{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-supervision-03.kt).
>
diff --git a/gradle.properties b/gradle.properties
index 2983dd11a0..26e5147c51 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -3,14 +3,14 @@
#
# Kotlin
-version=1.5.1-SNAPSHOT
+version=1.5.2-SNAPSHOT
group=org.jetbrains.kotlinx
-kotlin_version=1.5.20
+kotlin_version=1.5.30
# Dependencies
junit_version=4.12
junit5_version=5.7.0
-atomicfu_version=0.16.2
+atomicfu_version=0.16.3
knit_version=0.3.0
html_version=0.7.2
lincheck_version=2.14
@@ -22,7 +22,7 @@ rxjava2_version=2.2.8
rxjava3_version=3.0.2
javafx_version=11.0.2
javafx_plugin_version=0.0.8
-binary_compatibility_validator_version=0.6.0
+binary_compatibility_validator_version=0.7.0
blockhound_version=1.0.2.RELEASE
jna_version=5.5.0
@@ -56,3 +56,4 @@ jekyll_version=4.0
org.gradle.jvmargs=-Xmx4g
kotlin.mpp.enableCompatibilityMetadataVariant=true
+kotlin.mpp.stability.nowarn=true
diff --git a/gradle/compile-native-multiplatform.gradle b/gradle/compile-native-multiplatform.gradle
index 73e99e8465..0a247ede9a 100644
--- a/gradle/compile-native-multiplatform.gradle
+++ b/gradle/compile-native-multiplatform.gradle
@@ -25,6 +25,10 @@ kotlin {
addTarget(presets.watchosArm64)
addTarget(presets.watchosX86)
addTarget(presets.watchosX64)
+ addTarget(presets.iosSimulatorArm64)
+ addTarget(presets.watchosSimulatorArm64)
+ addTarget(presets.tvosSimulatorArm64)
+ addTarget(presets.macosArm64)
}
sourceSets {
diff --git a/gradle/opt-in.gradle b/gradle/opt-in.gradle
index bcf6bebe94..22f022dbb5 100644
--- a/gradle/opt-in.gradle
+++ b/gradle/opt-in.gradle
@@ -3,6 +3,7 @@
*/
ext.optInAnnotations = [
+ "kotlin.RequiresOptIn",
"kotlin.experimental.ExperimentalTypeInference",
"kotlin.ExperimentalMultiplatform",
"kotlinx.coroutines.DelicateCoroutinesApi",
diff --git a/kotlinx-coroutines-core/common/src/CancellableContinuation.kt b/kotlinx-coroutines-core/common/src/CancellableContinuation.kt
index b133b7935d..2c2f1b8ff6 100644
--- a/kotlinx-coroutines-core/common/src/CancellableContinuation.kt
+++ b/kotlinx-coroutines-core/common/src/CancellableContinuation.kt
@@ -81,6 +81,10 @@ public interface CancellableContinuation : Continuation {
* Same as [tryResume] but with [onCancellation] handler that called if and only if the value is not
* delivered to the caller because of the dispatch in the process, so that atomicity delivery
* guaranteed can be provided by having a cancellation fallback.
+ *
+ * Implementation note: current implementation always returns RESUME_TOKEN or `null`
+ *
+ * @suppress **This is unstable API and it is subject to change.**
*/
@InternalCoroutinesApi
public fun tryResume(value: T, idempotent: Any?, onCancellation: ((cause: Throwable) -> Unit)?): Any?
diff --git a/kotlinx-coroutines-core/common/src/CompletableJob.kt b/kotlinx-coroutines-core/common/src/CompletableJob.kt
index f986d78760..beafdaf2ca 100644
--- a/kotlinx-coroutines-core/common/src/CompletableJob.kt
+++ b/kotlinx-coroutines-core/common/src/CompletableJob.kt
@@ -21,7 +21,7 @@ public interface CompletableJob : Job {
*
* Subsequent invocations of this function have no effect and always produce `false`.
*
- * This function transitions this job into _completed- state if it was not completed or cancelled yet.
+ * This function transitions this job into _completed_ state if it was not completed or cancelled yet.
* However, that if this job has children, then it transitions into _completing_ state and becomes _complete_
* once all its children are [complete][isCompleted]. See [Job] for details.
*/
diff --git a/kotlinx-coroutines-core/common/src/CoroutineScope.kt b/kotlinx-coroutines-core/common/src/CoroutineScope.kt
index 627318f676..3ed233bfb9 100644
--- a/kotlinx-coroutines-core/common/src/CoroutineScope.kt
+++ b/kotlinx-coroutines-core/common/src/CoroutineScope.kt
@@ -49,7 +49,7 @@ import kotlin.coroutines.intrinsics.*
* * `CoroutineScope()` uses [Dispatchers.Default] for its coroutines.
* * `MainScope()` uses [Dispatchers.Main] for its coroutines.
*
- * **The key part of custom usage of `CustomScope` is cancelling it and the end of the lifecycle.**
+ * **The key part of custom usage of `CustomScope` is cancelling it at the end of the lifecycle.**
* The [CoroutineScope.cancel] extension function shall be used when the entity that was launching coroutines
* is no longer needed. It cancels all the coroutines that might still be running on behalf of it.
*
@@ -185,7 +185,7 @@ public val CoroutineScope.isActive: Boolean
* }
* ```
*
- * In top-level code, when launching a concurrent operation operation from a non-suspending context, an appropriately
+ * In top-level code, when launching a concurrent operation from a non-suspending context, an appropriately
* confined instance of [CoroutineScope] shall be used instead of a `GlobalScope`. See docs on [CoroutineScope] for
* details.
*
diff --git a/kotlinx-coroutines-core/common/src/Job.kt b/kotlinx-coroutines-core/common/src/Job.kt
index be58213288..9552153aa9 100644
--- a/kotlinx-coroutines-core/common/src/Job.kt
+++ b/kotlinx-coroutines-core/common/src/Job.kt
@@ -168,7 +168,7 @@ public interface Job : CoroutineContext.Element {
/**
* Starts coroutine related to this job (if any) if it was not started yet.
- * The result `true` if this invocation actually started coroutine or `false`
+ * The result is `true` if this invocation actually started coroutine or `false`
* if it was already started or completed.
*/
public fun start(): Boolean
diff --git a/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt b/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt
index bcf1921594..4751296c87 100644
--- a/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt
+++ b/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt
@@ -136,6 +136,7 @@ internal abstract class AbstractSendChannel(
return sendSuspend(element)
}
+ @Suppress("DEPRECATION")
override fun offer(element: E): Boolean {
// Temporary migration for offer users who rely on onUndeliveredElement
try {
diff --git a/kotlinx-coroutines-core/common/src/flow/Channels.kt b/kotlinx-coroutines-core/common/src/flow/Channels.kt
index 5cc8ad8b35..382953efcb 100644
--- a/kotlinx-coroutines-core/common/src/flow/Channels.kt
+++ b/kotlinx-coroutines-core/common/src/flow/Channels.kt
@@ -171,7 +171,7 @@ private class ChannelAsFlow(
*/
@Deprecated(
level = DeprecationLevel.WARNING,
- message = "'BroadcastChannel' is obsolete and all coreresponding operators are deprecated " +
+ message = "'BroadcastChannel' is obsolete and all corresponding operators are deprecated " +
"in the favour of StateFlow and SharedFlow"
) // Since 1.5.0, was @FlowPreview, safe to remove in 1.7.0
public fun BroadcastChannel.asFlow(): Flow = flow {
@@ -182,7 +182,7 @@ public fun BroadcastChannel.asFlow(): Flow = flow {
* ### Deprecated
*
* **This API is deprecated.** The [BroadcastChannel] provides a complex channel-like API for hot flows.
- * [SharedFlow] is a easier-to-use and more flow-centric API for the same purposes, so using
+ * [SharedFlow] is an easier-to-use and more flow-centric API for the same purposes, so using
* [shareIn] operator is preferred. It is not a direct replacement, so please
* study [shareIn] documentation to see what kind of shared flow fits your use-case. As a rule of thumb:
*
diff --git a/kotlinx-coroutines-core/common/src/flow/SharedFlow.kt b/kotlinx-coroutines-core/common/src/flow/SharedFlow.kt
index 9bcf088e95..d79e203464 100644
--- a/kotlinx-coroutines-core/common/src/flow/SharedFlow.kt
+++ b/kotlinx-coroutines-core/common/src/flow/SharedFlow.kt
@@ -469,7 +469,7 @@ private class SharedFlowImpl(
// outside of the lock: register dispose on cancellation
emitter?.let { cont.disposeOnCancellation(it) }
// outside of the lock: resume slots if needed
- for (cont in resumes) cont?.resume(Unit)
+ for (r in resumes) r?.resume(Unit)
}
private fun cancelEmitter(emitter: Emitter) = synchronized(this) {
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Lint.kt b/kotlinx-coroutines-core/common/src/flow/operators/Lint.kt
index 858c885c1e..83f83e1e15 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Lint.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Lint.kt
@@ -83,7 +83,8 @@ public val FlowCollector<*>.coroutineContext: CoroutineContext
get() = noImpl()
@Deprecated(
- message = "SharedFlow never completes, so this operator has no effect.",
+ message = "SharedFlow never completes, so this operator typically has not effect, it can only " +
+ "catch exceptions from 'onSubscribe' operator",
level = DeprecationLevel.WARNING,
replaceWith = ReplaceWith("this")
)
diff --git a/kotlinx-coroutines-core/common/src/flow/terminal/Collect.kt b/kotlinx-coroutines-core/common/src/flow/terminal/Collect.kt
index d26839f9ea..771f8332c3 100644
--- a/kotlinx-coroutines-core/common/src/flow/terminal/Collect.kt
+++ b/kotlinx-coroutines-core/common/src/flow/terminal/Collect.kt
@@ -44,7 +44,7 @@ public suspend fun Flow<*>.collect(): Unit = collect(NopCollector)
* .launchIn(uiScope)
* ```
*
- * Note that resulting value of [launchIn] is not used the provided scope takes care of cancellation.
+ * Note that the resulting value of [launchIn] is not used and the provided scope takes care of cancellation.
*/
public fun Flow.launchIn(scope: CoroutineScope): Job = scope.launch {
collect() // tail-call
diff --git a/kotlinx-coroutines-core/common/src/internal/StackTraceRecovery.common.kt b/kotlinx-coroutines-core/common/src/internal/StackTraceRecovery.common.kt
index b91f30d319..2d00768d7c 100644
--- a/kotlinx-coroutines-core/common/src/internal/StackTraceRecovery.common.kt
+++ b/kotlinx-coroutines-core/common/src/internal/StackTraceRecovery.common.kt
@@ -20,6 +20,7 @@ internal expect fun recoverStackTrace(exception: E, continuation:
/**
* initCause on JVM, nop on other platforms
*/
+@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
internal expect fun Throwable.initCause(cause: Throwable)
/**
diff --git a/kotlinx-coroutines-core/common/src/sync/Mutex.kt b/kotlinx-coroutines-core/common/src/sync/Mutex.kt
index 7d0a343d95..19584e0981 100644
--- a/kotlinx-coroutines-core/common/src/sync/Mutex.kt
+++ b/kotlinx-coroutines-core/common/src/sync/Mutex.kt
@@ -10,7 +10,6 @@ import kotlinx.coroutines.internal.*
import kotlinx.coroutines.intrinsics.*
import kotlinx.coroutines.selects.*
import kotlin.contracts.*
-import kotlin.coroutines.*
import kotlin.jvm.*
import kotlin.native.concurrent.*
@@ -124,8 +123,6 @@ private val LOCK_FAIL = Symbol("LOCK_FAIL")
@SharedImmutable
private val UNLOCK_FAIL = Symbol("UNLOCK_FAIL")
@SharedImmutable
-private val SELECT_SUCCESS = Symbol("SELECT_SUCCESS")
-@SharedImmutable
private val LOCKED = Symbol("LOCKED")
@SharedImmutable
private val UNLOCKED = Symbol("UNLOCKED")
@@ -191,7 +188,7 @@ internal class MutexImpl(locked: Boolean) : Mutex, SelectClause2 {
}
private suspend fun lockSuspend(owner: Any?) = suspendCancellableCoroutineReusable sc@ { cont ->
- val waiter = LockCont(owner, cont)
+ var waiter = LockCont(owner, cont)
_state.loop { state ->
when (state) {
is Empty -> {
@@ -210,11 +207,24 @@ internal class MutexImpl(locked: Boolean) : Mutex, SelectClause2 {
is LockedQueue -> {
val curOwner = state.owner
check(curOwner !== owner) { "Already locked by $owner" }
- if (state.addLastIf(waiter) { _state.value === state }) {
- // added to waiter list!
+
+ state.addLast(waiter)
+ /*
+ * If the state has been changed while we were adding the waiter,
+ * it means that 'unlock' has taken it and _either_ resumed it successfully or just overwritten.
+ * To rendezvous that, we try to "invalidate" our node and go for retry.
+ *
+ * Node has to be re-instantiated as we do not support node re-adding, even to
+ * another list
+ */
+ if (_state.value === state || !waiter.take()) {
+ // added to waiter list
cont.removeOnCancellation(waiter)
return@sc
}
+
+ waiter = LockCont(owner, cont)
+ return@loop
}
is OpDescriptor -> state.perform(this) // help
else -> error("Illegal state $state")
@@ -252,8 +262,17 @@ internal class MutexImpl(locked: Boolean) : Mutex, SelectClause2 {
is LockedQueue -> {
check(state.owner !== owner) { "Already locked by $owner" }
val node = LockSelect(owner, select, block)
- if (state.addLastIf(node) { _state.value === state }) {
- // successfully enqueued
+ /*
+ * If the state has been changed while we were adding the waiter,
+ * it means that 'unlock' has taken it and _either_ resumed it successfully or just overwritten.
+ * To rendezvous that, we try to "invalidate" our node and go for retry.
+ *
+ * Node has to be re-instantiated as we do not support node re-adding, even to
+ * another list
+ */
+ state.addLast(node)
+ if (_state.value === state || !node.take()) {
+ // added to waiter list
select.disposeOnSelect(node)
return
}
@@ -300,7 +319,7 @@ internal class MutexImpl(locked: Boolean) : Mutex, SelectClause2 {
}
}
- public override fun unlock(owner: Any?) {
+ override fun unlock(owner: Any?) {
_state.loop { state ->
when (state) {
is Empty -> {
@@ -319,10 +338,9 @@ internal class MutexImpl(locked: Boolean) : Mutex, SelectClause2 {
val op = UnlockOp(state)
if (_state.compareAndSet(state, op) && op.perform(this) == null) return
} else {
- val token = (waiter as LockWaiter).tryResumeLockWaiter()
- if (token != null) {
+ if ((waiter as LockWaiter).tryResumeLockWaiter()) {
state.owner = waiter.owner ?: LOCKED
- waiter.completeResumeLockWaiter(token)
+ waiter.completeResumeLockWaiter()
return
}
}
@@ -352,21 +370,28 @@ internal class MutexImpl(locked: Boolean) : Mutex, SelectClause2 {
private abstract inner class LockWaiter(
@JvmField val owner: Any?
) : LockFreeLinkedListNode(), DisposableHandle {
+ private val isTaken = atomic(false)
+ fun take(): Boolean = isTaken.compareAndSet(false, true)
final override fun dispose() { remove() }
- abstract fun tryResumeLockWaiter(): Any?
- abstract fun completeResumeLockWaiter(token: Any)
+ abstract fun tryResumeLockWaiter(): Boolean
+ abstract fun completeResumeLockWaiter()
}
private inner class LockCont(
owner: Any?,
- @JvmField val cont: CancellableContinuation
+ private val cont: CancellableContinuation
) : LockWaiter(owner) {
- override fun tryResumeLockWaiter() = cont.tryResume(Unit, idempotent = null) {
- // if this continuation gets cancelled during dispatch to the caller, then release the lock
- unlock(owner)
+
+ override fun tryResumeLockWaiter(): Boolean {
+ if (!take()) return false
+ return cont.tryResume(Unit, idempotent = null) {
+ // if this continuation gets cancelled during dispatch to the caller, then release the lock
+ unlock(owner)
+ } != null
}
- override fun completeResumeLockWaiter(token: Any) = cont.completeResume(token)
- override fun toString(): String = "LockCont[$owner, $cont] for ${this@MutexImpl}"
+
+ override fun completeResumeLockWaiter() = cont.completeResume(RESUME_TOKEN)
+ override fun toString(): String = "LockCont[$owner, ${cont}] for ${this@MutexImpl}"
}
private inner class LockSelect(
@@ -374,9 +399,8 @@ internal class MutexImpl(locked: Boolean) : Mutex, SelectClause2 {
@JvmField val select: SelectInstance,
@JvmField val block: suspend (Mutex) -> R
) : LockWaiter(owner) {
- override fun tryResumeLockWaiter(): Any? = if (select.trySelect()) SELECT_SUCCESS else null
- override fun completeResumeLockWaiter(token: Any) {
- assert { token === SELECT_SUCCESS }
+ override fun tryResumeLockWaiter(): Boolean = take() && select.trySelect()
+ override fun completeResumeLockWaiter() {
block.startCoroutineCancellable(receiver = this@MutexImpl, completion = select.completion) {
// if this continuation gets cancelled during dispatch to the caller, then release the lock
unlock(owner)
diff --git a/kotlinx-coroutines-core/common/test/AsyncLazyTest.kt b/kotlinx-coroutines-core/common/test/AsyncLazyTest.kt
index 5b23b64143..cd2401049e 100644
--- a/kotlinx-coroutines-core/common/test/AsyncLazyTest.kt
+++ b/kotlinx-coroutines-core/common/test/AsyncLazyTest.kt
@@ -76,7 +76,7 @@ class AsyncLazyTest : TestBase() {
expected = { it is TestException }
) {
expect(1)
- val d = async(start = CoroutineStart.LAZY) {
+ val d = async(start = CoroutineStart.LAZY) {
finish(3)
throw TestException()
}
@@ -90,7 +90,7 @@ class AsyncLazyTest : TestBase() {
expected = { it is TestException }
) {
expect(1)
- val d = async(start = CoroutineStart.LAZY) {
+ val d = async(start = CoroutineStart.LAZY) {
expect(3)
yield() // this has not effect, because parent coroutine is waiting
finish(4)
@@ -104,7 +104,7 @@ class AsyncLazyTest : TestBase() {
@Test
fun testCatchException() = runTest {
expect(1)
- val d = async(NonCancellable, start = CoroutineStart.LAZY) {
+ val d = async(NonCancellable, start = CoroutineStart.LAZY) {
expect(3)
throw TestException()
}
@@ -184,4 +184,4 @@ class AsyncLazyTest : TestBase() {
assertEquals(d.await(), 42) // await shall throw CancellationException
expectUnreached()
}
-}
\ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/common/test/AsyncTest.kt b/kotlinx-coroutines-core/common/test/AsyncTest.kt
index 3019ddeab1..2096a4d69e 100644
--- a/kotlinx-coroutines-core/common/test/AsyncTest.kt
+++ b/kotlinx-coroutines-core/common/test/AsyncTest.kt
@@ -43,7 +43,7 @@ class AsyncTest : TestBase() {
@Test
fun testSimpleException() = runTest(expected = { it is TestException }) {
expect(1)
- val d = async {
+ val d = async {
finish(3)
throw TestException()
}
@@ -170,7 +170,7 @@ class AsyncTest : TestBase() {
@Test
fun testDeferAndYieldException() = runTest(expected = { it is TestException }) {
expect(1)
- val d = async {
+ val d = async {
expect(3)
yield() // no effect, parent waiting
finish(4)
@@ -266,4 +266,38 @@ class AsyncTest : TestBase() {
assertFalse(deferred.isCancelled)
}
+ @Test
+ fun testAsyncWithFinally() = runTest {
+ expect(1)
+
+ @Suppress("UNREACHABLE_CODE")
+ val d = async {
+ expect(3)
+ try {
+ yield() // to main, will cancel
+ } finally {
+ expect(6) // will go there on await
+ return@async "Fail" // result will not override cancellation
+ }
+ expectUnreached()
+ "Fail2"
+ }
+ expect(2)
+ yield() // to async
+ expect(4)
+ check(d.isActive && !d.isCompleted && !d.isCancelled)
+ d.cancel()
+ check(!d.isActive && !d.isCompleted && d.isCancelled)
+ check(!d.isActive && !d.isCompleted && d.isCancelled)
+ expect(5)
+ try {
+ d.await() // awaits
+ expectUnreached() // does not complete normally
+ } catch (e: Throwable) {
+ expect(7)
+ check(e is CancellationException)
+ }
+ check(!d.isActive && d.isCompleted && d.isCancelled)
+ finish(8)
+ }
}
diff --git a/kotlinx-coroutines-core/common/test/AtomicCancellationCommonTest.kt b/kotlinx-coroutines-core/common/test/AtomicCancellationCommonTest.kt
index a41013779a..3881eb2779 100644
--- a/kotlinx-coroutines-core/common/test/AtomicCancellationCommonTest.kt
+++ b/kotlinx-coroutines-core/common/test/AtomicCancellationCommonTest.kt
@@ -140,7 +140,7 @@ class AtomicCancellationCommonTest : TestBase() {
val mutex = Mutex(true) // locked mutex
val job = launch(start = CoroutineStart.UNDISPATCHED) {
expect(2)
- val result = select { // suspends
+ select { // suspends
mutex.onLock {
expect(4)
"OK"
diff --git a/kotlinx-coroutines-core/common/test/CancellableResumeTest.kt b/kotlinx-coroutines-core/common/test/CancellableResumeTest.kt
index fbfa082555..bff971961b 100644
--- a/kotlinx-coroutines-core/common/test/CancellableResumeTest.kt
+++ b/kotlinx-coroutines-core/common/test/CancellableResumeTest.kt
@@ -82,7 +82,7 @@ class CancellableResumeTest : TestBase() {
cont.invokeOnCancellation { expect(3) }
ctx.cancel()
expect(4)
- cont.resume("OK") { cause ->
+ cont.resume("OK") {
expect(5)
}
finish(6)
@@ -108,7 +108,7 @@ class CancellableResumeTest : TestBase() {
}
ctx.cancel()
expect(4)
- cont.resume("OK") { cause ->
+ cont.resume("OK") {
expect(5)
throw TestException3("FAIL") // onCancellation block fails with exception
}
diff --git a/kotlinx-coroutines-core/common/test/CancelledParentAttachTest.kt b/kotlinx-coroutines-core/common/test/CancelledParentAttachTest.kt
index 749bbfc921..9dd61b8012 100644
--- a/kotlinx-coroutines-core/common/test/CancelledParentAttachTest.kt
+++ b/kotlinx-coroutines-core/common/test/CancelledParentAttachTest.kt
@@ -11,75 +11,105 @@ import kotlin.test.*
class CancelledParentAttachTest : TestBase() {
@Test
- fun testAsync() = CoroutineStart.values().forEach(::testAsyncCancelledParent)
+ fun testAsync() = runTest {
+ CoroutineStart.values().forEach { testAsyncCancelledParent(it) }
+ }
- private fun testAsyncCancelledParent(start: CoroutineStart) =
- runTest({ it is CancellationException }) {
- cancel()
- expect(1)
- val d = async(start = start) { 42 }
- expect(2)
- d.invokeOnCompletion {
- finish(3)
- reset()
+ private suspend fun testAsyncCancelledParent(start: CoroutineStart) {
+ try {
+ withContext(Job()) {
+ cancel()
+ expect(1)
+ val d = async(start = start) { 42 }
+ expect(2)
+ d.invokeOnCompletion {
+ finish(3)
+ reset()
+ }
}
+ expectUnreached()
+ } catch (e: CancellationException) {
+ // Expected
}
+ }
@Test
- fun testLaunch() = CoroutineStart.values().forEach(::testLaunchCancelledParent)
+ fun testLaunch() = runTest {
+ CoroutineStart.values().forEach { testLaunchCancelledParent(it) }
+ }
- private fun testLaunchCancelledParent(start: CoroutineStart) =
- runTest({ it is CancellationException }) {
- cancel()
- expect(1)
- val d = launch(start = start) { }
- expect(2)
- d.invokeOnCompletion {
- finish(3)
- reset()
+ private suspend fun testLaunchCancelledParent(start: CoroutineStart) {
+ try {
+ withContext(Job()) {
+ cancel()
+ expect(1)
+ val d = launch(start = start) { }
+ expect(2)
+ d.invokeOnCompletion {
+ finish(3)
+ reset()
+ }
}
+ expectUnreached()
+ } catch (e: CancellationException) {
+ // Expected
}
+ }
@Test
- fun testProduce() =
- runTest({ it is CancellationException }) {
- cancel()
- expect(1)
- val d = produce { }
- expect(2)
- (d as Job).invokeOnCompletion {
- finish(3)
- reset()
- }
+ fun testProduce() = runTest({ it is CancellationException }) {
+ cancel()
+ expect(1)
+ val d = produce { }
+ expect(2)
+ (d as Job).invokeOnCompletion {
+ finish(3)
+ reset()
}
+ }
@Test
- fun testBroadcast() = CoroutineStart.values().forEach(::testBroadcastCancelledParent)
+ fun testBroadcast() = runTest {
+ CoroutineStart.values().forEach { testBroadcastCancelledParent(it) }
+ }
- private fun testBroadcastCancelledParent(start: CoroutineStart) =
- runTest({ it is CancellationException }) {
- cancel()
- expect(1)
- val bc = broadcast(start = start) {}
- expect(2)
- (bc as Job).invokeOnCompletion {
- finish(3)
- reset()
+ private suspend fun testBroadcastCancelledParent(start: CoroutineStart) {
+ try {
+ withContext(Job()) {
+ cancel()
+ expect(1)
+ val bc = broadcast(start = start) {}
+ expect(2)
+ (bc as Job).invokeOnCompletion {
+ finish(3)
+ reset()
+ }
}
+ expectUnreached()
+ } catch (e: CancellationException) {
+ // Expected
}
+ }
@Test
- fun testScopes() {
- testScope { coroutineScope { } }
- testScope { supervisorScope { } }
- testScope { flowScope { } }
- testScope { withTimeout(Long.MAX_VALUE) { } }
- testScope { withContext(Job()) { } }
- testScope { withContext(CoroutineName("")) { } }
+ fun testScopes() = runTest {
+ testScope { coroutineScope { } }
+ testScope { supervisorScope { } }
+ testScope { flowScope { } }
+ testScope { withTimeout(Long.MAX_VALUE) { } }
+ testScope { withContext(Job()) { } }
+ testScope { withContext(CoroutineName("")) { } }
}
- private inline fun testScope(crossinline block: suspend () -> Unit) = runTest({ it is CancellationException }) {
- cancel()
- block()
+ private suspend inline fun testScope(crossinline block: suspend () -> Unit) {
+ try {
+ withContext(Job()) {
+ cancel()
+ block()
+ }
+ expectUnreached()
+ } catch (e: CancellationException) {
+ // Expected
+ }
}
}
diff --git a/kotlinx-coroutines-core/common/test/CoroutineDispatcherOperatorFunInvokeTest.kt b/kotlinx-coroutines-core/common/test/CoroutineDispatcherOperatorFunInvokeTest.kt
index 6fdd3bbe8b..e6b340cc62 100644
--- a/kotlinx-coroutines-core/common/test/CoroutineDispatcherOperatorFunInvokeTest.kt
+++ b/kotlinx-coroutines-core/common/test/CoroutineDispatcherOperatorFunInvokeTest.kt
@@ -65,7 +65,6 @@ class CoroutineDispatcherOperatorFunInvokeTest : TestBase() {
dispatcher.dispatch(context, block)
}
- @ExperimentalCoroutinesApi
override fun isDispatchNeeded(context: CoroutineContext): Boolean {
return dispatcher.isDispatchNeeded(context)
}
diff --git a/kotlinx-coroutines-core/common/test/TestBase.common.kt b/kotlinx-coroutines-core/common/test/TestBase.common.kt
index 0ba80ee509..71c45769cb 100644
--- a/kotlinx-coroutines-core/common/test/TestBase.common.kt
+++ b/kotlinx-coroutines-core/common/test/TestBase.common.kt
@@ -13,7 +13,22 @@ import kotlin.test.*
public expect val isStressTest: Boolean
public expect val stressTestMultiplier: Int
+/**
+ * The result of a multiplatform asynchronous test.
+ * Aliases into Unit on K/JVM and K/N, and into Promise on K/JS.
+ */
+@Suppress("NO_ACTUAL_FOR_EXPECT")
+public expect class TestResult
+
public expect open class TestBase constructor() {
+ /*
+ * In common tests we emulate parameterized tests
+ * by iterating over parameters space in the single @Test method.
+ * This kind of tests is too slow for JS and does not fit into
+ * the default Mocha timeout, so we're using this flag to bail-out
+ * and run such tests only on JVM and K/N.
+ */
+ public val isBoundByJsTestTimeout: Boolean
public fun error(message: Any, cause: Throwable? = null): Nothing
public fun expect(index: Int)
public fun expectUnreached()
@@ -25,7 +40,7 @@ public expect open class TestBase constructor() {
expected: ((Throwable) -> Boolean)? = null,
unhandled: List<(Throwable) -> Boolean> = emptyList(),
block: suspend CoroutineScope.() -> Unit
- )
+ ): TestResult
}
public suspend inline fun hang(onCancellation: () -> Unit) {
diff --git a/kotlinx-coroutines-core/common/test/UndispatchedResultTest.kt b/kotlinx-coroutines-core/common/test/UndispatchedResultTest.kt
index e262572277..34b8164472 100644
--- a/kotlinx-coroutines-core/common/test/UndispatchedResultTest.kt
+++ b/kotlinx-coroutines-core/common/test/UndispatchedResultTest.kt
@@ -55,7 +55,7 @@ class UndispatchedResultTest : TestBase() {
try {
expect(1)
// Will cancel its parent
- async(context) {
+ async(context) {
expect(2)
throw TestException()
}.await()
diff --git a/kotlinx-coroutines-core/common/test/WithTimeoutDurationTest.kt b/kotlinx-coroutines-core/common/test/WithTimeoutDurationTest.kt
index b91a87f0bd..efd55fe231 100644
--- a/kotlinx-coroutines-core/common/test/WithTimeoutDurationTest.kt
+++ b/kotlinx-coroutines-core/common/test/WithTimeoutDurationTest.kt
@@ -17,7 +17,7 @@ class WithTimeoutDurationTest : TestBase() {
@Test
fun testBasicNoSuspend() = runTest {
expect(1)
- val result = withTimeout(10.seconds) {
+ val result = withTimeout(Duration.seconds(10)) {
expect(2)
"OK"
}
@@ -31,7 +31,7 @@ class WithTimeoutDurationTest : TestBase() {
@Test
fun testBasicSuspend() = runTest {
expect(1)
- val result = withTimeout(10.seconds) {
+ val result = withTimeout(Duration.seconds(10)) {
expect(2)
yield()
expect(3)
@@ -54,7 +54,7 @@ class WithTimeoutDurationTest : TestBase() {
}
expect(2)
// test that it does not yield to the above job when started
- val result = withTimeout(1.seconds) {
+ val result = withTimeout(Duration.seconds(1)) {
expect(3)
yield() // yield only now
expect(5)
@@ -74,7 +74,7 @@ class WithTimeoutDurationTest : TestBase() {
fun testYieldBlockingWithTimeout() = runTest(
expected = { it is CancellationException }
) {
- withTimeout(100.milliseconds) {
+ withTimeout(Duration.milliseconds(100)) {
while (true) {
yield()
}
@@ -87,7 +87,7 @@ class WithTimeoutDurationTest : TestBase() {
@Test
fun testWithTimeoutChildWait() = runTest {
expect(1)
- withTimeout(100.milliseconds) {
+ withTimeout(Duration.milliseconds(100)) {
expect(2)
// launch child with timeout
launch {
@@ -102,7 +102,7 @@ class WithTimeoutDurationTest : TestBase() {
@Test
fun testBadClass() = runTest {
val bad = BadClass()
- val result = withTimeout(100.milliseconds) {
+ val result = withTimeout(Duration.milliseconds(100)) {
bad
}
assertSame(bad, result)
@@ -118,9 +118,9 @@ class WithTimeoutDurationTest : TestBase() {
fun testExceptionOnTimeout() = runTest {
expect(1)
try {
- withTimeout(100.milliseconds) {
+ withTimeout(Duration.milliseconds(100)) {
expect(2)
- delay(1000.milliseconds)
+ delay(Duration.milliseconds(1000))
expectUnreached()
"OK"
}
@@ -135,10 +135,10 @@ class WithTimeoutDurationTest : TestBase() {
expected = { it is CancellationException }
) {
expect(1)
- withTimeout(100.milliseconds) {
+ withTimeout(Duration.milliseconds(100)) {
expect(2)
try {
- delay(1000.milliseconds)
+ delay(Duration.milliseconds(1000))
} catch (e: CancellationException) {
finish(3)
}
@@ -151,10 +151,10 @@ class WithTimeoutDurationTest : TestBase() {
fun testSuppressExceptionWithAnotherException() = runTest {
expect(1)
try {
- withTimeout(100.milliseconds) {
+ withTimeout(Duration.milliseconds(100)) {
expect(2)
try {
- delay(1000.milliseconds)
+ delay(Duration.milliseconds(1000))
} catch (e: CancellationException) {
expect(3)
throw TestException()
@@ -172,7 +172,7 @@ class WithTimeoutDurationTest : TestBase() {
fun testNegativeTimeout() = runTest {
expect(1)
try {
- withTimeout(-1.milliseconds) {
+ withTimeout(-Duration.milliseconds(1)) {
expectUnreached()
"OK"
}
@@ -187,7 +187,7 @@ class WithTimeoutDurationTest : TestBase() {
expect(1)
try {
expect(2)
- withTimeout(1.seconds) {
+ withTimeout(Duration.seconds(1)) {
expect(3)
throw TestException()
}
diff --git a/kotlinx-coroutines-core/common/test/WithTimeoutOrNullDurationTest.kt b/kotlinx-coroutines-core/common/test/WithTimeoutOrNullDurationTest.kt
index f72bb7d99d..b5777753b6 100644
--- a/kotlinx-coroutines-core/common/test/WithTimeoutOrNullDurationTest.kt
+++ b/kotlinx-coroutines-core/common/test/WithTimeoutOrNullDurationTest.kt
@@ -19,7 +19,7 @@ class WithTimeoutOrNullDurationTest : TestBase() {
@Test
fun testBasicNoSuspend() = runTest {
expect(1)
- val result = withTimeoutOrNull(10.seconds) {
+ val result = withTimeoutOrNull(Duration.seconds(10)) {
expect(2)
"OK"
}
@@ -33,7 +33,7 @@ class WithTimeoutOrNullDurationTest : TestBase() {
@Test
fun testBasicSuspend() = runTest {
expect(1)
- val result = withTimeoutOrNull(10.seconds) {
+ val result = withTimeoutOrNull(Duration.seconds(10)) {
expect(2)
yield()
expect(3)
@@ -56,7 +56,7 @@ class WithTimeoutOrNullDurationTest : TestBase() {
}
expect(2)
// test that it does not yield to the above job when started
- val result = withTimeoutOrNull(1.seconds) {
+ val result = withTimeoutOrNull(Duration.seconds(1)) {
expect(3)
yield() // yield only now
expect(5)
@@ -74,7 +74,7 @@ class WithTimeoutOrNullDurationTest : TestBase() {
@Test
fun testYieldBlockingWithTimeout() = runTest {
expect(1)
- val result = withTimeoutOrNull(100.milliseconds) {
+ val result = withTimeoutOrNull(Duration.milliseconds(100)) {
while (true) {
yield()
}
@@ -86,7 +86,7 @@ class WithTimeoutOrNullDurationTest : TestBase() {
@Test
fun testSmallTimeout() = runTest {
val channel = Channel(1)
- val value = withTimeoutOrNull(1.milliseconds) {
+ val value = withTimeoutOrNull(Duration.milliseconds(1)) {
channel.receive()
}
assertNull(value)
@@ -94,7 +94,7 @@ class WithTimeoutOrNullDurationTest : TestBase() {
@Test
fun testThrowException() = runTest(expected = {it is AssertionError}) {
- withTimeoutOrNull(Duration.INFINITE) {
+ withTimeoutOrNull(Duration.INFINITE) {
throw AssertionError()
}
}
@@ -103,12 +103,13 @@ class WithTimeoutOrNullDurationTest : TestBase() {
fun testInnerTimeout() = runTest(
expected = { it is CancellationException }
) {
- withTimeoutOrNull(1000.milliseconds) {
- withTimeout(10.milliseconds) {
+ withTimeoutOrNull(Duration.milliseconds(1000)) {
+ withTimeout(Duration.milliseconds(10)) {
while (true) {
yield()
}
}
+ @Suppress("UNREACHABLE_CODE")
expectUnreached() // will timeout
}
expectUnreached() // will timeout
@@ -118,7 +119,7 @@ class WithTimeoutOrNullDurationTest : TestBase() {
fun testNestedTimeout() = runTest(expected = { it is TimeoutCancellationException }) {
withTimeoutOrNull(Duration.INFINITE) {
// Exception from this withTimeout is not suppressed by withTimeoutOrNull
- withTimeout(10.milliseconds) {
+ withTimeout(Duration.milliseconds(10)) {
delay(Duration.INFINITE)
1
}
@@ -130,9 +131,9 @@ class WithTimeoutOrNullDurationTest : TestBase() {
@Test
fun testOuterTimeout() = runTest {
var counter = 0
- val result = withTimeoutOrNull(250.milliseconds) {
+ val result = withTimeoutOrNull(Duration.milliseconds(250)) {
while (true) {
- val inner = withTimeoutOrNull(100.milliseconds) {
+ val inner = withTimeoutOrNull(Duration.milliseconds(100)) {
while (true) {
yield()
}
@@ -148,7 +149,7 @@ class WithTimeoutOrNullDurationTest : TestBase() {
@Test
fun testBadClass() = runTest {
val bad = BadClass()
- val result = withTimeoutOrNull(100.milliseconds) {
+ val result = withTimeoutOrNull(Duration.milliseconds(100)) {
bad
}
assertSame(bad, result)
@@ -163,9 +164,9 @@ class WithTimeoutOrNullDurationTest : TestBase() {
@Test
fun testNullOnTimeout() = runTest {
expect(1)
- val result = withTimeoutOrNull(100.milliseconds) {
+ val result = withTimeoutOrNull(Duration.milliseconds(100)) {
expect(2)
- delay(1000.milliseconds)
+ delay(Duration.milliseconds(1000))
expectUnreached()
"OK"
}
@@ -176,10 +177,10 @@ class WithTimeoutOrNullDurationTest : TestBase() {
@Test
fun testSuppressExceptionWithResult() = runTest {
expect(1)
- val result = withTimeoutOrNull(100.milliseconds) {
+ val result = withTimeoutOrNull(Duration.milliseconds(100)) {
expect(2)
try {
- delay(1000.milliseconds)
+ delay(Duration.milliseconds(1000))
} catch (e: CancellationException) {
expect(3)
}
@@ -193,10 +194,10 @@ class WithTimeoutOrNullDurationTest : TestBase() {
fun testSuppressExceptionWithAnotherException() = runTest {
expect(1)
try {
- withTimeoutOrNull(100.milliseconds) {
+ withTimeoutOrNull(Duration.milliseconds(100)) {
expect(2)
try {
- delay(1000.milliseconds)
+ delay(Duration.milliseconds(1000))
} catch (e: CancellationException) {
expect(3)
throw TestException()
@@ -215,11 +216,11 @@ class WithTimeoutOrNullDurationTest : TestBase() {
@Test
fun testNegativeTimeout() = runTest {
expect(1)
- var result = withTimeoutOrNull(-1.milliseconds) {
+ var result = withTimeoutOrNull(-Duration.milliseconds(1)) {
expectUnreached()
}
assertNull(result)
- result = withTimeoutOrNull(0.milliseconds) {
+ result = withTimeoutOrNull(Duration.milliseconds(0)) {
expectUnreached()
}
assertNull(result)
@@ -231,7 +232,7 @@ class WithTimeoutOrNullDurationTest : TestBase() {
expect(1)
try {
expect(2)
- withTimeoutOrNull(1000.milliseconds) {
+ withTimeoutOrNull(Duration.milliseconds(1000)) {
expect(3)
throw TestException()
}
diff --git a/kotlinx-coroutines-core/common/test/WithTimeoutOrNullTest.kt b/kotlinx-coroutines-core/common/test/WithTimeoutOrNullTest.kt
index 40d2758daa..90bcf2dac3 100644
--- a/kotlinx-coroutines-core/common/test/WithTimeoutOrNullTest.kt
+++ b/kotlinx-coroutines-core/common/test/WithTimeoutOrNullTest.kt
@@ -92,7 +92,7 @@ class WithTimeoutOrNullTest : TestBase() {
@Test
fun testThrowException() = runTest(expected = {it is AssertionError}) {
- withTimeoutOrNull(Long.MAX_VALUE) {
+ withTimeoutOrNull(Long.MAX_VALUE) {
throw AssertionError()
}
}
@@ -107,6 +107,7 @@ class WithTimeoutOrNullTest : TestBase() {
yield()
}
}
+ @Suppress("UNREACHABLE_CODE")
expectUnreached() // will timeout
}
expectUnreached() // will timeout
diff --git a/kotlinx-coroutines-core/common/test/channels/ChannelUndeliveredElementTest.kt b/kotlinx-coroutines-core/common/test/channels/ChannelUndeliveredElementTest.kt
index 5513dab782..f26361f2f8 100644
--- a/kotlinx-coroutines-core/common/test/channels/ChannelUndeliveredElementTest.kt
+++ b/kotlinx-coroutines-core/common/test/channels/ChannelUndeliveredElementTest.kt
@@ -10,17 +10,19 @@ import kotlin.test.*
class ChannelUndeliveredElementTest : TestBase() {
@Test
- fun testSendSuccessfully() = runAllKindsTest { kind ->
- val channel = kind.create { it.cancel() }
- val res = Resource("OK")
- launch {
- channel.send(res)
+ fun testSendSuccessfully() = runTest {
+ runAllKindsTest { kind ->
+ val channel = kind.create { it.cancel() }
+ val res = Resource("OK")
+ launch {
+ channel.send(res)
+ }
+ val ok = channel.receive()
+ assertEquals("OK", ok.value)
+ assertFalse(res.isCancelled) // was not cancelled
+ channel.close()
+ assertFalse(res.isCancelled) // still was not cancelled
}
- val ok = channel.receive()
- assertEquals("OK", ok.value)
- assertFalse(res.isCancelled) // was not cancelled
- channel.close()
- assertFalse(res.isCancelled) // still was not cancelled
}
@Test
@@ -86,21 +88,23 @@ class ChannelUndeliveredElementTest : TestBase() {
}
@Test
- fun testSendToClosedChannel() = runAllKindsTest { kind ->
- val channel = kind.create { it.cancel() }
- channel.close() // immediately close channel
- val res = Resource("OK")
- assertFailsWith {
- channel.send(res) // send fails to closed channel, resource was not delivered
+ fun testSendToClosedChannel() = runTest {
+ runAllKindsTest { kind ->
+ val channel = kind.create { it.cancel() }
+ channel.close() // immediately close channel
+ val res = Resource("OK")
+ assertFailsWith {
+ channel.send(res) // send fails to closed channel, resource was not delivered
+ }
+ assertTrue(res.isCancelled)
}
- assertTrue(res.isCancelled)
}
- private fun runAllKindsTest(test: suspend CoroutineScope.(TestChannelKind) -> Unit) {
+ private suspend fun runAllKindsTest(test: suspend CoroutineScope.(TestChannelKind) -> Unit) {
for (kind in TestChannelKind.values()) {
if (kind.viaBroadcast) continue // does not support onUndeliveredElement
try {
- runTest {
+ withContext(Job()) {
test(kind)
}
} catch(e: Throwable) {
@@ -119,4 +123,19 @@ class ChannelUndeliveredElementTest : TestBase() {
check(!_cancelled.getAndSet(true)) { "Already cancelled" }
}
}
+
+ @Test
+ fun testHandlerIsNotInvoked() = runTest { // #2826
+ val channel = Channel {
+ expectUnreached()
+ }
+
+ expect(1)
+ launch {
+ expect(2)
+ channel.receive()
+ }
+ channel.send(Unit)
+ finish(3)
+ }
}
diff --git a/kotlinx-coroutines-core/common/test/flow/VirtualTime.kt b/kotlinx-coroutines-core/common/test/flow/VirtualTime.kt
index b2d957be46..bba5c6bd87 100644
--- a/kotlinx-coroutines-core/common/test/flow/VirtualTime.kt
+++ b/kotlinx-coroutines-core/common/test/flow/VirtualTime.kt
@@ -48,7 +48,6 @@ internal class VirtualTimeDispatcher(enclosingScope: CoroutineScope) : Coroutine
originalDispatcher.dispatch(context, block)
}
- @ExperimentalCoroutinesApi
override fun isDispatchNeeded(context: CoroutineContext): Boolean = originalDispatcher.isDispatchNeeded(context)
override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle {
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/CatchTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/CatchTest.kt
index eedfac2ea3..447eb73b5d 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/CatchTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/CatchTest.kt
@@ -133,7 +133,7 @@ class CatchTest : TestBase() {
.flowOn(d2)
// flowOn with a different dispatcher introduces asynchrony so that all exceptions in the
// upstream flows are handled before they go downstream
- .onEach { value ->
+ .onEach {
expectUnreached() // already cancelled
}
.catch { e ->
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/DebounceTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/DebounceTest.kt
index ce75e598e9..aa0893e888 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/DebounceTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/DebounceTest.kt
@@ -205,19 +205,19 @@ class DebounceTest : TestBase() {
val flow = flow {
expect(3)
emit("A")
- delay(1500.milliseconds)
+ delay(Duration.milliseconds(1500))
emit("B")
- delay(500.milliseconds)
+ delay(Duration.milliseconds(500))
emit("C")
- delay(250.milliseconds)
+ delay(Duration.milliseconds(250))
emit("D")
- delay(2000.milliseconds)
+ delay(Duration.milliseconds(2000))
emit("E")
expect(4)
}
expect(2)
- val result = flow.debounce(1000.milliseconds).toList()
+ val result = flow.debounce(Duration.milliseconds(1000)).toList()
assertEquals(listOf("A", "D", "E"), result)
finish(5)
}
@@ -296,13 +296,13 @@ class DebounceTest : TestBase() {
val flow = flow {
expect(3)
emit("A")
- delay(1500.milliseconds)
+ delay(Duration.milliseconds(1500))
emit("B")
- delay(500.milliseconds)
+ delay(Duration.milliseconds(500))
emit("C")
- delay(250.milliseconds)
+ delay(Duration.milliseconds(250))
emit("D")
- delay(2000.milliseconds)
+ delay(Duration.milliseconds(2000))
emit("E")
expect(4)
}
@@ -310,9 +310,9 @@ class DebounceTest : TestBase() {
expect(2)
val result = flow.debounce {
if (it == "C") {
- 0.milliseconds
+ Duration.milliseconds(0)
} else {
- 1000.milliseconds
+ Duration.milliseconds(1000)
}
}.toList()
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeBaseTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeBaseTest.kt
index 44376980cd..4095172dab 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeBaseTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeBaseTest.kt
@@ -90,5 +90,5 @@ abstract class FlatMapMergeBaseTest : FlatMapBaseTest() {
}
@Test
- abstract fun testFlatMapConcurrency()
+ abstract fun testFlatMapConcurrency(): TestResult
}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/OnEachTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/OnEachTest.kt
index bad9db9757..3c1ebfa005 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/OnEachTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/OnEachTest.kt
@@ -42,7 +42,6 @@ class OnEachTest : TestBase() {
}.onEach {
latch.receive()
throw TestException()
- it + 1
}.catch { emit(42) }
assertEquals(42, flow.single())
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/SampleTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/SampleTest.kt
index 22a0d4a39a..87bee56f1d 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/SampleTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/SampleTest.kt
@@ -268,7 +268,6 @@ class SampleTest : TestBase() {
expect(2)
yield()
throw TestException()
- it
}
assertFailsWith(flow)
@@ -282,19 +281,19 @@ class SampleTest : TestBase() {
val flow = flow {
expect(3)
emit("A")
- delay(1500.milliseconds)
+ delay(Duration.milliseconds(1500))
emit("B")
- delay(500.milliseconds)
+ delay(Duration.milliseconds(500))
emit("C")
- delay(250.milliseconds)
+ delay(Duration.milliseconds(250))
emit("D")
- delay(2000.milliseconds)
+ delay(Duration.milliseconds(2000))
emit("E")
expect(4)
}
expect(2)
- val result = flow.sample(1000.milliseconds).toList()
+ val result = flow.sample(Duration.milliseconds(1000)).toList()
assertEquals(listOf("A", "B", "D"), result)
finish(5)
}
diff --git a/kotlinx-coroutines-core/common/test/flow/sharing/SharedFlowTest.kt b/kotlinx-coroutines-core/common/test/flow/sharing/SharedFlowTest.kt
index 32d88f3c99..6e18b38f55 100644
--- a/kotlinx-coroutines-core/common/test/flow/sharing/SharedFlowTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/sharing/SharedFlowTest.kt
@@ -439,7 +439,8 @@ class SharedFlowTest : TestBase() {
}
@Test
- fun testDifferentBufferedFlowCapacities() {
+ fun testDifferentBufferedFlowCapacities() = runTest {
+ if (isBoundByJsTestTimeout) return@runTest // Too slow for JS, bounded by 2 sec. default JS timeout
for (replay in 0..10) {
for (extraBufferCapacity in 0..5) {
if (replay == 0 && extraBufferCapacity == 0) continue // test only buffered shared flows
@@ -456,7 +457,7 @@ class SharedFlowTest : TestBase() {
}
}
- private fun testBufferedFlow(sh: MutableSharedFlow, replay: Int) = runTest {
+ private suspend fun testBufferedFlow(sh: MutableSharedFlow, replay: Int) = withContext(Job()) {
reset()
expect(1)
val n = 100 // initially emitted to fill buffer
@@ -601,6 +602,7 @@ class SharedFlowTest : TestBase() {
}
@Test
+ @Suppress("DEPRECATION") // 'catch'
fun onSubscriptionThrows() = runTest {
expect(1)
val sh = MutableSharedFlow(1)
@@ -678,6 +680,7 @@ class SharedFlowTest : TestBase() {
@Test
fun testStateFlowModel() = runTest {
+ if (isBoundByJsTestTimeout) return@runTest // Too slow for JS, bounded by 2 sec. default JS timeout
val stateFlow = MutableStateFlow(null)
val expect = modelLog(stateFlow)
val sharedFlow = MutableSharedFlow(
@@ -795,4 +798,4 @@ class SharedFlowTest : TestBase() {
job.join()
finish(5)
}
-}
\ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/sharing/SharingStartedWhileSubscribedTest.kt b/kotlinx-coroutines-core/common/test/flow/sharing/SharingStartedWhileSubscribedTest.kt
index bcf626e3e3..516bb2e291 100644
--- a/kotlinx-coroutines-core/common/test/flow/sharing/SharingStartedWhileSubscribedTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/sharing/SharingStartedWhileSubscribedTest.kt
@@ -30,11 +30,13 @@ class SharingStartedWhileSubscribedTest : TestBase() {
@Test
fun testDurationParams() {
assertEquals(SharingStarted.WhileSubscribed(0), SharingStarted.WhileSubscribed(Duration.ZERO))
- assertEquals(SharingStarted.WhileSubscribed(10), SharingStarted.WhileSubscribed(10.milliseconds))
+ assertEquals(SharingStarted.WhileSubscribed(10), SharingStarted.WhileSubscribed(Duration.milliseconds(10)))
assertEquals(SharingStarted.WhileSubscribed(1000), SharingStarted.WhileSubscribed(1.seconds))
assertEquals(SharingStarted.WhileSubscribed(Long.MAX_VALUE), SharingStarted.WhileSubscribed(Duration.INFINITE))
assertEquals(SharingStarted.WhileSubscribed(replayExpirationMillis = 0), SharingStarted.WhileSubscribed(replayExpiration = Duration.ZERO))
- assertEquals(SharingStarted.WhileSubscribed(replayExpirationMillis = 3), SharingStarted.WhileSubscribed(replayExpiration = 3.milliseconds))
+ assertEquals(SharingStarted.WhileSubscribed(replayExpirationMillis = 3), SharingStarted.WhileSubscribed(
+ replayExpiration = Duration.milliseconds(3)
+ ))
assertEquals(SharingStarted.WhileSubscribed(replayExpirationMillis = 7000), SharingStarted.WhileSubscribed(replayExpiration = 7.seconds))
assertEquals(SharingStarted.WhileSubscribed(replayExpirationMillis = Long.MAX_VALUE), SharingStarted.WhileSubscribed(replayExpiration = Duration.INFINITE))
}
diff --git a/kotlinx-coroutines-core/common/test/flow/terminal/FoldTest.kt b/kotlinx-coroutines-core/common/test/flow/terminal/FoldTest.kt
index 3c88571378..9a920f1d5f 100644
--- a/kotlinx-coroutines-core/common/test/flow/terminal/FoldTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/terminal/FoldTest.kt
@@ -47,7 +47,6 @@ class FoldTest : TestBase() {
latch.receive()
expect(4)
throw TestException()
- 42 // Workaround for KT-30642, return type should not be Nothing
}
}
finish(6)
diff --git a/kotlinx-coroutines-core/common/test/flow/terminal/ReduceTest.kt b/kotlinx-coroutines-core/common/test/flow/terminal/ReduceTest.kt
index 99ee1d6641..8ba0b5efbc 100644
--- a/kotlinx-coroutines-core/common/test/flow/terminal/ReduceTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/terminal/ReduceTest.kt
@@ -30,7 +30,7 @@ class ReduceTest : TestBase() {
fun testNullableReduce() = runTest {
val flow = flowOf(1, null, null, 2)
var invocations = 0
- val sum = flow.reduce { acc, value ->
+ val sum = flow.reduce { _, value ->
++invocations
value
}
@@ -67,7 +67,6 @@ class ReduceTest : TestBase() {
latch.receive()
expect(4)
throw TestException()
- 42 // Workaround for KT-30642, return type should not be Nothing
}
}
finish(6)
diff --git a/kotlinx-coroutines-core/common/test/selects/SelectTimeoutDurationTest.kt b/kotlinx-coroutines-core/common/test/selects/SelectTimeoutDurationTest.kt
index 66cb72a535..26d6f809b8 100644
--- a/kotlinx-coroutines-core/common/test/selects/SelectTimeoutDurationTest.kt
+++ b/kotlinx-coroutines-core/common/test/selects/SelectTimeoutDurationTest.kt
@@ -14,15 +14,15 @@ class SelectTimeoutDurationTest : TestBase() {
fun testBasic() = runTest {
expect(1)
val result = select {
- onTimeout(1000.milliseconds) {
+ onTimeout(Duration.milliseconds(1000)) {
expectUnreached()
"FAIL"
}
- onTimeout(100.milliseconds) {
+ onTimeout(Duration.milliseconds(100)) {
expect(2)
"OK"
}
- onTimeout(500.milliseconds) {
+ onTimeout(Duration.milliseconds(500)) {
expectUnreached()
"FAIL"
}
@@ -35,7 +35,7 @@ class SelectTimeoutDurationTest : TestBase() {
fun testZeroTimeout() = runTest {
expect(1)
val result = select {
- onTimeout(1.seconds) {
+ onTimeout(Duration.seconds(1)) {
expectUnreached()
"FAIL"
}
@@ -52,11 +52,11 @@ class SelectTimeoutDurationTest : TestBase() {
fun testNegativeTimeout() = runTest {
expect(1)
val result = select {
- onTimeout(1.seconds) {
+ onTimeout(Duration.seconds(1)) {
expectUnreached()
"FAIL"
}
- onTimeout(-10.milliseconds) {
+ onTimeout(-Duration.milliseconds(10)) {
expect(2)
"OK"
}
@@ -71,13 +71,13 @@ class SelectTimeoutDurationTest : TestBase() {
val iterations =10_000
for (i in 0..iterations) {
val result = selectUnbiased {
- onTimeout(-1.seconds) {
+ onTimeout(-Duration.seconds(1)) {
0
}
onTimeout(Duration.ZERO) {
1
}
- onTimeout(1.seconds) {
+ onTimeout(Duration.seconds(1)) {
expectUnreached()
2
}
diff --git a/kotlinx-coroutines-core/js/src/CoroutineContext.kt b/kotlinx-coroutines-core/js/src/CoroutineContext.kt
index e08345a1d2..a98ea9732d 100644
--- a/kotlinx-coroutines-core/js/src/CoroutineContext.kt
+++ b/kotlinx-coroutines-core/js/src/CoroutineContext.kt
@@ -4,8 +4,8 @@
package kotlinx.coroutines
+import kotlinx.browser.*
import kotlinx.coroutines.internal.*
-import kotlin.browser.*
import kotlin.coroutines.*
private external val navigator: dynamic
@@ -13,12 +13,6 @@ private const val UNDEFINED = "undefined"
internal external val process: dynamic
internal actual fun createDefaultDispatcher(): CoroutineDispatcher = when {
- // Check if we are running under ReactNative. We have to use NodeDispatcher under it.
- // The problem is that ReactNative has a `window` object with `addEventListener`, but it does not really work.
- // For details see https://github.com/Kotlin/kotlinx.coroutines/issues/236
- // The check for ReactNative is based on https://github.com/facebook/react-native/commit/3c65e62183ce05893be0822da217cb803b121c61
- jsTypeOf(navigator) != UNDEFINED && navigator != null && navigator.product == "ReactNative" ->
- NodeDispatcher
// Check if we are running under jsdom. WindowDispatcher doesn't work under jsdom because it accesses MessageEvent#source.
// It is not implemented in jsdom, see https://github.com/jsdom/jsdom/blob/master/Changelog.md
// "It's missing a few semantics, especially around origins, as well as MessageEvent source."
@@ -27,7 +21,7 @@ internal actual fun createDefaultDispatcher(): CoroutineDispatcher = when {
jsTypeOf(window) != UNDEFINED && window.asDynamic() != null && jsTypeOf(window.asDynamic().addEventListener) != UNDEFINED ->
window.asCoroutineDispatcher()
// If process is undefined (e.g. in NativeScript, #1404), use SetTimeout-based dispatcher
- jsTypeOf(process) == UNDEFINED -> SetTimeoutDispatcher
+ jsTypeOf(process) == UNDEFINED || jsTypeOf(process.nextTick) == UNDEFINED -> SetTimeoutDispatcher
// Fallback to NodeDispatcher when browser environment is not detected
else -> NodeDispatcher
}
diff --git a/kotlinx-coroutines-core/js/src/internal/LinkedList.kt b/kotlinx-coroutines-core/js/src/internal/LinkedList.kt
index f2711f50af..147b31dc3e 100644
--- a/kotlinx-coroutines-core/js/src/internal/LinkedList.kt
+++ b/kotlinx-coroutines-core/js/src/internal/LinkedList.kt
@@ -32,7 +32,18 @@ public open class LinkedListNode {
this._prev = node
}
+ /*
+ * Remove that is invoked as a virtual function with a
+ * potentially augmented behaviour.
+ * I.g. `LockFreeLinkedListHead` throws, while `SendElementWithUndeliveredHandler`
+ * invokes handler on remove
+ */
public open fun remove(): Boolean {
+ return removeImpl()
+ }
+
+ @PublishedApi
+ internal fun removeImpl(): Boolean {
if (_removed) return false
val prev = this._prev
val next = this._next
@@ -76,7 +87,7 @@ public open class LinkedListNode {
public fun removeFirstOrNull(): Node? {
val next = _next
if (next === this) return null
- check(next.remove()) { "Should remove" }
+ check(next.removeImpl()) { "Should remove" }
return next
}
@@ -85,7 +96,7 @@ public open class LinkedListNode {
if (next === this) return null
if (next !is T) return null
if (predicate(next)) return next
- check(next.remove()) { "Should remove" }
+ check(next.removeImpl()) { "Should remove" }
return next
}
}
diff --git a/kotlinx-coroutines-core/js/test/PromiseTest.kt b/kotlinx-coroutines-core/js/test/PromiseTest.kt
index d0f6b2b714..cc1297cd78 100644
--- a/kotlinx-coroutines-core/js/test/PromiseTest.kt
+++ b/kotlinx-coroutines-core/js/test/PromiseTest.kt
@@ -74,4 +74,16 @@ class PromiseTest : TestBase() {
assertSame(d2, deferred)
assertEquals("OK", d2.await())
}
-}
\ No newline at end of file
+
+ @Test
+ fun testLeverageTestResult(): TestResult {
+ // Cannot use expect(..) here
+ var seq = 0
+ val result = runTest {
+ ++seq
+ }
+ return result.then {
+ if (seq != 1) error("Unexpected result: $seq")
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/js/test/TestBase.kt b/kotlinx-coroutines-core/js/test/TestBase.kt
index 8b3d69a7f5..cc7865ba07 100644
--- a/kotlinx-coroutines-core/js/test/TestBase.kt
+++ b/kotlinx-coroutines-core/js/test/TestBase.kt
@@ -9,10 +9,15 @@ import kotlin.js.*
public actual val isStressTest: Boolean = false
public actual val stressTestMultiplier: Int = 1
+@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_TO_CLASS_WITH_DECLARATION_SITE_VARIANCE")
+public actual typealias TestResult = Promise
+
public actual open class TestBase actual constructor() {
+ public actual val isBoundByJsTestTimeout = true
private var actionIndex = 0
private var finished = false
private var error: Throwable? = null
+ private var lastTestPromise: Promise<*>? = null
/**
* Throws [IllegalStateException] like `error` in stdlib, but also ensures that the test will not
@@ -70,16 +75,37 @@ public actual open class TestBase actual constructor() {
finished = false
}
- // todo: The dynamic (promise) result is a work-around for missing suspend tests, see KT-22228
@Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS")
public actual fun runTest(
expected: ((Throwable) -> Boolean)? = null,
unhandled: List<(Throwable) -> Boolean> = emptyList(),
block: suspend CoroutineScope.() -> Unit
- ): dynamic {
+ ): TestResult {
var exCount = 0
var ex: Throwable? = null
- return GlobalScope.promise(block = block, context = CoroutineExceptionHandler { context, e ->
+ /*
+ * This is an additional sanity check against `runTest` mis-usage on JS.
+ * The only way to write an async test on JS is to return Promise from the test function.
+ * _Just_ launching promise and returning `Unit` won't suffice as the underlying test framework
+ * won't be able to detect an asynchronous failure in a timely manner.
+ * We cannot detect such situations, but we can detect the most common erroneous pattern
+ * in our code base, an attempt to use multiple `runTest` in the same `@Test` method,
+ * which typically is a premise to the same error:
+ * ```
+ * @Test
+ * fun incorrectTestForJs() { // <- promise is not returned
+ * for (parameter in parameters) {
+ * runTest {
+ * runTestForParameter(parameter)
+ * }
+ * }
+ * }
+ * ```
+ */
+ if (lastTestPromise != null) {
+ error("Attempt to run multiple asynchronous test within one @Test method")
+ }
+ val result = GlobalScope.promise(block = block, context = CoroutineExceptionHandler { _, e ->
if (e is CancellationException) return@CoroutineExceptionHandler // are ignored
exCount++
when {
@@ -102,6 +128,8 @@ public actual open class TestBase actual constructor() {
error?.let { throw it }
check(actionIndex == 0 || finished) { "Expecting that 'finish(...)' was invoked, but it was not" }
}
+ lastTestPromise = result
+ return result
}
}
diff --git a/kotlinx-coroutines-core/jvm/src/channels/Actor.kt b/kotlinx-coroutines-core/jvm/src/channels/Actor.kt
index 96cda7b1af..4657bc7d1e 100644
--- a/kotlinx-coroutines-core/jvm/src/channels/Actor.kt
+++ b/kotlinx-coroutines-core/jvm/src/channels/Actor.kt
@@ -163,7 +163,7 @@ private class LazyActorCoroutine(
return super.send(element)
}
- @Suppress("DEPRECATION_ERROR")
+ @Suppress("DEPRECATION")
override fun offer(element: E): Boolean {
start()
return super.offer(element)
diff --git a/kotlinx-coroutines-core/jvm/src/internal/LockFreeLinkedList.kt b/kotlinx-coroutines-core/jvm/src/internal/LockFreeLinkedList.kt
index caad1e3323..9bbc6dc9eb 100644
--- a/kotlinx-coroutines-core/jvm/src/internal/LockFreeLinkedList.kt
+++ b/kotlinx-coroutines-core/jvm/src/internal/LockFreeLinkedList.kt
@@ -259,7 +259,7 @@ public actual open class LockFreeLinkedListNode {
// Helps with removal of this node
public actual fun helpRemove() {
// Note: this node must be already removed
- (next as Removed).ref.correctPrev(null)
+ (next as Removed).ref.helpRemovePrev()
}
// Helps with removal of nodes that are previous to this
diff --git a/kotlinx-coroutines-core/jvm/src/internal/StackTraceRecovery.kt b/kotlinx-coroutines-core/jvm/src/internal/StackTraceRecovery.kt
index 174c57b762..6153862e2a 100644
--- a/kotlinx-coroutines-core/jvm/src/internal/StackTraceRecovery.kt
+++ b/kotlinx-coroutines-core/jvm/src/internal/StackTraceRecovery.kt
@@ -216,6 +216,7 @@ internal actual typealias CoroutineStackFrame = kotlin.coroutines.jvm.internal.C
@Suppress("ACTUAL_WITHOUT_EXPECT")
internal actual typealias StackTraceElement = java.lang.StackTraceElement
+@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
internal actual fun Throwable.initCause(cause: Throwable) {
// Resolved to member, verified by test
initCause(cause)
diff --git a/kotlinx-coroutines-core/jvm/test/AbstractLincheckTest.kt b/kotlinx-coroutines-core/jvm/test/AbstractLincheckTest.kt
index 2b4e91c02a..89bbbfd7ee 100644
--- a/kotlinx-coroutines-core/jvm/test/AbstractLincheckTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/AbstractLincheckTest.kt
@@ -15,7 +15,7 @@ abstract class AbstractLincheckTest : VerifierState() {
open fun StressOptions.customize(isStressTest: Boolean): StressOptions = this
@Test
- open fun modelCheckingTest() = ModelCheckingOptions()
+ fun modelCheckingTest() = ModelCheckingOptions()
.iterations(if (isStressTest) 100 else 20)
.invocationsPerIteration(if (isStressTest) 10_000 else 1_000)
.commonConfiguration()
diff --git a/kotlinx-coroutines-core/jvm/test/AsyncJvmTest.kt b/kotlinx-coroutines-core/jvm/test/AsyncJvmTest.kt
index 026752086c..59ff76a538 100644
--- a/kotlinx-coroutines-core/jvm/test/AsyncJvmTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/AsyncJvmTest.kt
@@ -8,7 +8,10 @@ import kotlin.test.*
class AsyncJvmTest : TestBase() {
- // This must be a common test but it fails on JS because of KT-21961
+ // We have the same test in common module, but the maintainer uses this particular file
+ // and semi-automatically types cmd+N + AsyncJvm in order to duck-tape any JVM samples/repros,
+ // please do not remove this test
+
@Test
fun testAsyncWithFinally() = runTest {
expect(1)
diff --git a/kotlinx-coroutines-core/jvm/test/FieldWalker.kt b/kotlinx-coroutines-core/jvm/test/FieldWalker.kt
index c4232d6e60..179b2e5e6e 100644
--- a/kotlinx-coroutines-core/jvm/test/FieldWalker.kt
+++ b/kotlinx-coroutines-core/jvm/test/FieldWalker.kt
@@ -11,7 +11,6 @@ import java.util.*
import java.util.Collections.*
import java.util.concurrent.atomic.*
import java.util.concurrent.locks.*
-import kotlin.collections.ArrayList
import kotlin.test.*
object FieldWalker {
@@ -90,6 +89,7 @@ object FieldWalker {
cur = ref.parent
path += "[${ref.index}]"
}
+ else -> error("Should not be reached")
}
}
path.reverse()
@@ -154,8 +154,9 @@ object FieldWalker {
while (true) {
val fields = type.declaredFields.filter {
!it.type.isPrimitive
- && (statics || !Modifier.isStatic(it.modifiers))
- && !(it.type.isArray && it.type.componentType.isPrimitive)
+ && (statics || !Modifier.isStatic(it.modifiers))
+ && !(it.type.isArray && it.type.componentType.isPrimitive)
+ && it.name != "previousOut" // System.out from TestBase that we store in a field to restore later
}
fields.forEach { it.isAccessible = true } // make them all accessible
result.addAll(fields)
diff --git a/kotlinx-coroutines-core/jvm/test/TestBase.kt b/kotlinx-coroutines-core/jvm/test/TestBase.kt
index 17238e873c..61a2c8b8b7 100644
--- a/kotlinx-coroutines-core/jvm/test/TestBase.kt
+++ b/kotlinx-coroutines-core/jvm/test/TestBase.kt
@@ -7,11 +7,10 @@ package kotlinx.coroutines
import kotlinx.coroutines.internal.*
import kotlinx.coroutines.scheduling.*
import org.junit.*
-import java.lang.Math.*
+import java.io.*
import java.util.*
import java.util.concurrent.atomic.*
import kotlin.coroutines.*
-import kotlin.math.*
import kotlin.test.*
private val VERBOSE = systemProp("test.verbose", false)
@@ -23,12 +22,15 @@ public actual val isStressTest = System.getProperty("stressTest")?.toBoolean() ?
public val stressTestMultiplierSqrt = if (isStressTest) 5 else 1
+private const val SHUTDOWN_TIMEOUT = 1_000L // 1s at most to wait per thread
+
/**
* Multiply various constants in stress tests by this factor, so that they run longer during nightly stress test.
*/
public actual val stressTestMultiplier = stressTestMultiplierSqrt * stressTestMultiplierSqrt
-public val stressTestMultiplierCbrt = cbrt(stressTestMultiplier.toDouble()).roundToInt()
+@Suppress("ACTUAL_WITHOUT_EXPECT")
+public actual typealias TestResult = Unit
/**
* Base class for tests, so that tests for predictable scheduling of actions in multiple coroutines sharing a single
@@ -49,7 +51,11 @@ public val stressTestMultiplierCbrt = cbrt(stressTestMultiplier.toDouble()).roun
* }
* ```
*/
-public actual open class TestBase actual constructor() {
+public actual open class TestBase(private var disableOutCheck: Boolean) {
+
+ actual constructor(): this(false)
+
+ public actual val isBoundByJsTestTimeout = false
private var actionIndex = AtomicInteger()
private var finished = AtomicBoolean()
private var error = AtomicReference()
@@ -58,9 +64,15 @@ public actual open class TestBase actual constructor() {
private lateinit var threadsBefore: Set
private val uncaughtExceptions = Collections.synchronizedList(ArrayList())
private var originalUncaughtExceptionHandler: Thread.UncaughtExceptionHandler? = null
- private val SHUTDOWN_TIMEOUT = 1_000L // 1s at most to wait per thread
+ /*
+ * System.out that we redefine in order to catch any debugging/diagnostics
+ * 'println' from main source set.
+ * NB: We do rely on the name 'previousOut' in the FieldWalker in order to skip its
+ * processing
+ */
+ private lateinit var previousOut: PrintStream
- /**
+ /**
* Throws [IllegalStateException] like `error` in stdlib, but also ensures that the test will not
* complete successfully even if this exception is consumed somewhere in the test.
*/
@@ -113,7 +125,7 @@ public actual open class TestBase actual constructor() {
}
/**
- * Asserts that this it the last action in the test. It must be invoked by any test that used [expect].
+ * Asserts that this is the last action in the test. It must be invoked by any test that used [expect].
*/
public actual fun finish(index: Int) {
expect(index)
@@ -133,6 +145,16 @@ public actual open class TestBase actual constructor() {
finished.set(false)
}
+ private object TestOutputStream : PrintStream(object : OutputStream() {
+ override fun write(b: Int) {
+ error("Detected unexpected call to 'println' from source code")
+ }
+ })
+
+ fun println(message: Any?) {
+ previousOut.println(message)
+ }
+
@Before
fun before() {
initPoolsBeforeTest()
@@ -143,6 +165,10 @@ public actual open class TestBase actual constructor() {
e.printStackTrace()
uncaughtExceptions.add(e)
}
+ if (!disableOutCheck) {
+ previousOut = System.out
+ System.setOut(TestOutputStream)
+ }
}
@After
@@ -154,7 +180,7 @@ public actual open class TestBase actual constructor() {
}
// Shutdown all thread pools
shutdownPoolsAfterTest()
- // Check that that are now leftover threads
+ // Check that are now leftover threads
runCatching {
checkTestThreads(threadsBefore)
}.onFailure {
@@ -162,6 +188,9 @@ public actual open class TestBase actual constructor() {
}
// Restore original uncaught exception handler
Thread.setDefaultUncaughtExceptionHandler(originalUncaughtExceptionHandler)
+ if (!disableOutCheck) {
+ System.setOut(previousOut)
+ }
if (uncaughtExceptions.isNotEmpty()) {
makeError("Expected no uncaught exceptions, but got $uncaughtExceptions")
}
@@ -187,7 +216,7 @@ public actual open class TestBase actual constructor() {
expected: ((Throwable) -> Boolean)? = null,
unhandled: List<(Throwable) -> Boolean> = emptyList(),
block: suspend CoroutineScope.() -> Unit
- ) {
+ ): TestResult {
var exCount = 0
var ex: Throwable? = null
try {
diff --git a/kotlinx-coroutines-core/jvm/test/channels/ChannelLFStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ChannelLFStressTest.kt
deleted file mode 100644
index 256ef62132..0000000000
--- a/kotlinx-coroutines-core/jvm/test/channels/ChannelLFStressTest.kt
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.coroutines.channels
-
-import kotlinx.atomicfu.*
-import kotlinx.coroutines.*
-import java.util.concurrent.atomic.AtomicLong
-import java.util.concurrent.atomic.AtomicLongArray
-import kotlin.math.*
-import kotlin.test.*
-
-/**
- * Tests lock-freedom of send and receive operations on rendezvous and conflated channels.
- * There is a single channel with two sender and two receiver threads.
- * When one sender or receiver gets suspended at most one other operation is allowed to cease having progress
- * (`allowSuspendedThreads = 1`).
- *
- * **Note**: In the current implementation buffered channels are not lock-free, so this test would fail
- * if channel is created with a buffer.
- */
-class ChannelLFStressTest : TestBase() {
- private val nSeconds = 5 * stressTestMultiplier
- private val env = LockFreedomTestEnvironment("ChannelLFStressTest", allowSuspendedThreads = 1)
- private lateinit var channel: Channel
-
- private val sendIndex = AtomicLong()
- private val receiveCount = AtomicLong()
- private val duplicateCount = AtomicLong()
-
- private val nCheckedSize = 10_000_000
- private val nChecked = (nCheckedSize * Long.SIZE_BITS).toLong()
- private val receivedBits = AtomicLongArray(nCheckedSize) // bit set of received values
-
- @Test
- fun testRendezvousLockFreedom() {
- channel = Channel()
- performLockFreedomTest()
- // ensure that all sent were received
- checkAllReceived()
- }
-
- private fun performLockFreedomTest() {
- env.onCompletion {
- // We must cancel the channel to abort both senders & receivers
- channel.cancel(TestCompleted())
- }
- repeat(2) { env.testThread("sender-$it") { sender() } }
- repeat(2) { env.testThread("receiver-$it") { receiver() } }
- env.performTest(nSeconds) {
- println("Sent: $sendIndex, Received: $receiveCount, dups: $duplicateCount")
- }
- // ensure no duplicates
- assertEquals(0L, duplicateCount.get())
- }
-
- private fun checkAllReceived() {
- for (i in 0 until min(sendIndex.get(), nChecked)) {
- assertTrue(isReceived(i))
- }
- }
-
- private suspend fun sender() {
- val value = sendIndex.getAndIncrement()
- try {
- channel.send(value)
- } catch (e: TestCompleted) {
- check(env.isCompleted) // expected when test was completed
- markReceived(value) // fake received (actually failed to send)
- }
- }
-
- private suspend fun receiver() {
- val value = try {
- channel.receive()
- } catch (e: TestCompleted) {
- check(env.isCompleted) // expected when test was completed
- return
- }
- receiveCount.incrementAndGet()
- markReceived(value)
- }
-
- private fun markReceived(value: Long) {
- if (value >= nChecked) return // too big
- val index = (value / Long.SIZE_BITS).toInt()
- val mask = 1L shl (value % Long.SIZE_BITS).toInt()
- while (true) {
- val bits = receivedBits.get(index)
- if (bits and mask != 0L) {
- duplicateCount.incrementAndGet()
- break
- }
- if (receivedBits.compareAndSet(index, bits, bits or mask)) break
- }
- }
-
- private fun isReceived(value: Long): Boolean {
- val index = (value / Long.SIZE_BITS).toInt()
- val mask = 1L shl (value % Long.SIZE_BITS).toInt()
- val bits = receivedBits.get(index)
- return bits and mask != 0L
- }
-
- private class TestCompleted : CancellationException()
-}
diff --git a/kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListAtomicLFStressTest.kt b/kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListAtomicLFStressTest.kt
deleted file mode 100644
index 225b848186..0000000000
--- a/kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListAtomicLFStressTest.kt
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.coroutines.internal
-
-import kotlinx.atomicfu.LockFreedomTestEnvironment
-import kotlinx.coroutines.stressTestMultiplier
-import org.junit.Test
-import java.util.*
-import java.util.concurrent.atomic.AtomicLong
-import java.util.concurrent.atomic.AtomicReference
-import kotlin.test.*
-
-/**
- * This stress test has 4 threads adding randomly to the list and them immediately undoing
- * this addition by remove, and 4 threads trying to remove nodes from two lists simultaneously (atomically).
- */
-class LockFreeLinkedListAtomicLFStressTest {
- private val env = LockFreedomTestEnvironment("LockFreeLinkedListAtomicLFStressTest")
-
- private data class Node(val i: Long) : LockFreeLinkedListNode()
-
- private val nSeconds = 5 * stressTestMultiplier
-
- private val nLists = 4
- private val nAdderThreads = 4
- private val nRemoverThreads = 4
-
- private val lists = Array(nLists) { LockFreeLinkedListHead() }
-
- private val undone = AtomicLong()
- private val missed = AtomicLong()
- private val removed = AtomicLong()
- private val error = AtomicReference()
- private val index = AtomicLong()
-
- @Test
- fun testStress() {
- repeat(nAdderThreads) { threadId ->
- val rnd = Random()
- env.testThread(name = "adder-$threadId") {
- when (rnd.nextInt(4)) {
- 0 -> {
- val list = lists[rnd.nextInt(nLists)]
- val node = Node(index.incrementAndGet())
- addLastOp(list, node)
- randomSpinWaitIntermission()
- tryRemoveOp(node)
- }
- 1 -> {
- // just to test conditional add
- val list = lists[rnd.nextInt(nLists)]
- val node = Node(index.incrementAndGet())
- addLastIfTrueOp(list, node)
- randomSpinWaitIntermission()
- tryRemoveOp(node)
- }
- 2 -> {
- // just to test failed conditional add and burn some time
- val list = lists[rnd.nextInt(nLists)]
- val node = Node(index.incrementAndGet())
- addLastIfFalseOp(list, node)
- }
- 3 -> {
- // add two atomically
- val idx1 = rnd.nextInt(nLists - 1)
- val idx2 = idx1 + 1 + rnd.nextInt(nLists - idx1 - 1)
- check(idx1 < idx2) // that is our global order
- val list1 = lists[idx1]
- val list2 = lists[idx2]
- val node1 = Node(index.incrementAndGet())
- val node2 = Node(index.incrementAndGet())
- addTwoOp(list1, node1, list2, node2)
- randomSpinWaitIntermission()
- tryRemoveOp(node1)
- randomSpinWaitIntermission()
- tryRemoveOp(node2)
- }
- else -> error("Cannot happen")
- }
- }
- }
- repeat(nRemoverThreads) { threadId ->
- val rnd = Random()
- env.testThread(name = "remover-$threadId") {
- val idx1 = rnd.nextInt(nLists - 1)
- val idx2 = idx1 + 1 + rnd.nextInt(nLists - idx1 - 1)
- check(idx1 < idx2) // that is our global order
- val list1 = lists[idx1]
- val list2 = lists[idx2]
- removeTwoOp(list1, list2)
- }
- }
- env.performTest(nSeconds) {
- val undone = undone.get()
- val missed = missed.get()
- val removed = removed.get()
- println(" Adders undone $undone node additions")
- println(" Adders missed $missed nodes")
- println("Remover removed $removed nodes")
- }
- error.get()?.let { throw it }
- assertEquals(missed.get(), removed.get())
- assertTrue(undone.get() > 0)
- assertTrue(missed.get() > 0)
- lists.forEach { it.validate() }
- }
-
- private fun addLastOp(list: LockFreeLinkedListHead, node: Node) {
- list.addLast(node)
- }
-
- private fun addLastIfTrueOp(list: LockFreeLinkedListHead, node: Node) {
- assertTrue(list.addLastIf(node) { true })
- }
-
- private fun addLastIfFalseOp(list: LockFreeLinkedListHead, node: Node) {
- assertFalse(list.addLastIf(node) { false })
- }
-
- private fun addTwoOp(list1: LockFreeLinkedListHead, node1: Node, list2: LockFreeLinkedListHead, node2: Node) {
- val add1 = list1.describeAddLast(node1)
- val add2 = list2.describeAddLast(node2)
- val op = object : AtomicOp() {
- init {
- add1.atomicOp = this
- add2.atomicOp = this
- }
- override fun prepare(affected: Any?): Any? =
- add1.prepare(this) ?:
- add2.prepare(this)
-
- override fun complete(affected: Any?, failure: Any?) {
- add1.complete(this, failure)
- add2.complete(this, failure)
- }
- }
- assertTrue(op.perform(null) == null)
- }
-
- private fun tryRemoveOp(node: Node) {
- if (node.remove())
- undone.incrementAndGet()
- else
- missed.incrementAndGet()
- }
-
- private fun removeTwoOp(list1: LockFreeLinkedListHead, list2: LockFreeLinkedListHead) {
- val remove1 = list1.describeRemoveFirst()
- val remove2 = list2.describeRemoveFirst()
- val op = object : AtomicOp() {
- init {
- remove1.atomicOp = this
- remove2.atomicOp = this
- }
- override fun prepare(affected: Any?): Any? =
- remove1.prepare(this) ?:
- remove2.prepare(this)
-
- override fun complete(affected: Any?, failure: Any?) {
- remove1.complete(this, failure)
- remove2.complete(this, failure)
- }
- }
- val success = op.perform(null) == null
- if (success) removed.addAndGet(2)
- }
-}
\ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/lincheck/MutexLincheckTest.kt b/kotlinx-coroutines-core/jvm/test/lincheck/MutexLincheckTest.kt
index f7f59eef5e..a278985fdd 100644
--- a/kotlinx-coroutines-core/jvm/test/lincheck/MutexLincheckTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/lincheck/MutexLincheckTest.kt
@@ -9,15 +9,10 @@ import kotlinx.coroutines.sync.*
import org.jetbrains.kotlinx.lincheck.*
import org.jetbrains.kotlinx.lincheck.annotations.Operation
import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.*
-import org.junit.*
class MutexLincheckTest : AbstractLincheckTest() {
private val mutex = Mutex()
- override fun modelCheckingTest() {
- // Ignored via empty body as the only way
- }
-
@Operation
fun tryLock() = mutex.tryLock()
diff --git a/kotlinx-coroutines-core/jvm/test/selects/SelectDeadlockLFStressTest.kt b/kotlinx-coroutines-core/jvm/test/selects/SelectDeadlockLFStressTest.kt
deleted file mode 100644
index 4497bec5b9..0000000000
--- a/kotlinx-coroutines-core/jvm/test/selects/SelectDeadlockLFStressTest.kt
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.coroutines.selects
-
-import kotlinx.atomicfu.*
-import kotlinx.coroutines.*
-import kotlinx.coroutines.channels.*
-import org.junit.*
-import org.junit.Ignore
-import org.junit.Test
-import kotlin.math.*
-import kotlin.test.*
-
-/**
- * A stress-test on lock-freedom of select sending/receiving into opposite channels.
- */
-class SelectDeadlockLFStressTest : TestBase() {
- private val env = LockFreedomTestEnvironment("SelectDeadlockLFStressTest", allowSuspendedThreads = 1)
- private val nSeconds = 5 * stressTestMultiplier
-
- private val c1 = Channel()
- private val c2 = Channel()
-
- @Test
- fun testLockFreedom() = testScenarios(
- "s1r2",
- "s2r1",
- "r1s2",
- "r2s1"
- )
-
- private fun testScenarios(vararg scenarios: String) {
- env.onCompletion {
- c1.cancel(TestCompleted())
- c2.cancel(TestCompleted())
- }
- val t = scenarios.mapIndexed { i, scenario ->
- val idx = i + 1L
- TestDef(idx, "$idx [$scenario]", scenario)
- }
- t.forEach { it.test() }
- env.performTest(nSeconds) {
- t.forEach { println(it) }
- }
- }
-
- private inner class TestDef(
- var sendIndex: Long = 0L,
- val name: String,
- scenario: String
- ) {
- var receiveIndex = 0L
-
- val clauses: List.() -> Unit> = ArrayList.() -> Unit>().apply {
- require(scenario.length % 2 == 0)
- for (i in scenario.indices step 2) {
- val ch = when (val c = scenario[i + 1]) {
- '1' -> c1
- '2' -> c2
- else -> error("Channel '$c'")
- }
- val clause = when (val op = scenario[i]) {
- 's' -> fun SelectBuilder.() { sendClause(ch) }
- 'r' -> fun SelectBuilder.() { receiveClause(ch) }
- else -> error("Operation '$op'")
- }
- add(clause)
- }
- }
-
- fun test() = env.testThread(name) {
- doSendReceive()
- }
-
- private suspend fun doSendReceive() {
- try {
- select {
- for (clause in clauses) clause()
- }
- } catch (e: TestCompleted) {
- assertTrue(env.isCompleted)
- }
- }
-
- private fun SelectBuilder.sendClause(c: Channel) =
- c.onSend(sendIndex) {
- sendIndex += 4L
- }
-
- private fun SelectBuilder.receiveClause(c: Channel) =
- c.onReceive { i ->
- receiveIndex = max(i, receiveIndex)
- }
-
- override fun toString(): String = "$name: send=$sendIndex, received=$receiveIndex"
- }
-
- private class TestCompleted : CancellationException()
-}
\ No newline at end of file
diff --git a/kotlinx-coroutines-core/native/test/TestBase.kt b/kotlinx-coroutines-core/native/test/TestBase.kt
index 890f029ca2..4ffa6c0b11 100644
--- a/kotlinx-coroutines-core/native/test/TestBase.kt
+++ b/kotlinx-coroutines-core/native/test/TestBase.kt
@@ -7,7 +7,11 @@ package kotlinx.coroutines
public actual val isStressTest: Boolean = false
public actual val stressTestMultiplier: Int = 1
+@Suppress("ACTUAL_WITHOUT_EXPECT")
+public actual typealias TestResult = Unit
+
public actual open class TestBase actual constructor() {
+ public actual val isBoundByJsTestTimeout = false
private var actionIndex = 0
private var finished = false
private var error: Throwable? = null
@@ -70,7 +74,7 @@ public actual open class TestBase actual constructor() {
expected: ((Throwable) -> Boolean)? = null,
unhandled: List<(Throwable) -> Boolean> = emptyList(),
block: suspend CoroutineScope.() -> Unit
- ) {
+ ): TestResult {
var exCount = 0
var ex: Throwable? = null
try {
diff --git a/kotlinx-coroutines-debug/README.md b/kotlinx-coroutines-debug/README.md
index f7b8602236..cd71f580f0 100644
--- a/kotlinx-coroutines-debug/README.md
+++ b/kotlinx-coroutines-debug/README.md
@@ -61,7 +61,7 @@ stacktraces will be dumped to the console.
### Using as JVM agent
Debug module can also be used as a standalone JVM agent to enable debug probes on the application startup.
-You can run your application with an additional argument: `-javaagent:kotlinx-coroutines-debug-1.5.1.jar`.
+You can run your application with an additional argument: `-javaagent:kotlinx-coroutines-debug-1.5.2.jar`.
Additionally, on Linux and Mac OS X you can use `kill -5 $pid` command in order to force your application to print all alive coroutines.
When used as Java agent, `"kotlinx.coroutines.debug.enable.creation.stack.trace"` system property can be used to control
[DebugProbes.enableCreationStackTraces] along with agent startup.
diff --git a/kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutDisabledTracesTest.kt b/kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutDisabledTracesTest.kt
index 886007c3e8..2063090c82 100644
--- a/kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutDisabledTracesTest.kt
+++ b/kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutDisabledTracesTest.kt
@@ -8,7 +8,7 @@ import kotlinx.coroutines.*
import org.junit.*
import org.junit.runners.model.*
-class CoroutinesTimeoutDisabledTracesTest : TestBase() {
+class CoroutinesTimeoutDisabledTracesTest : TestBase(disableOutCheck = true) {
@Rule
@JvmField
diff --git a/kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutEagerTest.kt b/kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutEagerTest.kt
index 0845f5bcb3..7a686ff2a7 100644
--- a/kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutEagerTest.kt
+++ b/kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutEagerTest.kt
@@ -8,7 +8,7 @@ import kotlinx.coroutines.*
import org.junit.*
import org.junit.runners.model.*
-class CoroutinesTimeoutEagerTest : TestBase() {
+class CoroutinesTimeoutEagerTest : TestBase(disableOutCheck = true) {
@Rule
@JvmField
diff --git a/kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutTest.kt b/kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutTest.kt
index ac3408e20a..53447ac5f9 100644
--- a/kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutTest.kt
+++ b/kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutTest.kt
@@ -8,7 +8,7 @@ import kotlinx.coroutines.*
import org.junit.*
import org.junit.runners.model.*
-class CoroutinesTimeoutTest : TestBase() {
+class CoroutinesTimeoutTest : TestBase(disableOutCheck = true) {
@Rule
@JvmField
diff --git a/kotlinx-coroutines-debug/test/junit4/TestFailureValidation.kt b/kotlinx-coroutines-debug/test/junit4/TestFailureValidation.kt
index 34ba679adb..6d25a6da7d 100644
--- a/kotlinx-coroutines-debug/test/junit4/TestFailureValidation.kt
+++ b/kotlinx-coroutines-debug/test/junit4/TestFailureValidation.kt
@@ -104,6 +104,6 @@ internal class TestFailureValidation(private val testsSpec: Map = listOf(), val notExpectedOutParts:
- List = listOf(), val error: Class? = null
+ val testName: String, val expectedOutParts: List = listOf(),
+ val notExpectedOutParts: List = listOf(), val error: Class? = null
)
diff --git a/kotlinx-coroutines-test/README.md b/kotlinx-coroutines-test/README.md
index 622e81d50b..43ae18f532 100644
--- a/kotlinx-coroutines-test/README.md
+++ b/kotlinx-coroutines-test/README.md
@@ -9,7 +9,7 @@ This package provides testing utilities for effectively testing coroutines.
Add `kotlinx-coroutines-test` to your project test dependencies:
```
dependencies {
- testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.1'
+ testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.2'
}
```
diff --git a/ui/coroutines-guide-ui.md b/ui/coroutines-guide-ui.md
index 408a43d1e1..71b2d69c5c 100644
--- a/ui/coroutines-guide-ui.md
+++ b/ui/coroutines-guide-ui.md
@@ -110,7 +110,7 @@ Add dependencies on `kotlinx-coroutines-android` module to the `dependencies { .
`app/build.gradle` file:
```groovy
-implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1"
+implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2"
```
You can clone [kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) project from GitHub onto your