Skip to content

Commit b05e15e

Browse files
authored
Increase code coverage in core module (#3962)
* Increase code coverage in the core module * Add missing tests for reasonable things (i.e. invariants are tested, but things like utility 'toString' are not) * Remove some unused code * Add a few more excludes * Move Broadcast-related deprecations to 'Deprecated.kt' and promote them to ERROR * Update Kover and verification rules * Remove unused dead code
1 parent 605ec56 commit b05e15e

21 files changed

+173
-107
lines changed

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ rxjava3_version=3.0.2
2323
javafx_version=17.0.2
2424
javafx_plugin_version=0.0.8
2525
binary_compatibility_validator_version=0.13.2
26-
kover_version=0.7.0-Beta
26+
kover_version=0.7.4
2727
blockhound_version=1.0.8.RELEASE
2828
jna_version=5.9.0
2929

kotlinx-coroutines-core/api/kotlinx-coroutines-core.api

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,6 @@ public final class kotlinx/coroutines/CoroutineStart : java/lang/Enum {
265265
public static final field LAZY Lkotlinx/coroutines/CoroutineStart;
266266
public static final field UNDISPATCHED Lkotlinx/coroutines/CoroutineStart;
267267
public static fun getEntries ()Lkotlin/enums/EnumEntries;
268-
public final fun invoke (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)V
269268
public final fun invoke (Lkotlin/jvm/functions/Function2;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)V
270269
public final fun isLazy ()Z
271270
public static fun valueOf (Ljava/lang/String;)Lkotlinx/coroutines/CoroutineStart;

kotlinx-coroutines-core/build.gradle

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -351,12 +351,6 @@ static void configureJvmForLincheck(task, additional = false) {
351351
task moreTest(dependsOn: [jvmStressTest, jvmLincheckTest, jvmLincheckTestAdditional])
352352
check.dependsOn moreTest
353353

354-
def commonKoverExcludes =
355-
["kotlinx.coroutines.debug.*", // Tested by debug module
356-
"kotlinx.coroutines.channels.ChannelsKt__DeprecatedKt.*", // Deprecated
357-
"kotlinx.coroutines.scheduling.LimitingDispatcher", // Deprecated
358-
"kotlinx.coroutines.scheduling.ExperimentalCoroutineDispatcher" // Deprecated
359-
]
360354

361355
kover {
362356
excludeTests {
@@ -375,9 +369,12 @@ koverReport {
375369
excludes {
376370
classes(
377371
"kotlinx.coroutines.debug.*", // Tested by debug module
378-
"kotlinx.coroutines.channels.ChannelsKt__DeprecatedKt.*", // Deprecated
372+
"kotlinx.coroutines.channels.ChannelsKt__DeprecatedKt*", // Deprecated
379373
"kotlinx.coroutines.scheduling.LimitingDispatcher", // Deprecated
380374
"kotlinx.coroutines.scheduling.ExperimentalCoroutineDispatcher", // Deprecated
375+
"kotlinx.coroutines.flow.FlowKt__MigrationKt*", // Migrations
376+
"kotlinx.coroutines.flow.LintKt*", // Migrations
377+
"kotlinx.coroutines.internal.WeakMapCtorCache", // Fallback implementation that we never test
381378
"_COROUTINE._CREATION", // For IDE navigation
382379
"_COROUTINE._BOUNDARY", // For IDE navigation
383380
)

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

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -75,25 +75,6 @@ public enum class CoroutineStart {
7575
*/
7676
UNDISPATCHED;
7777

78-
/**
79-
* Starts the corresponding block as a coroutine with this coroutine's start strategy.
80-
*
81-
* * [DEFAULT] uses [startCoroutineCancellable].
82-
* * [ATOMIC] uses [startCoroutine].
83-
* * [UNDISPATCHED] uses [startCoroutineUndispatched].
84-
* * [LAZY] does nothing.
85-
*
86-
* @suppress **This an internal API and should not be used from general code.**
87-
*/
88-
@InternalCoroutinesApi
89-
public operator fun <T> invoke(block: suspend () -> T, completion: Continuation<T>): Unit =
90-
when (this) {
91-
DEFAULT -> block.startCoroutineCancellable(completion)
92-
ATOMIC -> block.startCoroutine(completion)
93-
UNDISPATCHED -> block.startCoroutineUndispatched(completion)
94-
LAZY -> Unit // will start lazily
95-
}
96-
9778
/**
9879
* Starts the corresponding block with receiver as a coroutine with this coroutine start strategy.
9980
*

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

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -17,27 +17,6 @@ internal const val DEFAULT_CLOSE_MESSAGE = "Channel was closed"
1717

1818
// -------- Operations on BroadcastChannel --------
1919

20-
/**
21-
* Opens subscription to this [BroadcastChannel] and makes sure that the given [block] consumes all elements
22-
* from it by always invoking [cancel][ReceiveChannel.cancel] after the execution of the block.
23-
*
24-
* **Note: This API is obsolete since 1.5.0 and deprecated for removal since 1.7.0**
25-
* It is replaced with [SharedFlow][kotlinx.coroutines.flow.SharedFlow].
26-
*
27-
* Safe to remove in 1.9.0 as was inline before.
28-
*/
29-
@ObsoleteCoroutinesApi
30-
@Suppress("DEPRECATION")
31-
@Deprecated(level = DeprecationLevel.WARNING, message = "BroadcastChannel is deprecated in the favour of SharedFlow and is no longer supported")
32-
public inline fun <E, R> BroadcastChannel<E>.consume(block: ReceiveChannel<E>.() -> R): R {
33-
val channel = openSubscription()
34-
try {
35-
return channel.block()
36-
} finally {
37-
channel.cancel()
38-
}
39-
}
40-
4120
/**
4221
* This function is deprecated in the favour of [ReceiveChannel.receiveCatching].
4322
*
@@ -118,19 +97,6 @@ public suspend fun <E> ReceiveChannel<E>.toList(): List<E> = buildList {
11897
}
11998
}
12099

121-
/**
122-
* Subscribes to this [BroadcastChannel] and performs the specified action for each received element.
123-
*
124-
* **Note: This API is obsolete since 1.5.0 and deprecated for removal since 1.7.0**
125-
*/
126-
@Deprecated(level = DeprecationLevel.WARNING, message = "BroadcastChannel is deprecated in the favour of SharedFlow and is no longer supported")
127-
@Suppress("DEPRECATION")
128-
public suspend inline fun <E> BroadcastChannel<E>.consumeEach(action: (E) -> Unit): Unit =
129-
consume {
130-
for (element in this) action(element)
131-
}
132-
133-
134100
@PublishedApi
135101
internal fun ReceiveChannel<*>.cancelConsumed(cause: Throwable?) {
136102
cancel(cause?.let {

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,39 @@ import kotlinx.coroutines.*
1111
import kotlin.coroutines.*
1212
import kotlin.jvm.*
1313

14+
/**
15+
* Opens subscription to this [BroadcastChannel] and makes sure that the given [block] consumes all elements
16+
* from it by always invoking [cancel][ReceiveChannel.cancel] after the execution of the block.
17+
*
18+
* **Note: This API is obsolete since 1.5.0 and deprecated for removal since 1.7.0**
19+
* It is replaced with [SharedFlow][kotlinx.coroutines.flow.SharedFlow].
20+
*
21+
* Safe to remove in 1.9.0 as was inline before.
22+
*/
23+
@ObsoleteCoroutinesApi
24+
@Suppress("DEPRECATION")
25+
@Deprecated(level = DeprecationLevel.ERROR, message = "BroadcastChannel is deprecated in the favour of SharedFlow and is no longer supported")
26+
public inline fun <E, R> BroadcastChannel<E>.consume(block: ReceiveChannel<E>.() -> R): R {
27+
val channel = openSubscription()
28+
try {
29+
return channel.block()
30+
} finally {
31+
channel.cancel()
32+
}
33+
}
34+
35+
/**
36+
* Subscribes to this [BroadcastChannel] and performs the specified action for each received element.
37+
*
38+
* **Note: This API is obsolete since 1.5.0 and deprecated for removal since 1.7.0**
39+
*/
40+
@Deprecated(level = DeprecationLevel.ERROR, message = "BroadcastChannel is deprecated in the favour of SharedFlow and is no longer supported")
41+
@Suppress("DEPRECATION", "DEPRECATION_ERROR")
42+
public suspend inline fun <E> BroadcastChannel<E>.consumeEach(action: (E) -> Unit): Unit =
43+
consume {
44+
for (element in this) action(element)
45+
}
46+
1447
/** @suppress **/
1548
@PublishedApi // Binary compatibility
1649
internal fun consumesAll(vararg channels: ReceiveChannel<*>): CompletionHandler =

kotlinx-coroutines-core/common/src/flow/operators/Delay.kt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -308,12 +308,9 @@ public fun <T> Flow<T>.sample(periodMillis: Long): Flow<T> {
308308
*/
309309
internal fun CoroutineScope.fixedPeriodTicker(
310310
delayMillis: Long,
311-
initialDelayMillis: Long = delayMillis
312311
): ReceiveChannel<Unit> {
313-
require(delayMillis >= 0) { "Expected non-negative delay, but has $delayMillis ms" }
314-
require(initialDelayMillis >= 0) { "Expected non-negative initial delay, but has $initialDelayMillis ms" }
315312
return produce(capacity = 0) {
316-
delay(initialDelayMillis)
313+
delay(delayMillis)
317314
while (true) {
318315
channel.send(Unit)
319316
delay(delayMillis)

kotlinx-coroutines-core/common/src/flow/operators/Zip.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
@file:JvmMultifileClass
66
@file:JvmName("FlowKt")
7-
@file:Suppress("UNCHECKED_CAST", "NON_APPLICABLE_CALL_FOR_BUILDER_INFERENCE") // KT-32203
7+
@file:Suppress("UNCHECKED_CAST")
88

99
package kotlinx.coroutines.flow
1010

kotlinx-coroutines-core/common/src/internal/ThreadSafeHeap.kt

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,6 @@ public open class ThreadSafeHeap<T> : SynchronizedObject() where T: ThreadSafeHe
3232

3333
public val isEmpty: Boolean get() = size == 0
3434

35-
public fun clear(): Unit = synchronized(this) {
36-
a?.fill(null)
37-
_size.value = 0
38-
}
39-
4035
public fun find(
4136
predicate: (value: T) -> Boolean
4237
): T? = synchronized(this) block@{

kotlinx-coroutines-core/common/src/intrinsics/Undispatched.kt

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,6 @@ internal fun <T> (suspend () -> T).startCoroutineUnintercepted(completion: Conti
2020
}
2121
}
2222

23-
/**
24-
* Use this function to start a new coroutine in [CoroutineStart.UNDISPATCHED] mode &mdash;
25-
* immediately execute the coroutine in the current thread until the next suspension.
26-
* It does not use [ContinuationInterceptor], but updates the context of the current thread for the new coroutine.
27-
*/
28-
internal fun <T> (suspend () -> T).startCoroutineUndispatched(completion: Continuation<T>) {
29-
startDirect(completion) { actualCompletion ->
30-
withCoroutineContext(completion.context, null) {
31-
startCoroutineUninterceptedOrReturn(actualCompletion)
32-
}
33-
}
34-
}
35-
3623
/**
3724
* Use this function to start a new coroutine in [CoroutineStart.UNDISPATCHED] mode &mdash;
3825
* immediately execute the coroutine in the current thread until the next suspension.

kotlinx-coroutines-core/common/test/channels/BroadcastTest.kt

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
/*
2-
* Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

5-
@file:Suppress("NAMED_ARGUMENTS_NOT_ALLOWED")
5+
@file:Suppress("DEPRECATION")
66

77
package kotlinx.coroutines.channels
88

@@ -25,13 +25,14 @@ class BroadcastTest : TestBase() {
2525
}
2626
yield() // has no effect, because default is lazy
2727
expect(2)
28-
b.consume {
29-
expect(3)
30-
assertEquals(1, receive()) // suspends
31-
expect(7)
32-
assertEquals(2, receive()) // suspends
33-
expect(8)
34-
}
28+
29+
val subscription = b.openSubscription()
30+
expect(3)
31+
assertEquals(1, subscription.receive()) // suspends
32+
expect(7)
33+
assertEquals(2, subscription.receive()) // suspends
34+
expect(8)
35+
subscription.cancel()
3536
expect(9)
3637
yield() // to broadcast
3738
finish(11)
@@ -138,4 +139,4 @@ class BroadcastTest : TestBase() {
138139
yield() // to cancel broadcast
139140
finish(6)
140141
}
141-
}
142+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.coroutines.flow
6+
7+
import kotlinx.coroutines.*
8+
import kotlin.test.*
9+
10+
class BuildersTest : TestBase() {
11+
12+
@Test
13+
fun testSuspendLambdaAsFlow() = runTest {
14+
val lambda = suspend { 42 }
15+
assertEquals(42, lambda.asFlow().single())
16+
}
17+
18+
@Test
19+
fun testRangeAsFlow() = runTest {
20+
assertEquals((0..9).toList(), (0..9).asFlow().toList())
21+
assertEquals(emptyList(), (0..-1).asFlow().toList())
22+
23+
assertEquals((0L..9L).toList(), (0L..9L).asFlow().toList())
24+
assertEquals(emptyList(), (0L..-1L).asFlow().toList())
25+
}
26+
27+
@Test
28+
fun testArrayAsFlow() = runTest {
29+
assertEquals((0..9).toList(), IntArray(10) { it }.asFlow().toList())
30+
assertEquals(emptyList(), intArrayOf().asFlow().toList())
31+
32+
assertEquals((0L..9L).toList(), LongArray(10) { it.toLong() }.asFlow().toList())
33+
assertEquals(emptyList(), longArrayOf().asFlow().toList())
34+
}
35+
36+
@Test
37+
fun testSequence() = runTest {
38+
val expected = (0..9).toList()
39+
assertEquals(expected, expected.iterator().asFlow().toList())
40+
assertEquals(expected, expected.asIterable().asFlow().toList())
41+
}
42+
}

kotlinx-coroutines-core/common/test/flow/operators/BufferTest.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,5 +184,14 @@ class BufferTest : TestBase() {
184184
.toList()
185185
assertEquals(listOf(1, 2), result)
186186
}
187+
188+
@Test
189+
fun testFailsOnIllegalArguments() {
190+
val flow = emptyFlow<Int>()
191+
assertFailsWith<IllegalArgumentException> { flow.buffer(capacity = -3) }
192+
assertFailsWith<IllegalArgumentException> { flow.buffer(capacity = Int.MIN_VALUE) }
193+
assertFailsWith<IllegalArgumentException> { flow.buffer(capacity = Channel.CONFLATED, onBufferOverflow = BufferOverflow.DROP_LATEST) }
194+
assertFailsWith<IllegalArgumentException> { flow.buffer(capacity = Channel.CONFLATED, onBufferOverflow = BufferOverflow.DROP_OLDEST) }
195+
}
187196
}
188197

kotlinx-coroutines-core/common/test/flow/operators/CombineTest.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,10 @@ class CombineTest : CombineTestBase() {
261261
override fun <T1, T2, R> Flow<T1>.combineLatest(other: Flow<T2>, transform: suspend (T1, T2) -> R): Flow<R> = combineOriginal(other, transform)
262262
}
263263

264+
class CombineOverloadTest : CombineTestBase() {
265+
override fun <T1, T2, R> Flow<T1>.combineLatest(other: Flow<T2>, transform: suspend (T1, T2) -> R): Flow<R> = combineOriginal(this, other, transform)
266+
}
267+
264268
class CombineTransformTest : CombineTestBase() {
265269
override fun <T1, T2, R> Flow<T1>.combineLatest(other: Flow<T2>, transform: suspend (T1, T2) -> R): Flow<R> = combineTransformOriginal(other) { a, b ->
266270
emit(transform(a, b))

kotlinx-coroutines-core/common/test/flow/operators/DebounceTest.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,4 +315,10 @@ class DebounceTest : TestBase() {
315315
assertEquals(listOf("A", "C", "D", "E"), result)
316316
finish(5)
317317
}
318+
319+
@Test
320+
fun testFailsWithIllegalArgument() {
321+
val flow = emptyFlow<Int>()
322+
assertFailsWith<IllegalArgumentException> { flow.debounce(-1) }
323+
}
318324
}

kotlinx-coroutines-core/common/test/flow/operators/SampleTest.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,4 +296,10 @@ class SampleTest : TestBase() {
296296
assertEquals(listOf("A", "B", "D"), result)
297297
finish(5)
298298
}
299+
300+
@Test
301+
fun testFailsWithIllegalArgument() {
302+
val flow = emptyFlow<Int>()
303+
assertFailsWith<IllegalArgumentException> { flow.debounce(-1) }
304+
}
299305
}

kotlinx-coroutines-core/common/test/flow/operators/TimeoutTest.kt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,12 @@ package kotlinx.coroutines.flow.operators
66

77
import kotlinx.coroutines.*
88
import kotlinx.coroutines.flow.*
9+
import kotlinx.coroutines.flow.internal.*
10+
import kotlin.coroutines.*
911
import kotlin.test.*
12+
import kotlin.time.*
1013
import kotlin.time.Duration.Companion.milliseconds
14+
import kotlin.time.Duration.Companion.seconds
1115

1216
class TimeoutTest : TestBase() {
1317
@Test
@@ -228,4 +232,20 @@ class TimeoutTest : TestBase() {
228232
assertEquals((0 until 10).toList(), list)
229233
finish(6)
230234
}
235+
236+
@Test
237+
fun testImmediateTimeout() {
238+
testImmediateTimeout(Duration.ZERO)
239+
reset()
240+
testImmediateTimeout(-1.seconds)
241+
}
242+
243+
private fun testImmediateTimeout(timeout: Duration) {
244+
expect(1)
245+
val flow = emptyFlow<Int>().timeout(timeout)
246+
flow::collect.startCoroutine(NopCollector, Continuation(EmptyCoroutineContext) {
247+
assertTrue(it.exceptionOrNull() is TimeoutCancellationException)
248+
finish(2)
249+
})
250+
}
231251
}

kotlinx-coroutines-core/common/test/sync/MutexTest.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,21 @@ class MutexTest : TestBase() {
5959
assertFalse(mutex.isLocked)
6060
}
6161

62+
@Test
63+
fun testWithLockFailureUnlocksTheMutex() = runTest {
64+
val mutex = Mutex()
65+
assertFalse(mutex.isLocked)
66+
try {
67+
mutex.withLock {
68+
expect(1)
69+
throw TestException()
70+
}
71+
} catch (e: TestException) {
72+
finish(2)
73+
}
74+
assertFalse(mutex.isLocked)
75+
}
76+
6277
@Test
6378
fun testUnconfinedStackOverflow() {
6479
val waiters = 10000

0 commit comments

Comments
 (0)