aboutsummaryrefslogtreecommitdiffstats
path: root/kotlinx-coroutines-test/src/internal/MainTestDispatcher.kt
blob: 9953756f70a72df7f22cea260ac69d570ba5c75c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
/*
 * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
 */

package kotlinx.coroutines.test.internal

import kotlinx.coroutines.*
import kotlinx.coroutines.internal.*
import kotlin.coroutines.*

/**
 * The testable main dispatcher used by kotlinx-coroutines-test.
 * It is a [MainCoroutineDispatcher] which delegates all actions to a settable delegate.
 */
internal class TestMainDispatcher(private val mainFactory: MainDispatcherFactory) : MainCoroutineDispatcher(), Delay {
    private var _delegate: CoroutineDispatcher? = null
    private val delegate: CoroutineDispatcher get() {
        _delegate?.let { return it }
        mainFactory.tryCreateDispatcher(emptyList()).let {
            // If we've failed to create a dispatcher, do no set _delegate
            if (!isMissing()) {
                _delegate = it
            }
            return it
        }
    }

    @Suppress("INVISIBLE_MEMBER")
    private val delay: Delay get() = delegate as? Delay ?: DefaultDelay

    override val immediate: MainCoroutineDispatcher
        get() = (delegate as? MainCoroutineDispatcher)?.immediate ?: this

    override fun dispatch(context: CoroutineContext, block: Runnable) {
        delegate.dispatch(context, block)
    }

    @ExperimentalCoroutinesApi
    override fun isDispatchNeeded(context: CoroutineContext): Boolean = delegate.isDispatchNeeded(context)

    override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
        delay.scheduleResumeAfterDelay(timeMillis, continuation)
    }

    override suspend fun delay(time: Long) {
        delay.delay(time)
    }

    override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle {
        return delay.invokeOnTimeout(timeMillis, block, context)
    }

    public fun setDispatcher(dispatcher: CoroutineDispatcher) {
        _delegate = dispatcher
    }

    public fun resetDispatcher() {
        _delegate = null
    }
}

internal class TestMainDispatcherFactory : MainDispatcherFactory {

    override fun createDispatcher(allFactories: List<MainDispatcherFactory>): MainCoroutineDispatcher {
        val originalFactory = allFactories.asSequence()
            .filter { it !== this }
            .maxByOrNull { it.loadPriority } ?: MissingMainCoroutineDispatcherFactory
        return TestMainDispatcher(originalFactory)
    }

    /**
     * [Int.MAX_VALUE] -- test dispatcher always wins no matter what factories are present in the classpath.
     * By default all actions are delegated to the second-priority dispatcher, so that it won't be the issue.
     */
    override val loadPriority: Int
        get() = Int.MAX_VALUE
}