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

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions