Repository: Kotlin/kotlinx.coroutines
File: kotlinx-coroutines-core/common/test/flow/terminal/FirstTest.kt (Kotlin)

Code before:
package kotlinx.coroutines.flow

import kotlinx.coroutines.testing.*
import kotlinx.coroutines.*
import kotlinx.coroutines.CoroutineStart.*
import kotlinx.coroutines.channels.*
import kotlinx.coroutines.flow.internal.*
import kotlin.test.*
import kotlin.time.*

class FirstTest : TestBase() {
    @Test
    fun testFirst() = runTest {
        val flow = flowOf(1, 2, 3)
        assertEquals(1, flow.first())
    }

    @Test
    fun testNulls() = runTest {
        val flow = flowOf(null, 1)
        assertNull(flow.first())
        assertNull(flow.first { it == null })
        assertEquals(1, flow.first { it != null })
    }

    @Test
    fun testFirstWithPredicate() = runTest {
        val flow = flowOf(1, 2, 3)
        assertEquals(1, flow.first { it > 0 })
        assertEquals(2, flow.first { it > 1 })
        assertFailsWith<NoSuchElementException> { flow.first { it > 3 } }
    }

    @Test
    fun testFirstCancellation() = runTest {
        val latch = Channel<Unit>()
        val flow = flow {
            coroutineScope {
                launch {
                    latch.send(Unit)
                    hang { expect(1) }
                }
                emit(1)
                emit(2)
            }
        }


        val result = flow.first {
            latch.receive()
            true
        }
        assertEquals(1, result)
        finish(2)
    }

    @Test
    fun testEmptyFlow() = runTest {
        assertFailsWith<NoSuchElementException> { emptyFlow<Int>().first() }
        assertFailsWith<NoSuchElementException> { emptyFlow<Int>().first { true } }
    }

    @Test
    fun testErrorCancelsUpstream() = runTest {
        val latch = Channel<Unit>()
        val flow = flow {
            coroutineScope {
                launch {
                    latch.send(Unit)
                    hang { expect(1) }
                }
                emit(1)
            }
        }

        assertFailsWith<TestException> {
            flow.first {
                latch.receive()
                throw TestException()
            }
        }

        assertEquals(1, flow.first())
        finish(2)
    }

    @Test
    fun testFirstOrNull() = runTest {
        val flow = flowOf(1, 2, 3)
        assertEquals(1, flow.firstOrNull())
    }

    @Test
    fun testFirstOrNullWithPredicate() = runTest {
        val flow = flowOf(1, 2, 3)
        assertEquals(1, flow.firstOrNull { it > 0 })
        assertEquals(2, flow.firstOrNull { it > 1 })
        assertNull(flow.firstOrNull { it > 3 })
    }

    @Test
    fun testFirstOrNullCancellation() = runTest {
        val latch = Channel<Unit>()
        val flow = flow {
            coroutineScope {
                launch {
                    latch.send(Unit)
                    hang { expect(1) }
                }
                emit(1)
                emit(2)
            }
        }


        val result = flow.firstOrNull {
            latch.receive()
            true
        }
        assertEquals(1, result)
        finish(2)
    }

    @Test
    fun testFirstOrNullWithEmptyFlow() = runTest {
        assertNull(emptyFlow<Int>().firstOrNull())
        assertNull(emptyFlow<Int>().firstOrNull { true })
    }

    @Test
    fun testFirstOrNullWithNullElement() = runTest {
        assertNull(flowOf<String?>(null).firstOrNull())
        assertNull(flowOf<String?>(null).firstOrNull { true })
    }

    @Test
    fun testFirstOrNullWhenErrorCancelsUpstream() = runTest {
        val latch = Channel<Unit>()
        val flow = flow {
            coroutineScope {
                launch {
                    latch.send(Unit)
                    hang { expect(1) }
                }
                emit(1)
            }
        }

        assertFailsWith<TestException> {
            flow.firstOrNull {
                latch.receive()
                throw TestException()
            }
        }

        assertEquals(1, flow.firstOrNull())
        finish(2)
    }

    @Test
    fun testBadClass() = runTest {
        val instance = BadClass()
        val flow = flowOf(instance)
        assertSame(instance, flow.first())
        assertSame(instance, flow.firstOrNull())
        assertSame(instance, flow.first { true })
        assertSame(instance, flow.firstOrNull { true })
    }

    @Test
    fun testAbortFlowException() = runTest {
        val flow = flow<Int> {
            throw AbortFlowException(NopCollector) // Emulate cancellation
        }

        assertFailsWith<CancellationException> { flow.first() }
    }

    @Test
    fun test() = runTest {
        val job = launch(start = UNDISPATCHED) {
            flow {
                coroutineScope {
                    emitAll(produce(start = UNDISPATCHED) {
                        channel.send(Unit)
                    })
                }
            }.first()
            expectUnreached()
        }
        job.cancel()
    }
}

Review comment (suggestion):
In this scenario, it's worth it to use `expect` and `finish` to assert the order of execution. The rules:
* `expect(1)` is the first invocation in any test.
* `expect(n + 1)` can only be called after `expect(n)`.
* `finish(n + 1)` can only be called after `expect(n)`.
* Each test that uses an `expect` must call `finish`.
* After `finish`, no extra `expect` or `finish` can be called.

With this, we can ensure that indeed, the test checks what we want it to check: first, cancellation happens, then, `first` throws a `CancellationException`.

Code after:

    @Test
    fun testAbortFlowException() = runTest {
        val flow = flow<Int> {
            throw AbortFlowException(NopCollector) // Emulate cancellation
        }

        assertFailsWith<CancellationException> { flow.first() }
    }

    @Test
    fun testFirstThrowOnCancellation() = runTest {
        val job = launch(start = UNDISPATCHED) {
            flow {
                try {
                    emit(Unit)
                } finally {
                    runCatching { yield() }
                    finish(2)
                }
            }.first()
            expectUnreached()
        }
        expect(1)
        job.cancel()
    }
}