Skip to content

TestScope.runTest should support the option to explicitly manage "currentTime" instead of auto advancing it #4505

@dmstocking

Description

@dmstocking

Summary

runTest is hard coded to advance time and it would be nice to make that configurable.

Use case

I am writing tests that involve the a Postgres database and time. To avoid waiting the actual amount of time, I used a runTest {} based test. The problem is that my implementation of the database is with jasync suspending connection. During the test, a jasync call runs and a separate thread is running to handle the request. From the TestScope's point of view, there are no more coroutines to run. So, it advances time and hits a withTimout I have above the jasync command. I understand that a lot of the time the workRunner is really nice. I would like to have the option of it only running current tasks instead of incrementing the time automatically.

This is the section that is firing.

        val workRunner = launch(CoroutineName("kotlinx.coroutines.test runner")) {
            while (true) {
                // This `tryRunNextTaskUnless` automatically advances `currentTime`
                val executedSomething = testScheduler.tryRunNextTaskUnless { !isActive }
                if (executedSomething) {
                    /** yield to check for cancellation. On JS, we can't use [ensureActive] here, as the cancellation
                     * procedure needs a chance to run concurrently. */
                    yield()
                } else {
                    // waiting for the next task to be scheduled, or for the test runner to be cancelled
                    testScheduler.receiveDispatchEvent()
                }
            }
        }

Example real life failing test case

    @Test
    fun `should notify when your replacement doesn't show up`() = testContext.withLogic {
        schedule("adalovelace", "1991-08-26T07:57:08.123456Z")
        // Timeout here because jasync processing is unknown to runTest
        delay(12.hours())
        notifications.sent("adalovelace", "Your replacement hasn't arrived yet.")
    }

Example simple failing test case

    @Test
    fun `should allow manually updating the clock`() {
        val suspendingConnection = PostgreSQLConnectionBuilder
            .createConnectionPool("jdbc:postgresql://$host:$port/$databaseName?user=$username&password=$password")
            .asSuspending
        runTest {
            withTimeout(1_000) {
                suspendingConnection.sendQuery("SELECT 1;")
            }
        }
    }

I can try to create a project to demonstrate the problem if this isn't clear enough. Just let me know.

The Shape of the API

I am not quite sure on how we can replace this. I mean it would be nice if there was just another dispatcher like StandardDispatcher that had this behavior. Something like PauseDispatcher, ManualDispatcher, or something like that. I don't think that would allow you to replace the code in question. I think it would require a larger change.

Metadata

Metadata

Assignees

No one assigned

    Labels

    designdocsKDoc and API reference

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions