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

commonMain.io.mockk.Matchers.kt Maven / Gradle / Ivy

There is a newer version: 1.13.13
Show newest version
package io.mockk

import io.mockk.InternalPlatformDsl.toArray
import io.mockk.InternalPlatformDsl.toStr
import kotlin.math.min
import kotlin.reflect.KClass

/**
 * Matcher that checks equality. By reference and by value (equals method)
 */
data class EqMatcher(private val valueArg: T, val ref: Boolean = false, val inverse: Boolean = false) :
    Matcher {
    val value = InternalPlatformDsl.unboxChar(valueArg)

    override fun match(arg: T?): Boolean {
        val result = if (ref) {
            arg === value
        } else {
            if (arg == null) {
                false
            } else {
                val unboxedArg = InternalPlatformDsl.unboxChar(arg)
                InternalPlatformDsl.deepEquals(unboxedArg, value)
            }
        }
        return if (inverse) !result else result
    }

    override fun substitute(map: Map) =
        copy(valueArg = valueArg.internalSubstitute(map))

    override fun toString(): String {
        return if (ref)
            "${if (inverse) "refNonEq" else "refEq"}(${value.toStr()})"
        else
            "${if (inverse) "nonEq" else "eq"}(${value.toStr()})"
    }
}

/**
 * Matcher that always returns one same value.
 */
data class ConstantMatcher(val constValue: Boolean) : Matcher {
    override fun match(arg: T?): Boolean = constValue

    override fun toString(): String = if (constValue) "any()" else "none()"
}

/**
 * Delegates matching to lambda function
 */
data class FunctionMatcher(
    val matchingFunc: (T) -> Boolean,
    override val argumentType: KClass<*>
) : Matcher, TypedMatcher, EquivalentMatcher {
    override fun equivalent(): Matcher = ConstantMatcher(true)

    override fun match(arg: T?): Boolean {
        return if (arg == null) {
            false
        } else {
            try {
                matchingFunc(arg)
            } catch (a: AssertionError) {
                false
            }
        }
    }

    override fun toString(): String = "matcher<${argumentType.simpleName}>()"
}

data class FunctionWithNullableArgMatcher(
    val matchingFunc: (T?) -> Boolean,
    override val argumentType: KClass<*>
) : Matcher, TypedMatcher, EquivalentMatcher {
    override fun equivalent(): Matcher = ConstantMatcher(true)

    override fun match(arg: T?): Boolean = matchingFunc(arg)

    override fun checkType(arg: Any?): Boolean {
        if (arg == null) {
            return true
        }

        return super.checkType(arg)
    }

    override fun toString(): String = "matcher<${argumentType.simpleName}>()"
}

/**
 * Matcher capturing all results to the list.
 */
data class CaptureMatcher(
    val captureList: MutableList,
    override val argumentType: KClass<*>
) : Matcher, CapturingMatcher, TypedMatcher, EquivalentMatcher {
    override fun equivalent(): Matcher = ConstantMatcher(true)

    @Suppress("UNCHECKED_CAST")
    override fun capture(arg: Any?) {
        captureList.add(arg as T)
    }

    override fun match(arg: T?): Boolean = true

    override fun toString(): String = "capture<${argumentType.simpleName}>()"
}

/**
 * Matcher capturing all results to the list. Allows nulls
 */
data class CaptureNullableMatcher(
    val captureList: MutableList,
    override val argumentType: KClass<*>
) : Matcher, CapturingMatcher, TypedMatcher, EquivalentMatcher {
    override fun equivalent(): Matcher = ConstantMatcher(true)

    @Suppress("UNCHECKED_CAST")
    override fun capture(arg: Any?) {
        captureList.add(arg as T?)
    }

    override fun match(arg: T?): Boolean = true

    override fun checkType(arg: Any?): Boolean {
        if (arg == null) {
            return true
        }

        return super.checkType(arg)
    }

    override fun toString(): String = "capture<${argumentType.simpleName}?>()"
}

/**
 * Matcher capturing one last NON nullable value to the [CapturingSlot]
 */
data class CapturingSlotMatcher(
    val captureSlot: CapturingSlot,
    override val argumentType: KClass<*>,
) : Matcher, CapturingMatcher, TypedMatcher, EquivalentMatcher {
    override fun equivalent(): Matcher = ConstantMatcher(true)

    override fun capture(arg: Any?) {
        // does not capture null values
        if (arg != null) {
            captureSlot.captured = InternalPlatformDsl.boxCast(argumentType, arg)
        }
    }

    override fun match(arg: T?): Boolean = true

    override fun toString(): String = "slotCapture<${argumentType.simpleName}>()"
}

/**
 * Matcher capturing one last nullable value to the [CapturingSlot]
 */
data class CapturingNullableSlotMatcher(
    val captureSlot: CapturingSlot,
    override val argumentType: KClass<*>,
) : Matcher, CapturingMatcher, TypedMatcher, EquivalentMatcher {
    override fun equivalent(): Matcher = ConstantMatcher(true)

    override fun capture(arg: Any?) {
        if (arg == null) {
            captureSlot.captured = null
        } else {
            captureSlot.captured = InternalPlatformDsl.boxCast(argumentType, arg)
        }
    }

    override fun match(arg: T?): Boolean = true

    override fun checkType(arg: Any?): Boolean {
        if (arg == null) {
            return true
        }

        return super.checkType(arg)
    }

    override fun toString(): String = "slotCapture<${argumentType.simpleName}>()"
}

/**
 * Matcher comparing values
 */
data class ComparingMatcher>(
    val value: T,
    val cmpFunc: Int,
    override val argumentType: KClass
) : Matcher, TypedMatcher {
    override fun match(arg: T?): Boolean {
        if (arg == null) return false
        val n = arg.compareTo(value)
        return when (cmpFunc) {
            2 -> n >= 0
            1 -> n > 0
            0 -> n == 0
            -1 -> n < 0
            -2 -> n <= 0
            else -> throw MockKException("Bad comparison function")
        }
    }

    override fun substitute(map: Map) =
        copy(value = value.internalSubstitute(map))

    override fun toString(): String =
        when (cmpFunc) {
            -2 -> "lessAndEquals($value)"
            -1 -> "less($value)"
            0 -> "cmpEq($value)"
            1 -> "more($value)"
            2 -> "moreAndEquals($value)"
            else -> throw MockKException("Bad comparison function")
        }
}

/**
 * Boolean logic "AND" and "OR" matcher composed of two other matchers
 */
data class AndOrMatcher(
    val and: Boolean,
    val first: T,
    val second: T
) : Matcher, CompositeMatcher, CapturingMatcher {
    override val operandValues: List
        get() = listOf(first, second)

    override var subMatchers: List>? = null

    override fun match(arg: T?): Boolean =
        if (and)
            subMatchers!![0].match(arg) && subMatchers!![1].match(arg)
        else
            subMatchers!![0].match(arg) || subMatchers!![1].match(arg)

    override fun substitute(map: Map): Matcher {
        val matcher = copy(
            first = first.internalSubstitute(map),
            second = second.internalSubstitute(map)
        )
        val sm = subMatchers
        if (sm != null) {
            matcher.subMatchers = sm.map { it.substitute(map) }
        }
        return matcher
    }

    override fun capture(arg: Any?) {
        captureSubMatchers(arg)
    }

    override fun toString(): String {
        val sm = subMatchers
        val op = if (and) "and" else "or"
        return if (sm != null)
            "$op(${sm[0]}, ${sm[1]})"
        else
            "$op()"
    }


}

/**
 * Boolean logic "NOT" matcher composed of one matcher
 */
data class NotMatcher(val value: T) : Matcher, CompositeMatcher, CapturingMatcher {
    override val operandValues: List
        get() = listOf(value)

    override var subMatchers: List>? = null

    override fun match(arg: T?): Boolean =
        !subMatchers!![0].match(arg)

    override fun substitute(map: Map): Matcher {
        val matcher = copy(value = value.internalSubstitute(map))
        val sm = subMatchers
        if (sm != null) {
            matcher.subMatchers = sm.map { it.substitute(map) }
        }
        return matcher
    }

    override fun capture(arg: Any?) {
        captureSubMatchers(arg)
    }

    override fun toString(): String {
        val sm = subMatchers
        return if (sm != null)
            "not(${sm[0]})"
        else
            "not()"
    }
}

/**
 * Checks if argument is null or non-null
 */
data class NullCheckMatcher(val inverse: Boolean = false) : Matcher {
    override fun match(arg: T?): Boolean = if (inverse) arg != null else arg == null

    override fun toString(): String {
        return if (inverse)
            "notNull()"
        else
            "null()"
    }
}

/**
 * Checks matcher data type
 */
data class OfTypeMatcher(val cls: KClass<*>) : Matcher {
    override fun match(arg: T?): Boolean = cls.isInstance(arg)

    override fun toString() = "ofType(${cls.simpleName})"
}

/**
 * Matcher to replace all unspecified argument matchers to any()
 * Handled by logic in a special way
 */
class AllAnyMatcher : Matcher {
    override fun match(arg: T?): Boolean = true

    override fun toString() = "allAny()"
}

/**
 * Invokes lambda
 */
class InvokeMatcher(val block: (T) -> Unit) : Matcher, EquivalentMatcher {
    override fun equivalent(): Matcher = ConstantMatcher(true)

    override fun match(arg: T?): Boolean {
        if (arg == null) {
            return true
        }
        block(arg)
        return true
    }


    override fun toString(): String = "coInvoke()"
}

/**
 * Matcher that can match arrays via provided matchers for each element.
 */
data class ArrayMatcher(private val matchers: List>) : Matcher, CapturingMatcher {

    override fun capture(arg: Any?) {
        if (arg == null) {
            return
        }

        val arr = arg.toArray()

        repeat(min(arr.size, matchers.size)) { i ->
            val matcher = matchers[i]
            if (matcher is CapturingMatcher) {
                matcher.capture(arr[i])
            }
        }
    }

    override fun match(arg: T?): Boolean {
        if (arg == null) {
            return false
        }

        val arr = arg.toArray()

        if (arr.size != matchers.size) {
            return false
        }

        repeat(arr.size) { i ->
            if (!matchers[i].match(arr[i])) {
                return false
            }
        }

        return true
    }

    override fun substitute(map: Map) =
        copy(matchers = matchers.map { it.substitute(map) })

    override fun toString(): String {
        return matchers.joinToString(prefix = "[", postfix = "]")
    }
}

data class VarargMatcher(
    private val all: Boolean,
    private val matcher: MockKMatcherScope.MockKVarargScope.(T?) -> Boolean,
    private val prefix: List> = listOf(),
    private val postfix: List> = listOf()
) : Matcher, CapturingMatcher {

    @Suppress("UNCHECKED_CAST")
    override fun match(arg: Any?): Boolean {
        if (arg == null) {
            return false
        }

        val arr = arg.toArray()

        if (arr.size < prefix.size + postfix.size) {
            return false
        }

        repeat(prefix.size) {
            val el = arr[it] as T?
            if (!prefix[it].match(el)) {
                return false
            }
        }

        repeat(postfix.size) {
            val el = arr[arr.size - postfix.size + it] as T?
            if (!postfix[it].match(el)) {
                return false
            }
        }


        val centralPartSize = arr.size - postfix.size - prefix.size

        if (all) {
            repeat(centralPartSize) {
                val position = it + prefix.size
                val el = arr[position] as T?
                val scope = MockKMatcherScope.MockKVarargScope(position, arr.size)
                if (!scope.matcher(el)) {
                    return false
                }
            }
            return true
        } else {
            repeat(centralPartSize) {
                val position = it + prefix.size
                val el = arr[position] as T?
                val scope = MockKMatcherScope.MockKVarargScope(position, arr.size)
                if (scope.matcher(el)) {
                    return true
                }
            }
            return false
        }
    }

    @Suppress("UNCHECKED_CAST")
    override fun capture(arg: Any?) {
        if (arg == null) {
            return
        }

        val arr = arg.toArray()

        if (arr.size < prefix.size + postfix.size) {
            return
        }

        repeat(prefix.size) {
            val el = arr[it] as T?
            val elMatcher = prefix[it]
            if (elMatcher is CapturingMatcher) {
                elMatcher.capture(el)
            }
        }

        repeat(postfix.size) {
            val el = arr[arr.size - postfix.size + it] as T?
            val elMatcher = postfix[it]
            if (elMatcher is CapturingMatcher) {
                elMatcher.capture(el)
            }
        }
    }

    override fun toString() = "VarargMatcher(all=$all, prefix=$prefix, postfix=$postfix, centralPart=lambda)"
}


fun CompositeMatcher<*>.captureSubMatchers(arg: Any?) {
    subMatchers?.let {
        it.filterIsInstance()
            .forEach { matcher -> matcher.capture(arg) }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy