Merge pull request #25 from bnorm/soft-assertion-sample
Create soft assertion sample and fix bugs it exposed
This commit is contained in:
+22
-24
@@ -17,8 +17,7 @@
|
|||||||
package com.bnorm.power
|
package com.bnorm.power
|
||||||
|
|
||||||
import com.bnorm.power.internal.ReturnableBlockTransformer
|
import com.bnorm.power.internal.ReturnableBlockTransformer
|
||||||
import org.jetbrains.kotlin.backend.common.FileLoweringPass
|
import org.jetbrains.kotlin.backend.common.*
|
||||||
import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext
|
|
||||||
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
|
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
|
||||||
import org.jetbrains.kotlin.backend.common.ir.asSimpleLambda
|
import org.jetbrains.kotlin.backend.common.ir.asSimpleLambda
|
||||||
import org.jetbrains.kotlin.backend.common.ir.inline
|
import org.jetbrains.kotlin.backend.common.ir.inline
|
||||||
@@ -39,22 +38,11 @@ import org.jetbrains.kotlin.ir.builders.parent
|
|||||||
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
|
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||||
import org.jetbrains.kotlin.ir.declarations.path
|
import org.jetbrains.kotlin.ir.declarations.path
|
||||||
import org.jetbrains.kotlin.ir.expressions.IrCall
|
import org.jetbrains.kotlin.ir.expressions.*
|
||||||
import org.jetbrains.kotlin.ir.expressions.IrConst
|
|
||||||
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
|
||||||
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
|
|
||||||
import org.jetbrains.kotlin.ir.expressions.IrStringConcatenation
|
|
||||||
import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionExpressionImpl
|
import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionExpressionImpl
|
||||||
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
|
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
|
||||||
import org.jetbrains.kotlin.ir.types.IrSimpleType
|
import org.jetbrains.kotlin.ir.types.*
|
||||||
import org.jetbrains.kotlin.ir.types.IrType
|
import org.jetbrains.kotlin.ir.util.*
|
||||||
import org.jetbrains.kotlin.ir.types.IrTypeArgument
|
|
||||||
import org.jetbrains.kotlin.ir.types.IrTypeProjection
|
|
||||||
import org.jetbrains.kotlin.ir.types.getClass
|
|
||||||
import org.jetbrains.kotlin.ir.types.isBoolean
|
|
||||||
import org.jetbrains.kotlin.ir.types.isSubtypeOf
|
|
||||||
import org.jetbrains.kotlin.ir.util.functions
|
|
||||||
import org.jetbrains.kotlin.ir.util.isFunctionOrKFunction
|
|
||||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
|
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
|
||||||
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
|
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
|
||||||
import org.jetbrains.kotlin.ir.visitors.acceptVoid
|
import org.jetbrains.kotlin.ir.visitors.acceptVoid
|
||||||
@@ -138,7 +126,7 @@ class PowerAssertCallTransformer(
|
|||||||
val title = when {
|
val title = when {
|
||||||
messageArgument is IrConst<*> -> messageArgument
|
messageArgument is IrConst<*> -> messageArgument
|
||||||
messageArgument is IrStringConcatenation -> messageArgument
|
messageArgument is IrStringConcatenation -> messageArgument
|
||||||
lambda != null -> lambda.inline(parent).transform(ReturnableBlockTransformer(context, symbol), null)
|
lambda != null -> lambda.deepCopyWithSymbols(parent).inline(parent).transform(ReturnableBlockTransformer(context, symbol), null)
|
||||||
messageArgument != null -> {
|
messageArgument != null -> {
|
||||||
val invoke = messageArgument.type.getClass()!!.functions.single { it.name == OperatorNameConventions.INVOKE }
|
val invoke = messageArgument.type.getClass()!!.functions.single { it.name == OperatorNameConventions.INVOKE }
|
||||||
irCallOp(invoke.symbol, invoke.returnType, messageArgument)
|
irCallOp(invoke.symbol, invoke.returnType, messageArgument)
|
||||||
@@ -147,20 +135,20 @@ class PowerAssertCallTransformer(
|
|||||||
else -> irString("Assertion failed")
|
else -> irString("Assertion failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
return delegate.buildCall(this, buildMessage(file, fileSource, title, expression, subStack))
|
return delegate.buildCall(this, expression, buildMessage(file, fileSource, title.deepCopyWithSymbols(parent), expression, subStack))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// println(assertionArgument.dump())
|
// println(expression.dump())
|
||||||
// println(tree.dump())
|
// println(tree.dump())
|
||||||
|
|
||||||
return generator.buildAssert(this, root)
|
return generator.buildAssert(this, root)
|
||||||
// .also { println(it.dump())}
|
// .also { println(it.dump()) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private interface FunctionDelegate {
|
private interface FunctionDelegate {
|
||||||
fun buildCall(builder: IrBuilderWithScope, message: IrExpression): IrExpression
|
fun buildCall(builder: IrBuilderWithScope, original: IrCall, message: IrExpression): IrExpression
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findDelegate(fqName: FqName): FunctionDelegate? {
|
private fun findDelegate(fqName: FqName): FunctionDelegate? {
|
||||||
@@ -174,8 +162,13 @@ class PowerAssertCallTransformer(
|
|||||||
return@mapNotNull when {
|
return@mapNotNull when {
|
||||||
isStringSupertype(parameters[1].type) -> {
|
isStringSupertype(parameters[1].type) -> {
|
||||||
object : FunctionDelegate {
|
object : FunctionDelegate {
|
||||||
override fun buildCall(builder: IrBuilderWithScope, message: IrExpression): IrExpression = with(builder) {
|
override fun buildCall(builder: IrBuilderWithScope, original: IrCall, message: IrExpression): IrExpression = with(builder) {
|
||||||
irCall(overload, type = overload.owner.returnType).apply {
|
irCall(overload, type = overload.owner.returnType).apply {
|
||||||
|
dispatchReceiver = original.dispatchReceiver?.deepCopyWithSymbols(parent)
|
||||||
|
extensionReceiver = original.extensionReceiver?.deepCopyWithSymbols(parent)
|
||||||
|
for (i in 0 until original.typeArgumentsCount) {
|
||||||
|
putTypeArgument(i, original.getTypeArgument(i))
|
||||||
|
}
|
||||||
putValueArgument(0, irFalse())
|
putValueArgument(0, irFalse())
|
||||||
putValueArgument(1, message)
|
putValueArgument(1, message)
|
||||||
}
|
}
|
||||||
@@ -184,7 +177,7 @@ class PowerAssertCallTransformer(
|
|||||||
}
|
}
|
||||||
isStringFunction(parameters[1].type) -> {
|
isStringFunction(parameters[1].type) -> {
|
||||||
object : FunctionDelegate {
|
object : FunctionDelegate {
|
||||||
override fun buildCall(builder: IrBuilderWithScope, message: IrExpression): IrExpression = with(builder) {
|
override fun buildCall(builder: IrBuilderWithScope, original: IrCall, message: IrExpression): IrExpression = with(builder) {
|
||||||
val scope = this
|
val scope = this
|
||||||
val lambda = buildFun {
|
val lambda = buildFun {
|
||||||
name = Name.special("<anonymous>")
|
name = Name.special("<anonymous>")
|
||||||
@@ -198,8 +191,13 @@ class PowerAssertCallTransformer(
|
|||||||
}
|
}
|
||||||
parent = scope.parent
|
parent = scope.parent
|
||||||
}
|
}
|
||||||
val expression = IrFunctionExpressionImpl(-1, -1, context.irBuiltIns.stringType, lambda, IrStatementOrigin.LAMBDA)
|
val expression = IrFunctionExpressionImpl(-1, -1, parameters[1].type, lambda, IrStatementOrigin.LAMBDA)
|
||||||
irCall(overload, type = overload.owner.returnType).apply {
|
irCall(overload, type = overload.owner.returnType).apply {
|
||||||
|
dispatchReceiver = original.dispatchReceiver?.deepCopyWithSymbols(parent)
|
||||||
|
extensionReceiver = original.extensionReceiver?.deepCopyWithSymbols(parent)
|
||||||
|
for (i in 0 until original.typeArgumentsCount) {
|
||||||
|
putTypeArgument(i, original.getTypeArgument(i))
|
||||||
|
}
|
||||||
putValueArgument(0, irFalse())
|
putValueArgument(0, irFalse())
|
||||||
putValueArgument(1, expression)
|
putValueArgument(1, expression)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,5 +57,11 @@ kotlin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
configure<com.bnorm.power.PowerAssertGradleExtension> {
|
configure<com.bnorm.power.PowerAssertGradleExtension> {
|
||||||
functions = listOf("kotlin.assert", "kotlin.test.assertTrue", "kotlin.require")
|
functions = listOf(
|
||||||
|
"kotlin.assert",
|
||||||
|
"kotlin.test.assertTrue",
|
||||||
|
"kotlin.require",
|
||||||
|
"com.bnorm.power.AssertScope.assert",
|
||||||
|
"com.bnorm.power.assert"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.bnorm.power
|
||||||
|
|
||||||
|
import kotlin.contracts.*
|
||||||
|
|
||||||
|
typealias LazyMessage = () -> Any
|
||||||
|
|
||||||
|
interface AssertScope {
|
||||||
|
fun assert(assertion: Boolean, lazyMessage: LazyMessage? = null)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalContracts::class)
|
||||||
|
fun assert(assertion: Boolean, lazyMessage: LazyMessage? = null) {
|
||||||
|
contract { returns() implies assertion }
|
||||||
|
if (!assertion) {
|
||||||
|
throw AssertionError(lazyMessage?.invoke()?.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SoftAssertScope : AssertScope {
|
||||||
|
private val assertions = mutableListOf<Throwable>()
|
||||||
|
|
||||||
|
override fun assert(assertion: Boolean, lazyMessage: LazyMessage?) {
|
||||||
|
if (!assertion) {
|
||||||
|
assertions.add(AssertionError(lazyMessage?.invoke()?.toString()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun close(exception: Throwable? = null) {
|
||||||
|
if (assertions.isNotEmpty()) {
|
||||||
|
val base = exception ?: AssertionError("Multiple failed assertions")
|
||||||
|
for (assertion in assertions) {
|
||||||
|
base.addSuppressed(assertion)
|
||||||
|
}
|
||||||
|
throw base
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalContracts::class)
|
||||||
|
fun <R> assertSoftly(block: AssertScope.() -> R): R {
|
||||||
|
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
|
||||||
|
|
||||||
|
val scope = SoftAssertScope()
|
||||||
|
val result = runCatching { scope.block() }
|
||||||
|
scope.close(result.exceptionOrNull())
|
||||||
|
return result.getOrThrow()
|
||||||
|
}
|
||||||
@@ -29,4 +29,22 @@ class PowerAssertTest {
|
|||||||
fun require() {
|
fun require() {
|
||||||
require(Person.UNKNOWN.size == 1)
|
require(Person.UNKNOWN.size == 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun softAssert() {
|
||||||
|
val unknown: List<Person>? = Person.UNKNOWN
|
||||||
|
assert(unknown != null)
|
||||||
|
assert(unknown.size == 2)
|
||||||
|
|
||||||
|
val jane: Person
|
||||||
|
val john: Person
|
||||||
|
assertSoftly {
|
||||||
|
jane = unknown[0]
|
||||||
|
assert(jane.firstName == "Jane")
|
||||||
|
assert(jane.lastName == "Doe") { "bad jane last name" }
|
||||||
|
|
||||||
|
john = unknown[1]
|
||||||
|
assert(john.lastName == "Doe" && john.firstName == "John") { "bad john" }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user