All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.mockk.impl.recording.ChainedCallDetector.kt Maven / Gradle / Ivy

package io.mockk.impl.recording

import io.mockk.*
import io.mockk.InternalPlatformDsl.toArray
import io.mockk.InternalPlatformDsl.toStr
import io.mockk.impl.InternalPlatform
import io.mockk.impl.log.Logger
import io.mockk.impl.log.SafeLog
import kotlin.reflect.KClass

class ChainedCallDetector(safeLog: SafeLog) {
    val log = safeLog(Logger())

    val matcherMap = hashMapOf, Matcher<*>>()
    val allCompositeMatchers = mutableListOf>>()
    val argMatchers = mutableListOf>()

    lateinit var call: RecordedCall

    @Suppress("CAST_NEVER_SUCCEEDS")
    fun detect(callRounds: List, callN: Int) {
        val callInAllRounds = callRounds.map { it.calls[callN] }
        val zeroCall = callInAllRounds[0]
        var allAny = false

        log.trace { "Processing call #$callN: ${zeroCall.method.toStr()}" }

        fun gatherMatchers() {
            repeat(zeroCall.matchers.size) { nMatcher ->
                val matcher = callInAllRounds.map { it.matchers[nMatcher].matcher }.last()
                val signature = callInAllRounds.map { it.matchers[nMatcher].signature }.toList()

                if (matcher is CompositeMatcher<*>) {
                    allCompositeMatchers.add(callInAllRounds.map {
                        it.matchers[nMatcher].matcher as CompositeMatcher<*>
                    })
                }

                matcherMap[signature] = matcher
            }

            log.trace { "Matcher map for ${zeroCall.method.toStr()}: $matcherMap" }
        }

        fun buildMatcher(isStart: Boolean, zeroCallValue: Any?, matcherBySignature: Matcher<*>?): Matcher<*> {
            return if (matcherBySignature == null) {
                if (allAny)
                    ConstantMatcher(true)
                else {
                    eqOrNullMatcher(zeroCallValue)
                }
            } else {
                if (isStart && matcherBySignature is AllAnyMatcher) {
                    allAny = true
                    ConstantMatcher(true)
                } else {
                    matcherBySignature
                }
            }
        }

        fun regularArgument(nArgument: Int): Matcher<*> {
            val signature = callInAllRounds.map {
                InternalPlatform.packRef(it.args[nArgument])
            }.toList()

            log.trace { "Signature for $nArgument argument of ${zeroCall.method.toStr()}: $signature" }

            val matcherBySignature = matcherMap.remove(signature)

            return buildMatcher(
                nArgument == 0,
                zeroCall.args[nArgument],
                matcherBySignature
            )
        }

        fun varArgArgument(nArgument: Int): Matcher<*> {
            val varArgMatchers = mutableListOf>()

            val zeroCallArg = zeroCall.args[nArgument]!!.toArray()
            repeat(zeroCallArg.size) { nVarArg ->
                val signature = callInAllRounds.map {
                    val arg = it.args[nArgument]!!.toArray()
                    InternalPlatform.packRef(arg[nVarArg])
                }.toList()

                log.trace { "Signature for $nArgument/$nVarArg argument of ${zeroCall.method.toStr()}: $signature" }

                val matcherBySignature = matcherMap.remove(signature)
                varArgMatchers.add(
                    buildMatcher(
                        nArgument == 0 && nVarArg == 0,
                        zeroCallArg[nVarArg],
                        matcherBySignature
                    )
                )
            }

            // FIXME unchecked cast
            return ArrayMatcher(varArgMatchers.map { it as Matcher })
        }

        fun detectArgMatchers() {
            allAny = false

            val varArgsArg = zeroCall.method.varArgsArg

            repeat(zeroCall.args.size) { nArgument ->
                val matcher = if (varArgsArg == nArgument) {
                    varArgArgument(nArgument)
                } else {
                    regularArgument(nArgument)
                }

                argMatchers.add(matcher)
            }
        }

        @Suppress("UNCHECKED_CAST")
        fun processCompositeMatchers() {
            for (compositeMatchers in allCompositeMatchers) {
                val matcher = compositeMatchers.last()

                matcher.subMatchers = matcher.operandValues.withIndex().map { (nOp, _) ->
                    val signature = compositeMatchers.map {
                        InternalPlatform.packRef(it.operandValues[nOp])
                    }.toList()

                    log.trace { "Signature for $nOp operand of $matcher composite matcher: $signature" }

                    matcherMap.remove(signature)
                            ?: eqOrNullMatcher(matcher.operandValues[nOp])
                } as List>?
            }
        }

        @Suppress("UNCHECKED_CAST")
        fun buildRecordedCall(): RecordedCall {
            if (zeroCall.method.isSuspend()) {
                log.trace { "Suspend function found. Replacing continuation with any() matcher" }
                argMatchers[argMatchers.size - 1] = ConstantMatcher(true)
            }

            if (matcherMap.isNotEmpty()) {
                throw MockKException("Failed matching mocking signature for\n${zeroCall.invocationStr}\nleft matchers: ${matcherMap.values}")
            }

            val im = InvocationMatcher(
                zeroCall.self,
                zeroCall.method,
                argMatchers.toList() as List>,
                allAny
            )
            log.trace { "Built matcher: $im" }

            return RecordedCall(
                zeroCall.retValue,
                zeroCall.isRetValueMock,
                zeroCall.retType,
                im,
                null,
                null
            )
        }

        gatherMatchers()
        detectArgMatchers()
        processCompositeMatchers()
        call = buildRecordedCall()
    }

    @Suppress("UNCHECKED_CAST")
    protected fun MethodDescription.isSuspend(): Boolean {
        return InternalPlatform.isSuspend(paramTypes as List>)
    }

    protected fun eqOrNullMatcher(arg: Any?): Matcher {
        return if (arg == null) {
            NullCheckMatcher(false)
        } else {
            EqMatcher(arg)
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy