commonMain.com.zegreatrob.testmints.Setup.kt Maven / Gradle / Ivy
package com.zegreatrob.testmints
import com.zegreatrob.testmints.report.MintReporter
class Setup(
private val contextProvider: (SC) -> C,
private val reporter: MintReporter,
private val additionalSetupActions: C.() -> Unit,
private val wrapper: (TestFunc) -> Unit,
) {
infix fun exercise(codeUnderTest: C.() -> R) = Exercise { verifyFunc ->
{ teardownFunc ->
runTest(codeUnderTest, verifyFunc, teardownFunc)
}
}
private fun runTest(
exerciseFunc: ExerciseFunc,
verifyFunc: VerifyFunc,
teardownFunc: TeardownFunc,
) {
var verifyFailure: Throwable? = null
var teardownException: Throwable? = null
val wrapperException = checkedInvoke(wrapper) { sharedContext ->
val reportedExerciseFunc = exerciseFunc.makeReporting(reporter)
val reportedVerifyFunc = verifyFunc.makeReporting(reporter)
val reportedTeardownFunc = teardownFunc.makeReporting(reporter)
val context = setup(sharedContext)
val result = reportedExerciseFunc(context)
verifyFailure = reportedVerifyFunc(context, result)
teardownException = reportedTeardownFunc(context, result)
}
reporter.teardownFinish()
reportExceptions(verifyFailure, teardownException, wrapperException)
}
private fun reportExceptions(
verifyFailure: Throwable?,
teardownException: Throwable?,
wrapperException: Throwable?,
) {
val problems = exceptionDescriptionMap(teardownException, wrapperException, verifyFailure)
if (problems.size == 1) {
throw problems.values.first()
} else if (problems.isNotEmpty()) {
throw CompoundMintTestException(problems)
}
}
private fun TeardownFunc.makeReporting(mintReporter: MintReporter) = { c: C, r: R ->
mintReporter.teardownStart()
captureException { this(c, r) }
}
private fun setup(sc: SC): C {
val context = contextProvider(sc)
additionalSetupActions(context)
return context
}
}
private fun ExerciseFunc.makeReporting(reporter: MintReporter): ExerciseFunc = {
reporter.exerciseStart(this)
this@makeReporting(this)
.also { reporter.exerciseFinish() }
}
private fun VerifyFunc.makeReporting(mintReporter: MintReporter) = { context: C, result: R ->
context
.also { mintReporter.verifyStart(result) }
.let { captureException { it.(this)(result) } }
.also { mintReporter.verifyFinish() }
}
private fun exceptionDescriptionMap(
teardownException: Throwable?,
templateTeardownException: Throwable?,
failure: Throwable?,
) =
mapOf(
"Failure" to failure,
"Teardown exception" to teardownException,
"Template teardown exception" to templateTeardownException,
)
.mapNotNull { (descriptor, exception) -> exception?.let { descriptor to exception } }
.toMap()
private fun checkedInvoke(wrapper: (TestFunc) -> Unit, test: TestFunc) = captureException {
var testWasInvoked = false
wrapper.invoke {
testWasInvoked = true
test(it)
}
if (!testWasInvoked) throw Exception("Incomplete test template: the wrapper function never called the test function")
}