From 72bb26572cac08ed6148c455e0fdf2442cceee69 Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Tue, 9 Aug 2022 11:25:45 +0300 Subject: [PATCH] Improve exception rendering in parametrized tests --- .../model/constructor/context/CgContext.kt | 7 ++- .../tree/CgCallableAccessManager.kt | 2 + .../constructor/tree/CgMethodConstructor.kt | 55 +++++++++++-------- .../util/CgStatementConstructor.kt | 13 +++++ .../framework/codegen/model/tree/CgElement.kt | 11 ++++ .../model/visitor/CgAbstractRenderer.kt | 10 ++++ .../codegen/model/visitor/CgVisitor.kt | 4 ++ 7 files changed, 77 insertions(+), 25 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt index dd9cc7c0cb..7e6b8bbad7 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt @@ -171,6 +171,10 @@ internal interface CgContextOwner { // a variable representing an actual result of the method under test call var actual: CgVariable + // a variable representing if test method contains reflective call or not + // and should we catch exceptions like InvocationTargetException or not so on + var containsReflectiveCall: Boolean + // map from a set of tests for a method to another map // which connects code generation error message // with the number of times it occurred @@ -420,7 +424,8 @@ internal data class CgContext( override val runtimeExceptionTestsBehaviour: RuntimeExceptionTestsBehaviour = RuntimeExceptionTestsBehaviour.defaultItem, override val hangingTestsTimeout: HangingTestsTimeout = HangingTestsTimeout(), - override val enableTestsTimeout: Boolean = true + override val enableTestsTimeout: Boolean = true, + override var containsReflectiveCall: Boolean = false, ) : CgContextOwner { override lateinit var statesCache: EnvironmentFieldStateCache override lateinit var actual: CgVariable diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgCallableAccessManager.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgCallableAccessManager.kt index 65473b6f44..0c00b74706 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgCallableAccessManager.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgCallableAccessManager.kt @@ -359,6 +359,7 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA } private fun MethodId.callWithReflection(caller: CgExpression?, args: List): CgMethodCall { + containsReflectiveCall = true val method = declaredExecutableRefs[this] ?: toExecutableVariable(args).also { declaredExecutableRefs = declaredExecutableRefs.put(this, it) @@ -372,6 +373,7 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA } private fun ConstructorId.callWithReflection(args: List): CgExecutableCall { + containsReflectiveCall = true val constructor = declaredExecutableRefs[this] ?: this.toExecutableVariable(args).also { declaredExecutableRefs = declaredExecutableRefs.put(this, it) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt index a54b59004b..de071c9eb1 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt @@ -41,6 +41,7 @@ import org.utbot.framework.codegen.model.tree.CgExecutableCall import org.utbot.framework.codegen.model.tree.CgExpression import org.utbot.framework.codegen.model.tree.CgFieldAccess import org.utbot.framework.codegen.model.tree.CgGetJavaClass +import org.utbot.framework.codegen.model.tree.CgIsInstance import org.utbot.framework.codegen.model.tree.CgLiteral import org.utbot.framework.codegen.model.tree.CgMethod import org.utbot.framework.codegen.model.tree.CgMethodCall @@ -1195,20 +1196,6 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c val testName = nameGenerator.parameterizedTestMethodName(dataProviderMethodName) withNameScope { val testParameterDeclarations = createParameterDeclarations(testSet, genericExecution) - val mainBody = { - substituteStaticFields(statics, isParametrized = true) - // build this instance - thisInstance = genericExecution.stateBefore.thisInstance?.let { currentMethodParameters[CgParameterKind.ThisInstance] } - - // build arguments for method under test and parameterized test - for (index in genericExecution.stateBefore.parameters.indices) { - methodArguments += currentMethodParameters[CgParameterKind.Argument(index)]!! - } - - //record result and generate result assertions - recordActualResult() - generateAssertionsForParameterizedTest() - } methodType = PARAMETRIZED testMethod( @@ -1219,21 +1206,39 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c dataProviderMethodName ) { rememberInitialStaticFields(statics) + substituteStaticFields(statics, isParametrized = true) + + // build this instance + thisInstance = genericExecution.stateBefore.thisInstance?.let { currentMethodParameters[CgParameterKind.ThisInstance] } + + // build arguments for method under test and parameterized test + for (index in genericExecution.stateBefore.parameters.indices) { + methodArguments += currentMethodParameters[CgParameterKind.Argument(index)]!! + } if (containsFailureExecution(testSet) || statics.isNotEmpty()) { var currentTryBlock = tryBlock { - mainBody() + recordActualResult() + generateAssertionsForParameterizedTest() } if (containsFailureExecution(testSet)) { - currentTryBlock = currentTryBlock.catch(Throwable::class.java.id) { e -> - val pseudoExceptionVarName = when (codegenLanguage) { - CodegenLanguage.JAVA -> "${expectedErrorVarName}.isInstance(${e.name.decapitalize()})" - CodegenLanguage.KOTLIN -> "${expectedErrorVarName}!!.isInstance(${e.name.decapitalize()})" + val expectedErrorVariable = currentMethodParameters[CgParameterKind.ExpectedException] + ?: error("Test set $testSet contains failure execution, but test method signature has no error parameter") + currentTryBlock = + if (containsReflectiveCall) { + currentTryBlock.catch(InvocationTargetException::class.java.id) { exception -> + testFrameworkManager.assertBoolean( + expectedErrorVariable.isInstance(exception[getTargetException]()) + ) + } + } else { + currentTryBlock.catch(Throwable::class.java.id) { throwable -> + testFrameworkManager.assertBoolean( + expectedErrorVariable.isInstance(throwable) + ) + } } - - testFrameworkManager.assertBoolean(CgVariable(pseudoExceptionVarName, booleanClassId)) - } } if (statics.isNotEmpty()) { @@ -1243,7 +1248,8 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c } +currentTryBlock } else { - mainBody() + recordActualResult() + generateAssertionsForParameterizedTest() } } } @@ -1341,7 +1347,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c name = nameGenerator.variableName(expectedErrorVarName) ), // exceptions are always reference type - isReferenceType = true + isReferenceType = true, ) this += expectedException currentMethodParameters[CgParameterKind.ExpectedException] = expectedException.parameter @@ -1466,6 +1472,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c thisInstance = null methodArguments.clear() currentExecution = null + containsReflectiveCall = false mockFrameworkManager.clearExecutionResources() currentMethodParameters.clear() } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/CgStatementConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/CgStatementConstructor.kt index 53c45ac091..0ccc973ce3 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/CgStatementConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/CgStatementConstructor.kt @@ -58,6 +58,8 @@ import org.utbot.framework.plugin.api.util.objectArrayClassId import org.utbot.framework.plugin.api.util.objectClassId import fj.data.Either import org.utbot.framework.codegen.model.tree.CgArrayInitializer +import org.utbot.framework.codegen.model.tree.CgIsInstance +import org.utbot.framework.plugin.api.util.classClassId import java.lang.reflect.Constructor import java.lang.reflect.Method import kotlin.reflect.KFunction @@ -116,6 +118,8 @@ interface CgStatementConstructor { fun CgTryCatch.catch(exception: ClassId, init: (CgVariable) -> Unit): CgTryCatch fun CgTryCatch.finally(init: () -> Unit): CgTryCatch + fun CgExpression.isInstance(value: CgExpression): CgIsInstance + fun innerBlock(init: () -> Unit): CgInnerBlock // fun CgTryCatchBuilder.statements(init: () -> Unit) @@ -302,6 +306,15 @@ internal class CgStatementConstructorImpl(context: CgContext) : return this.copy(finally = finallyBlock) } + override fun CgExpression.isInstance(value: CgExpression): CgIsInstance { + require(this.type == classClassId) { + "isInstance method can be called on object with type $classClassId only, but actual type is ${this.type}" + } + + //TODO: we should better process it as this[isInstanceMethodId](value) as it is a call + return CgIsInstance(this, value) + } + override fun innerBlock(init: () -> Unit): CgInnerBlock = CgInnerBlock(block(init)).also { currentBlock += it diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/CgElement.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/CgElement.kt index 4530c4e5eb..f3ce6cea94 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/CgElement.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/CgElement.kt @@ -75,6 +75,7 @@ interface CgElement { is CgDeclaration -> visit(element) is CgAssignment -> visit(element) is CgTypeCast -> visit(element) + is CgIsInstance -> visit(element) is CgThisInstance -> visit(element) is CgNotNullAssertion -> visit(element) is CgVariable -> visit(element) @@ -537,6 +538,16 @@ class CgTypeCast( override val type: ClassId = targetType } +/** + * Represents [java.lang.Class.isInstance] method. + */ +class CgIsInstance( + val classExpression: CgExpression, + val value: CgExpression, +): CgExpression { + override val type: ClassId = booleanClassId +} + // Value // TODO in general CgLiteral is not CgReferenceExpression because it can hold primitive values diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt index 6938831770..a2ce14285b 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt @@ -39,6 +39,7 @@ import org.utbot.framework.codegen.model.tree.CgGreaterThan import org.utbot.framework.codegen.model.tree.CgIfStatement import org.utbot.framework.codegen.model.tree.CgIncrement import org.utbot.framework.codegen.model.tree.CgInnerBlock +import org.utbot.framework.codegen.model.tree.CgIsInstance import org.utbot.framework.codegen.model.tree.CgLessThan import org.utbot.framework.codegen.model.tree.CgLiteral import org.utbot.framework.codegen.model.tree.CgLogicalAnd @@ -422,6 +423,15 @@ internal abstract class CgAbstractRenderer(val context: CgContext, val printer: print("${element.variable.name}--") } + // isInstance check + + override fun visit(element: CgIsInstance) { + element.classExpression.accept(this) + print(".isInstance(") + element.value.accept(this) + print(")") + } + // Try-catch override fun visit(element: CgTryCatch) { diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgVisitor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgVisitor.kt index f2b6c4d905..2d341a51d1 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgVisitor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgVisitor.kt @@ -42,6 +42,7 @@ import org.utbot.framework.codegen.model.tree.CgGreaterThan import org.utbot.framework.codegen.model.tree.CgIfStatement import org.utbot.framework.codegen.model.tree.CgIncrement import org.utbot.framework.codegen.model.tree.CgInnerBlock +import org.utbot.framework.codegen.model.tree.CgIsInstance import org.utbot.framework.codegen.model.tree.CgLessThan import org.utbot.framework.codegen.model.tree.CgLiteral import org.utbot.framework.codegen.model.tree.CgLogicalAnd @@ -180,6 +181,9 @@ interface CgVisitor { // Type cast fun visit(element: CgTypeCast): R + // isInstance check + fun visit(element: CgIsInstance): R + // This instance fun visit(element: CgThisInstance): R