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

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

There is a newer version: 12.1.40
Show newest version
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 timeoutMs: Long,
    private val wrapper: suspend (TestFunc) -> Unit,
) {
    infix fun  exercise(exerciseFunc: suspend C.() -> R) = Exercise(timeoutMs) { 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 exerciseProblem: Throwable? = null
        var verifyFailure: Throwable? = null
        var teardownException: Throwable? = null
        val wrapperException = checkedInvoke(wrapper) { sharedContext ->
            val context = performSetup(sharedContext)
            val result = try {
                performExercise(context, exerciseFunc)
                    .let { Result.success(it) }
            } catch (uhOh: Throwable) {
                exerciseProblem = uhOh
                Result.failure(uhOh)
            }
            verifyFailure = if (result.isSuccess) performVerify(context, result.getOrThrow(), verifyFunc) else null

            teardownException = performTeardown(context, result, teardownFunc)
        }
        reporter.teardownFinish()
        handleTeardownExceptions(exerciseProblem, verifyFailure, teardownException, wrapperException)
    }

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

    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,
    ): Throwable? {
        reporter.verifyStart(result)
        return captureException { context.assertionFunctions(result) }.also {
            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(
    exerciseProblem: Throwable?,
    failure: Throwable?,
    teardownException: Throwable?,
    templateTeardownException: Throwable?,
) {
    val problems = exceptionDescriptionMap(exerciseProblem, failure, teardownException, templateTeardownException)

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

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

private fun descriptionMap(
    exerciseProblem: Throwable?,
    failure: Throwable?,
    teardownException: Throwable?,
    templateTeardownException: Throwable?,
) = mapOf(
    "Exercise exception" to exerciseProblem,
    "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