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

commonMain.com.zegreatrob.testmints.async.Setup.kt Maven / Gradle / Ivy

package com.zegreatrob.testmints.async

import com.zegreatrob.testmints.CompoundMintTestException
import com.zegreatrob.testmints.captureException
import com.zegreatrob.testmints.report.MintReporter
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
import kotlinx.coroutines.cancel

class Setup(
    private val contextProvider: suspend (SC) -> C,
    private val scope: CoroutineScope,
    private val additionalActions: suspend C.() -> Unit,
    private val reporter: MintReporter,
    private val wrapper: suspend (TestFunc) -> Unit
) {
    infix fun  exercise(exerciseFunc: suspend C.() -> R) = Exercise { verifyFunc ->
        { teardownFunc ->
            scope.async { runTest(exerciseFunc, verifyFunc, teardownFunc) }.apply {
                invokeOnCompletion { cause -> scope.cancel(cause?.wrapCause()) }
            }
        }
    }

    private suspend fun  runTest(
        exerciseFunc: suspend C.() -> R,
        verifyFunc: suspend C.(R) -> Unit,
        teardownFunc: suspend C.(R) -> Unit
    ) {
        var verifyFailure: Throwable? = null
        var teardownException: Throwable? = null
        val wrapperException = checkedInvoke(wrapper) { sharedContext ->
            val context = performSetup(sharedContext)
            val result = performExercise(context, exerciseFunc)
            verifyFailure = performVerify(context, result, verifyFunc)
            teardownException = performTeardown(context, result, teardownFunc)
        }
        reporter.teardownFinish()
        handleTeardownExceptions(verifyFailure, teardownException, wrapperException)
    }

    private suspend fun  performTeardown(context: C, result: R, teardownFunc: suspend C.(R) -> Unit): Throwable? {
        reporter.teardownStart()
        return captureException { teardownFunc(context, result) }
    }

    private suspend fun  runCodeUnderTest(context: C, codeUnderTest: suspend C.() -> R): R {
        reporter.exerciseStart(context)
        val result = codeUnderTest(context)
        reporter.exerciseFinish()
        return result
    }

    private suspend fun  performVerify(context: C, result: R, assertionFunctions: suspend C.(R) -> Unit) =
        captureException {
            reporter.verifyStart(result)
            context.assertionFunctions(result)
            reporter.verifyFinish()
        }

    private suspend fun performSetup(sharedContext: SC): C {
        val context = contextProvider(sharedContext)
        additionalActions(context)
        if (context is ScopeMint) {
            waitForJobsToFinish(context.setupScope)
        }
        return context
    }

    private suspend fun  performExercise(context: C, exerciseFunc: suspend C.() -> R) =
        runCodeUnderTest(context, exerciseFunc)
            .also {
                if (context is ScopeMint) {
                    waitForJobsToFinish(context.exerciseScope)
                }
            }

}

private fun Throwable.wrapCause() = CancellationException("Test failure.", this)

private fun handleTeardownExceptions(
    failure: Throwable?,
    teardownException: Throwable?,
    templateTeardownException: Throwable?
) {
    val problems = exceptionDescriptionMap(failure, teardownException, templateTeardownException)

    if (problems.size == 1) {
        throw problems.values.first()
    } else if (problems.isNotEmpty()) {
        throw CompoundMintTestException(problems)
    }
}

private fun exceptionDescriptionMap(
    failure: Throwable?,
    teardownException: Throwable?,
    templateTeardownException: Throwable?
) = descriptionMap(failure, teardownException, templateTeardownException)
    .mapNotNull { (descriptor, exception) -> exception?.let { descriptor to exception } }
    .toMap()

private fun descriptionMap(
    failure: Throwable?,
    teardownException: Throwable?,
    templateTeardownException: Throwable?
) = mapOf(
    "Failure" to failure,
    "Teardown exception" to teardownException,
    "Template teardown exception" to templateTeardownException
)

private suspend fun  checkedInvoke(
    wrapper: suspend (TestFunc) -> Unit,
    test: TestFunc
) = captureException {
    var testWasInvoked = false
    wrapper.invoke { sharedContext ->
        testWasInvoked = true
        test(sharedContext)
    }
    if (!testWasInvoked) throw Exception("Incomplete test template: the wrapper function never called the test function")
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy