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

jvmMain.com.bkahlert.kommons.test.junit.DynamicTestDisplayNameGenerator.kt Maven / Gradle / Ivy

There is a newer version: 2.8.0
Show newest version
package com.bkahlert.kommons.test.junit

import com.bkahlert.kommons.test.KommonsTest
import com.bkahlert.kommons.test.LambdaBody
import com.bkahlert.kommons.test.SLF4J
import com.bkahlert.kommons.test.com.bkahlert.kommons.ansiRemoved
import com.bkahlert.kommons.test.com.bkahlert.kommons.debug.renderFunctionType
import com.bkahlert.kommons.test.com.bkahlert.kommons.debug.toCompactString
import com.bkahlert.kommons.test.com.bkahlert.kommons.decapitalize
import com.bkahlert.kommons.test.com.bkahlert.kommons.quoted
import kotlin.reflect.KCallable
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.KProperty
import kotlin.reflect.jvm.reflect

public object DynamicTestDisplayNameGenerator {

    internal const val FOR: String = "ꜰᴏʀ"

    @Suppress("SpellCheckingInspection")
    internal const val PROPERTY: String = "ᴩʀᴏᴩᴇʀᴛy"

    @Suppress("SpellCheckingInspection")
    internal const val FUNCTION: String = "ꜰᴜɴᴄᴛɪᴏɴ"

    @Suppress("SpellCheckingInspection")
    internal const val VALUE_OF: String = "ᴠᴀʟᴜᴇ ᴏꜰ"

    @Suppress("SpellCheckingInspection")
    internal const val RETURN: String = "ʀᴇᴛᴜʀɴ"

    internal const val BLANK: String = "𝘣𝘭𝘢𝘯𝘬"
    internal const val EMPTY: String = "𝘦𝘮𝘱𝘵𝘺"
    internal const val NULL: String = "𝘯𝘶𝘭𝘭"

    /**
     * Calculates the display name for a test with the specified [subject],
     * and the optional [testNamePattern] which supports curly placeholders `{}` like [SLF4J] does.
     *
     * If no [testNamePattern] is specified a [displayNameFallback] is calculated heuristically.
     *
     * @see displayNameFallback
     */
    public fun  displayNameFor(subject: T, testNamePattern: String? = null): String {
        val (fallbackPattern: String, args: Array<*>) = displayNameFallback(subject)
        return SLF4J.format(testNamePattern ?: fallbackPattern, *args).ansiRemoved
    }

    /**
     * Attempts to calculate a display name for a test case testing the specified [subject].
     */
    private fun displayNameFallback(subject: Any?): Pair> = when (subject) {
        is KProperty<*> -> "$PROPERTY {}" to arrayOf(subject.name)
        is KFunction<*> -> "$FUNCTION {}" to arrayOf(subject.name)
        is Function<*> -> kotlin.runCatching { subject.reflect() }.getOrNull()
            ?.let { displayNameFallback(it) }
            ?: ("{}" to arrayOf(subject.renderFunctionType()))
        is Triple<*, *, *> -> "( {}, {}, {} )" to arrayOf(
            subject.first.serialized,
            subject.second.serialized,
            subject.third.serialized
        )
        is Pair<*, *> -> "( {}, {} )" to arrayOf(subject.first.serialized, subject.second.serialized)
        is Map.Entry<*, *> -> "{} → {}" to arrayOf(subject.key.serialized, subject.value.serialized)
        is CharSequence -> "{}" to arrayOf(
            when {
                subject.isEmpty() -> EMPTY
                subject.isBlank() -> BLANK
                else -> subject.quoted
            }
        )
        else -> "{}" to arrayOf(subject.serialized)
    }

    private val  T?.serialized get() = this?.toCompactString() ?: NULL

    /** Returns an object with its [Any.toString] returning this string in order to protect it from being quoted (again). */
    private val CharSequence.protected: Any
        get() = object {
            override fun toString(): String = [email protected]()
        }

    /**
     * Attempts to calculate a rich display name for a property
     * expressed by the specified [fn].
     */
    public fun  String.property(fn: T.() -> R): String = when (fn) {
        is KProperty<*> -> "$this $VALUE_OF $PROPERTY ${fn.name}"
        is KFunction<*> -> "$this $RETURN $VALUE_OF ${fn.name}"
        is KCallable<*> -> KommonsTest.locateCall().run { "$this $VALUE_OF ${fn.getPropertyName(methodName)}" }
        else -> "$this " + KommonsTest.locateCall().run {
            getLambdaBodyOrNull(this, methodName)?.let { " ❴ $it ❵ " } ?: fn.toCompactString()
        }
    }

    /**
     * Returns the display name for an [subject] asserting test.
     */
    public fun  StackTraceElement.assertingDisplayName(subject: T, assertions: Assertions): String =
        buildString {
            append("❕ ")
            append(displayNameFor(subject))
            append(" ")
            append([email protected](assertions))
        }

    /**
     * Returns the display name for a transforming test.
     */
    public fun  StackTraceElement.expectingDisplayName(transform: (T) -> R): String =
        this.displayName("❔", transform)

    /**
     * Returns the display name for a catching test.
     */
    public fun  StackTraceElement.catchingDisplayName(transform: (T) -> R): String =
        this.displayName("❓", transform)

    /**
     * Returns the display name for an [exceptionType] throwing test.
     */
    public fun throwingDisplayName(exceptionType: KClass): String =
        buildString {
            append("❗")
            append(" ")
            append(exceptionType.simpleName)
        }

    /**
     * Returns the display name for a test applying [transform].
     */
    private fun  StackTraceElement.displayName(symbol: String, transform: (T) -> R): String =
        buildString {
            append(symbol)
            append(" ")
            append([email protected](transform))
            getLambdaBodyOrNull(this@displayName, "that", "it")?.also {
                append(" ")
                append(it)
            }
        }

    /**
     * Returns the display name for a test involving the subject returned by [provideSubject].
     */
    public fun  StackTraceElement.expectingDisplayName(provideSubject: () -> R): String =
        displayName("❔", provideSubject)

    /**
     * Returns the display name for a test involving an eventually thrown exception
     * by [provideSubject].
     */
    public fun  StackTraceElement.catchingDisplayName(provideSubject: () -> R): String =
        displayName("❓", provideSubject)

    /**
     * Returns the display name for a test involving the subject returned by [provide].
     */
    private fun  StackTraceElement.displayName(symbol: String, provide: () -> R): String =
        buildString {
            append(symbol)
            append(" ")
            append(displayNameFor([email protected](provide, null).protected))
            getLambdaBodyOrNull(this@displayName, "that", "it")?.also {
                append(" ")
                append(it)
            }
        }

    /**
     * Attempts to calculate a rich display name for a property
     * expressed by the specified [fn].
     */
    private fun  StackTraceElement.displayName(fn: T.() -> R, fnName: String? = null): String {
        return when (fn) {
            is KProperty<*> -> fn.name
            is KFunction<*> -> fn.name
            is KCallable<*> -> run { fn.getPropertyName(methodName) }
            else -> fnName?.let { getLambdaBodyOrNull(this, it) } ?: getLambdaBodyOrNull(this) ?: fn.toCompactString()
        }
    }

    /**
     * Attempts to calculate a rich display name for a property
     * expressed by the specified [fn].
     */
    private fun  StackTraceElement.displayName(fn: () -> R, fnName: String? = null): String {
        return when (fn) {
            is KProperty<*> -> fn.name
            is KFunction<*> -> fn.name
            is KCallable<*> -> run { fn.getPropertyName(methodName) }
            else -> fnName?.let { getLambdaBodyOrNull(this, it) } ?: getLambdaBodyOrNull(this) ?: fn.toCompactString()
        }
    }

    private fun KCallable<*>.getPropertyName(callerMethodName: String): String =
        "^$callerMethodName(?.+)$".toRegex().find(name)?.destructured?.let { (arg) -> arg.decapitalize() } ?: name

    private fun getLambdaBodyOrNull(
        stackTraceElement: StackTraceElement,
        vararg methodNameHints: String,
    ) = LambdaBody.parseOrNull(stackTraceElement, *methodNameHints)?.removePrefix("it.")?.toString()
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy