Skip to content

Commit 262876b

Browse files
committed
Merge branch 'master' into develop
2 parents a5dd74b + 0d26d6c commit 262876b

File tree

10 files changed

+166
-52
lines changed

10 files changed

+166
-52
lines changed

CHANGES.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Version 1.6.0
44

5-
Note that this is a full changelog relative to 1.5.2 version. Changelog relative to 1.6.0-RC3 can be found in the end.
5+
Note that this is a full changelog relative to the 1.5.2 version. Changelog relative to 1.6.0-RC3 can be found at the end.
66

77
### kotlinx-coroutines-test rework
88

@@ -25,6 +25,7 @@ Note that this is a full changelog relative to 1.5.2 version. Changelog relative
2525
* `Dispatchers.Main` is now used as the default source of time for `delay` and `withTimeout` when present(#2972).
2626
* To opt-out from this behaviour, `kotlinx.coroutines.main.delay` system property can be set to `false`.
2727
* Java target of coroutines build is now 8 instead of 6 (#1589).
28+
* **Source-breaking change**: extension `collect` no longer resolves when used with a non-in-place argument of a functional type. This is a candidate for a fix, uncovered after 1.6.0, see #3107 for the additional details.
2829

2930
### Bug fixes and improvements
3031

README.md

+17-31
Original file line numberDiff line numberDiff line change
@@ -99,55 +99,39 @@ And make sure that you use the latest Kotlin version:
9999

100100
Add dependencies (you can also add other modules that you need):
101101

102-
```groovy
102+
```kotlin
103103
dependencies {
104-
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0'
104+
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
105105
}
106106
```
107107

108108
And make sure that you use the latest Kotlin version:
109109

110-
```groovy
111-
buildscript {
112-
ext.kotlin_version = '1.6.0'
110+
```kotlin
111+
plugins {
112+
// For build.gradle.kts (Kotlin DSL)
113+
kotlin("jvm") version "1.6.0"
114+
115+
// For build.gradle (Groovy DSL)
116+
id "org.jetbrains.kotlin.jvm" version "1.6.0"
113117
}
114118
```
115119

116120
Make sure that you have `mavenCentral()` in the list of repositories:
117121

118-
```
119-
repository {
122+
```kotlin
123+
repositories {
120124
mavenCentral()
121125
}
122126
```
123127

124-
### Gradle Kotlin DSL
125-
126-
Add dependencies (you can also add other modules that you need):
127-
128-
```groovy
129-
dependencies {
130-
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
131-
}
132-
```
133-
134-
And make sure that you use the latest Kotlin version:
135-
136-
```groovy
137-
plugins {
138-
kotlin("jvm") version "1.5.30"
139-
}
140-
```
141-
142-
Make sure that you have `mavenCentral()` in the list of repositories.
143-
144128
### Android
145129

146130
Add [`kotlinx-coroutines-android`](ui/kotlinx-coroutines-android)
147131
module as a dependency when using `kotlinx.coroutines` on Android:
148132

149-
```groovy
150-
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0'
133+
```kotlin
134+
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0")
151135
```
152136

153137
This gives you access to the Android [Dispatchers.Main]
@@ -165,7 +149,8 @@ For more details see ["Optimization" section for Android](ui/kotlinx-coroutines-
165149
The `kotlinx-coroutines-core` artifact contains a resource file that is not required for the coroutines to operate
166150
normally and is only used by the debugger. To exclude it at no loss of functionality, add the following snippet to the
167151
`android` block in your Gradle file for the application subproject:
168-
```groovy
152+
153+
```kotlin
169154
packagingOptions {
170155
resources.excludes += "DebugProbesKt.bin"
171156
}
@@ -177,7 +162,8 @@ Core modules of `kotlinx.coroutines` are also available for
177162
[Kotlin/JS](https://kotlinlang.org/docs/reference/js-overview.html) and [Kotlin/Native](https://kotlinlang.org/docs/reference/native-overview.html).
178163

179164
In common code that should get compiled for different platforms, you can add a dependency to `kotlinx-coroutines-core` right to the `commonMain` source set:
180-
```groovy
165+
166+
```kotlin
181167
commonMain {
182168
dependencies {
183169
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")

docs/topics/coroutine-context-and-dispatchers.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ However, this parent-child relation can be explicitly overriden in one of two wa
306306

307307
1. When a different scope is explicitly specified when launching a coroutine (for example, `GlobalScope.launch`),
308308
then it does not inherit a `Job` from the parent scope.
309-
2. When a different `Job` object is passed as the context for the new coroutine (as show in the example below),
309+
2. When a different `Job` object is passed as the context for the new coroutine (as shown in the example below),
310310
then it overrides the `Job` of the parent scope.
311311

312312
In both cases, the launched coroutine is not tied to the scope it was launched from and operates independently.

kotlinx-coroutines-core/common/src/Await.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ public suspend fun <T> awaitAll(vararg deferreds: Deferred<T>): List<T> =
2929
* when all deferred computations are complete or resumes with the first thrown exception if any of computations
3030
* complete exceptionally including cancellation.
3131
*
32-
* This function is **not** equivalent to `this.map { it.await() }` which fails only when when it sequentially
33-
* gets to wait the failing deferred, while this `awaitAll` fails immediately as soon as any of the deferreds fail.
32+
* This function is **not** equivalent to `this.map { it.await() }` which fails only when it sequentially
33+
* gets to wait for the failing deferred, while this `awaitAll` fails immediately as soon as any of the deferreds fail.
3434
*
3535
* This suspending function is cancellable.
3636
* If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting,

kotlinx-coroutines-core/common/src/CoroutineScope.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ public val CoroutineScope.isActive: Boolean
178178
* ```
179179
* // concurrently load configuration and data
180180
* suspend fun loadConfigurationAndData() {
181-
* coroutinesScope {
181+
* coroutineScope {
182182
* launch { loadConfiguration() }
183183
* launch { loadData() }
184184
* }

kotlinx-coroutines-core/common/src/Dispatchers.common.kt

+6-6
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ public expect object Dispatchers {
2626
*
2727
* Access to this property may throw an [IllegalStateException] if no main dispatchers are present in the classpath.
2828
*
29-
* Depending on platform and classpath it can be mapped to different dispatchers:
29+
* Depending on platform and classpath, it can be mapped to different dispatchers:
3030
* - On JS and Native it is equivalent to the [Default] dispatcher.
31-
* - On JVM it either the Android main thread dispatcher, JavaFx or Swing EDT dispatcher. It is chosen by the
31+
* - On JVM it is either the Android main thread dispatcher, JavaFx or Swing EDT dispatcher. It is chosen by the
3232
* [`ServiceLoader`](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html).
3333
*
3434
* In order to work with the `Main` dispatcher, the following artifact should be added to the project runtime dependencies:
@@ -48,7 +48,7 @@ public expect object Dispatchers {
4848
* stack overflows.
4949
*
5050
* ### Event loop
51-
* Event loop semantics is a purely internal concept and have no guarantees on the order of execution
51+
* Event loop semantics is a purely internal concept and has no guarantees on the order of execution
5252
* except that all queued coroutines will be executed on the current thread in the lexical scope of the outermost
5353
* unconfined coroutine.
5454
*
@@ -63,11 +63,11 @@ public expect object Dispatchers {
6363
* }
6464
* println("Done")
6565
* ```
66-
* Can print both "1 2 3" and "1 3 2", this is an implementation detail that can be changed.
67-
* But it is guaranteed that "Done" will be printed only when both `withContext` calls are completed.
66+
* Can print both "1 2 3" and "1 3 2". This is an implementation detail that can be changed.
67+
* However, it is guaranteed that "Done" will be printed only when both `withContext` calls are completed.
6868
*
6969
* If you need your coroutine to be confined to a particular thread or a thread-pool after resumption,
70-
* but still want to execute it in the current call-frame until its first suspension, then you can use
70+
* but still want to execute it in the current call-frame until its first suspension, you can use
7171
* an optional [CoroutineStart] parameter in coroutine builders like
7272
* [launch][CoroutineScope.launch] and [async][CoroutineScope.async] setting it to
7373
* the value of [CoroutineStart.UNDISPATCHED].

kotlinx-coroutines-core/common/src/channels/Channel.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -684,7 +684,7 @@ public interface ChannelIterator<out E> {
684684
* exception which is either rethrown from the caller method or handed off to the exception handler in the current context
685685
* (see [CoroutineExceptionHandler]) when one is available.
686686
*
687-
* A typical usage for `onDeliveredElement` is to close a resource that is being transferred via the channel. The
687+
* A typical usage for `onUndeliveredElement` is to close a resource that is being transferred via the channel. The
688688
* following code pattern guarantees that opened resources are closed even if producer, consumer, and/or channel
689689
* are cancelled. Resources are never lost.
690690
*

kotlinx-coroutines-test/MIGRATION.md

+130-8
Original file line numberDiff line numberDiff line change
@@ -146,18 +146,140 @@ No action on your part is required, other than replacing `runBlocking` with `run
146146

147147
By now, calls to `pauseDispatcher` and `resumeDispatcher` should be purged from the code base, so only the unpaused
148148
variant of `TestCoroutineDispatcher` should be used.
149-
This version of the dispatcher, which can be observed has the property of eagerly entering `launch` and `async` blocks:
149+
This version of the dispatcher has the property of eagerly entering `launch` and `async` blocks:
150150
code until the first suspension is executed without dispatching.
151151

152-
We ensured sure that, when run with an `UnconfinedTestDispatcher`, `runTest` also eagerly enters `launch` and `async`
153-
blocks, but *this only works at the top level*: if a child coroutine also called `launch` or `async`, we don't provide
152+
There are two common ways in which this property is useful.
153+
154+
#### `TestCoroutineDispatcher` for the top-level coroutine
155+
156+
Some tests that rely on `launch` and `async` blocks being entered immediately have a form similar to this:
157+
```kotlin
158+
runTest(TestCoroutineDispatcher()) {
159+
launch {
160+
updateSomething()
161+
}
162+
checkThatSomethingWasUpdated()
163+
launch {
164+
updateSomethingElse()
165+
}
166+
checkThatSomethingElseWasUpdated()
167+
}
168+
```
169+
170+
If the `TestCoroutineDispatcher()` is simply removed, `StandardTestDispatcher()` will be used, which will cause
171+
the test to fail.
172+
173+
In these cases, `UnconfinedTestDispatcher()` should be used.
174+
We ensured that, when run with an `UnconfinedTestDispatcher`, `runTest` also eagerly enters `launch` and `async`
175+
blocks.
176+
177+
Note though that *this only works at the top level*: if a child coroutine also called `launch` or `async`, we don't provide
154178
any guarantees about their dispatching order.
155179

156-
So, using `UnconfinedTestDispatcher` as an argument to `runTest` will probably lead to the test being executed as it
157-
did, but in the possible case that the test relies on the specific dispatching order of `TestCoroutineDispatcher`, it
158-
will need to be tweaked.
159-
If the test expects some code to have run at some point, but it hasn't, use `runCurrent` to force the tasks scheduled
180+
#### `TestCoroutineDispatcher` for testing intermediate emissions
181+
182+
Some code tests `StateFlow` or channels in a manner similar to this:
183+
184+
```kotlin
185+
@Test
186+
fun testAllEmissions() = runTest(TestCoroutineDispatcher()) {
187+
val values = mutableListOf<Int>()
188+
val stateFlow = MutableStateFlow(0)
189+
val job = launch {
190+
stateFlow.collect {
191+
values.add(it)
192+
}
193+
}
194+
stateFlow.value = 1
195+
stateFlow.value = 2
196+
stateFlow.value = 3
197+
job.cancel()
198+
// each assignment will immediately resume the collecting child coroutine,
199+
// so no values will be skipped.
200+
assertEquals(listOf(0, 1, 2, 3), values)
201+
}
202+
```
203+
204+
Such code will fail when `TestCoroutineDispatcher()` is not used: not every emission will be listed.
205+
In this particular case, none will be listed at all.
206+
207+
The reason for this is that setting `stateFlow.value` (as is sending to a channel, as are some other things) wakes up
208+
the coroutine waiting for the new value, but *typically* does not immediately run the collecting code, instead simply
209+
dispatching it.
210+
The exceptions are the coroutines running in dispatchers that don't (always) go through a dispatch,
211+
`Dispatchers.Unconfined`, `Dispatchers.Main.immediate`, `UnconfinedTestDispatcher`, or `TestCoroutineDispatcher` in
212+
the unpaused state.
213+
214+
Therefore, a solution is to launch the collection in an unconfined dispatcher:
215+
216+
```kotlin
217+
@Test
218+
fun testAllEmissions() = runTest {
219+
val values = mutableListOf<Int>()
220+
val stateFlow = MutableStateFlow(0)
221+
val job = launch(UnconfinedTestDispatcher(testScheduler)) { // <------
222+
stateFlow.collect {
223+
values.add(it)
224+
}
225+
}
226+
stateFlow.value = 1
227+
stateFlow.value = 2
228+
stateFlow.value = 3
229+
job.cancel()
230+
// each assignment will immediately resume the collecting child coroutine,
231+
// so no values will be skipped.
232+
assertEquals(listOf(0, 1, 2, 3), values)
233+
}
234+
```
235+
236+
Note that `testScheduler` is passed so that the unconfined dispatcher is linked to `runTest`.
237+
Also, note that `UnconfinedTestDispatcher` is not passed to `runTest`.
238+
This is due to the fact that, *inside* the `UnconfinedTestDispatcher`, there are no execution order guarantees,
239+
so it would not be guaranteed that setting `stateFlow.value` would immediately run the collecting code
240+
(though in this case, it does).
241+
242+
#### Other considerations
243+
244+
Using `UnconfinedTestDispatcher` as an argument to `runTest` will probably lead to the test being executed as it
245+
did, but it's still possible that the test relies on the specific dispatching order of `TestCoroutineDispatcher`,
246+
so it will need to be tweaked.
247+
248+
If some code is expected to have run at some point, but it hasn't, use `runCurrent` to force the tasks scheduled
160249
at this moment of time to run.
250+
For example, the `StateFlow` example above can also be forced to succeed by doing this:
251+
252+
```kotlin
253+
@Test
254+
fun testAllEmissions() = runTest {
255+
val values = mutableListOf<Int>()
256+
val stateFlow = MutableStateFlow(0)
257+
val job = launch {
258+
stateFlow.collect {
259+
values.add(it)
260+
}
261+
}
262+
runCurrent()
263+
stateFlow.value = 1
264+
runCurrent()
265+
stateFlow.value = 2
266+
runCurrent()
267+
stateFlow.value = 3
268+
runCurrent()
269+
job.cancel()
270+
// each assignment will immediately resume the collecting child coroutine,
271+
// so no values will be skipped.
272+
assertEquals(listOf(0, 1, 2, 3), values)
273+
}
274+
```
275+
276+
Be wary though of this approach: using `runCurrent`, `advanceTimeBy`, or `advanceUntilIdle` is, essentially,
277+
simulating some particular execution order, which is not guaranteed to happen in production code.
278+
For example, using `UnconfinedTestDispatcher` to fix this test reflects how, in production code, one could use
279+
`Dispatchers.Unconfined` to observe all emitted values without conflation, but the `runCurrent()` approach only
280+
states that the behavior would be observed if a dispatch were to happen at some chosen points.
281+
It is, therefore, recommended to structure tests in a way that does not rely on a particular interleaving, unless
282+
that is the intention.
161283

162284
### The job hierarchy is completely different.
163285

@@ -322,4 +444,4 @@ fun testFoo() = runTest {
322444
```
323445

324446
The reason this works is that all entities that depend on `TestCoroutineScheduler` will attempt to acquire one from
325-
the current `Dispatchers.Main`.
447+
the current `Dispatchers.Main`.

kotlinx-coroutines-test/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ This package provides utilities for efficiently testing coroutines.
1111
| [runTest] | Runs the test code, automatically skipping delays and handling uncaught exceptions. |
1212
| [TestCoroutineScheduler] | The shared source of virtual time, used for controlling execution order and skipping delays. |
1313
| [TestScope] | A [CoroutineScope] that integrates with [runTest], providing access to [TestCoroutineScheduler]. |
14-
| [TestDispatcher] | A [CoroutineDispatcher] that whose delays are controlled by a [TestCoroutineScheduler]. |
14+
| [TestDispatcher] | A [CoroutineDispatcher] whose delays are controlled by a [TestCoroutineScheduler]. |
1515
| [Dispatchers.setMain] | Mocks the main dispatcher using the provided one. If mocked with a [TestDispatcher], its [TestCoroutineScheduler] is used everywhere by default. |
1616

1717
Provided [TestDispatcher] implementations:

kotlinx-coroutines-test/common/src/TestDispatcher.kt

+5
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ import kotlin.jvm.*
1010

1111
/**
1212
* A test dispatcher that can interface with a [TestCoroutineScheduler].
13+
*
14+
* The available implementations are:
15+
* * [StandardTestDispatcher] is a dispatcher that places new tasks into a queue.
16+
* * [UnconfinedTestDispatcher] is a dispatcher that behaves like [Dispatchers.Unconfined] while allowing to control
17+
* the virtual time.
1318
*/
1419
@ExperimentalCoroutinesApi
1520
public abstract class TestDispatcher internal constructor(): CoroutineDispatcher(), Delay {

0 commit comments

Comments
 (0)