Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 13 additions & 11 deletions kotlinx-coroutines-core/common/test/channels/BasicOperationsTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,31 @@ class BasicOperationsTest : TestBase() {
@Test
fun testSimpleSendReceive() = runTest {
// Parametrized common test :(
TestChannelKind.values().forEach { kind -> testSendReceive(kind, 20) }
TestChannelKind.entries.forEach { kind -> testSendReceive(kind, 20) }
}

@Test
fun testTrySendToFullChannel() = runTest {
TestChannelKind.values().forEach { kind -> testTrySendToFullChannel(kind) }
TestChannelKind.entries.forEach { kind -> testTrySendToFullChannel(kind) }
}

@Test
fun testTrySendAfterClose() = runTest {
TestChannelKind.values().forEach { kind -> testTrySendAfterClose(kind) }
TestChannelKind.entries.forEach { kind -> testTrySendAfterClose(kind) }
}

@Test
fun testSendAfterClose() = runTest {
TestChannelKind.values().forEach { kind -> testSendAfterClose(kind) }
TestChannelKind.entries.forEach { kind -> testSendAfterClose(kind) }
}

@Test
fun testReceiveCatching() = runTest {
TestChannelKind.values().forEach { kind -> testReceiveCatching(kind) }
TestChannelKind.entries.forEach { kind -> testReceiveCatching(kind) }
}

@Test
fun testInvokeOnClose() = TestChannelKind.values().forEach { kind ->
fun testInvokeOnClose() = TestChannelKind.entries.forEach { kind ->
reset()
val channel = kind.create<Int>()
channel.invokeOnClose {
Expand All @@ -48,7 +48,7 @@ class BasicOperationsTest : TestBase() {
}

@Test
fun testInvokeOnClosed() = TestChannelKind.values().forEach { kind ->
fun testInvokeOnClosed() = TestChannelKind.entries.forEach { kind ->
reset()
expect(1)
val channel = kind.create<Int>()
Expand All @@ -59,7 +59,7 @@ class BasicOperationsTest : TestBase() {
}

@Test
fun testMultipleInvokeOnClose() = TestChannelKind.values().forEach { kind ->
fun testMultipleInvokeOnClose() = TestChannelKind.entries.forEach { kind ->
reset()
val channel = kind.create<Int>()
channel.invokeOnClose { expect(3) }
Expand All @@ -71,14 +71,16 @@ class BasicOperationsTest : TestBase() {
}

@Test
fun testIterator() = runTest {
TestChannelKind.values().forEach { kind ->
fun testIteratorHasNextMustPrecedeNext() = runTest {
TestChannelKind.entries.forEach { kind ->
val channel = kind.create<Int>()
val iterator = channel.iterator()
assertFailsWith<IllegalStateException> { iterator.next() }
channel.close()
assertFailsWith<IllegalStateException> { iterator.next() }
assertFalse(iterator.hasNext())
assertFailsWith<NoSuchElementException> { iterator.next() }
assertFailsWith<IllegalStateException> { iterator.next() }
}
}

Expand Down Expand Up @@ -109,7 +111,7 @@ class BasicOperationsTest : TestBase() {
try {
expect(2)
channel.close()
} catch (e: TestException) {
} catch (_: TestException) {
expect(4)
}
}
Expand Down
196 changes: 196 additions & 0 deletions kotlinx-coroutines-core/common/test/channels/ClosedChannelTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package kotlinx.coroutines.channels

import kotlinx.coroutines.testing.*
import kotlin.test.*

class ClosedChannelTest : TestBase() {
/**
* Iterator.
*/

@Test
fun testIteratorClosedHasNextFalse() = runTest {
TestChannelKind.entries.forEach { kind ->
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Proposal:

    fun TestChannelKind.Companion.runTestForEach(
        testBody: suspend CoroutineScope.(TestChannelKind) -> Unit
    ): TestResult = runTest {
        TestChannelKind.entries.forEach { kind ->
            testBody(kind)
        }
    }

May be more convenient, with how often this pattern occurs.

val channel = kind.create<Int>()
val iterator = channel.iterator()
channel.close()
assertFalse(iterator.hasNext())
}
}

@Test
fun testIteratorClosedWithExceptionHasNextThrows() = runTest {
TestChannelKind.entries.forEach { kind ->
val channel = kind.create<Int>()
val iterator = channel.iterator()
channel.close(TestException())
assertFailsWith<TestException> { (iterator.hasNext()) }
}
}

@Test
fun testClosedToListOk() = runTest {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fun testClosedToListOk() = runTest {
fun testClosedToListDoesNotThrow() = runTest {

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, this test is not part of the Iterator section.

TestChannelKind.entries.forEach { kind ->
val channel = kind.create<Int>()
channel.close()
channel.toList()
}
}

@Test
fun testClosedWithExceptionToListThrows() = runTest {
TestChannelKind.entries.forEach { kind ->
val channel = kind.create<Int>()
channel.close(TestException())
assertFailsWith<TestException> { channel.toList() }
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicates ChannelsTest#testToListOnFailedChannel.


@Test
fun testClosedConsumeEachOk() = runTest {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicates ConsumeTest#testConsumeEachClosesOnSuccess, and I'd argue that place is a better fit for this test, since close is the more fundamental operation.

TestChannelKind.entries.forEach { kind ->
val channel = kind.create<Int>()
channel.close()
channel.consumeEach { }
}
}

@Test
fun testClosedWithExceptionConsumeEachThrows() = runTest {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicates ConsumeTest#testConsumeEachThrowingOnChannelClosing.

TestChannelKind.entries.forEach { kind ->
val channel = kind.create<Int>()
channel.close(TestException())
assertFailsWith<TestException> { channel.consumeEach { } }
}
}

/**
* toList() alternative when a channel might close with an exception.
*/

@Test
fun testToListCatching() = runTest {
TestChannelKind.entries.forEach { kind ->
val channel = kind.create<Unit>()
channel.close(TestException())
val lst = mutableListOf<Unit>()
while (true) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this loop necessary? Only one iteration of it will run. lst:add will never run, as won't getOrThrow(). The whole test can be simplified to

            channel.close(TestException())
            val result = channel.receiveCatching()
            result.onClosed { // suspends
                assertTrue(it is TestException)
            }
            assertTrue(result.isClosed)

This is covered at least by BasicOperationsTest#testReceiveCatching

channel.receiveCatching().onClosed { // suspends
assertTrue(it is TestException)
break
}.getOrThrow().apply(lst::add)
}
assertTrue(lst.isEmpty())
}
}

@Test
fun testTryToListCatching() = runTest {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto.

TestChannelKind.entries.forEach { kind ->
val channel = kind.create<Unit>()
channel.close(TestException())
val lst = mutableListOf<Unit>()
while (true) {
channel.tryReceive().onClosed { // does not suspend
assertTrue(it is TestException)
break
}.onFailure {
// empty, but not yet closed
break
}.getOrThrow().apply(lst::add)
}
assertTrue(lst.isEmpty())
}
}

/**
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This syntax means the comment is a KDoc and refers to the entity directly below it. To make this a normal multiline comment for separating groups of tests, use /*.

That said, I think the separation is easy to break accidentally. Why not introduce separate classes to make this more robust?

class ClosedChannelTest: TestBase() {
    /**
     * Properties.
     *
     * Properties stay the same regardless of whether the channel was closed with or without exception.
     */
    class PropertiesTests: TestBase() {
        
    }
}

* Properties.
*
* Properties stay the same regardless of whether the channel was closed with or without exception.
*/

@Test
fun testClosedIsClosedForReceive() = runTest {
TestChannelKind.entries.forEach { kind ->
val channel = kind.create<Int>()
assertFalse(channel.isClosedForReceive)
channel.close()
assertTrue(channel.isClosedForReceive)
}
}

@Test
fun testClosedWithExceptionIsClosedForReceive() = runTest {
TestChannelKind.entries.forEach { kind ->
val channel = kind.create<Int>()
assertFalse(channel.isClosedForReceive)
channel.close(TestException())
assertTrue(channel.isClosedForReceive)
}
}

@Test
fun testIsEmpty() = runTest {
val channel = Channel<Int>()
assertTrue(channel.isEmpty)
assertFalse(channel.isClosedForReceive)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unrelated to closing channels.


@Test
fun testClosedIsEmptyFalse() = runTest {
TestChannelKind.entries.forEach { kind ->
val channel = kind.create<Int>()
assertTrue(channel.isEmpty)
assertFalse(channel.isClosedForReceive)
channel.close()
// NB! Not obvious.
assertFalse(channel.isEmpty)
assertTrue(channel.isClosedForReceive)
}
}

@Test
fun testClosedWithExceptionIsEmptyFalse() = runTest {
TestChannelKind.entries.forEach { kind ->
val channel = kind.create<Int>()
assertTrue(channel.isEmpty)
assertFalse(channel.isClosedForReceive)
channel.close(TestException())
assertFalse(channel.isEmpty)
assertTrue(channel.isClosedForReceive)
}
}

@Test
fun testSendClosedReceiveIsEmptyFalse() = runTest {
val channel = Channel<Int>(1)
assertTrue(channel.isEmpty)
assertFalse(channel.isClosedForReceive)
channel.send(1)
assertFalse(channel.isEmpty)
assertFalse(channel.isClosedForReceive)
channel.close()
assertFalse(channel.isEmpty)
assertFalse(channel.isClosedForReceive)
channel.receive()
assertFalse(channel.isEmpty)
assertTrue(channel.isClosedForReceive)
}

@Test
fun testSendClosedWithExceptionReceiveIsEmptyFalse() = runTest {
val channel = Channel<Int>(1)
assertTrue(channel.isEmpty)
assertFalse(channel.isClosedForReceive)
channel.send(1)
assertFalse(channel.isEmpty)
assertFalse(channel.isClosedForReceive)
channel.close(TestException())
assertFalse(channel.isEmpty)
assertFalse(channel.isClosedForReceive)
channel.receive()
assertFalse(channel.isEmpty)
assertTrue(channel.isClosedForReceive)
}

}