Skip to content

Improve exception rendering in parametrized tests #690

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA
}

private fun MethodId.callWithReflection(caller: CgExpression?, args: List<CgExpression>): CgMethodCall {
containsReflectiveCall = true
val method = declaredExecutableRefs[this]
?: toExecutableVariable(args).also {
declaredExecutableRefs = declaredExecutableRefs.put(this, it)
Expand All @@ -372,6 +373,7 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA
}

private fun ConstructorId.callWithReflection(args: List<CgExpression>): CgExecutableCall {
containsReflectiveCall = true
val constructor = declaredExecutableRefs[this]
?: this.toExecutableVariable(args).also {
declaredExecutableRefs = declaredExecutableRefs.put(this, it)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand All @@ -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()) {
Expand All @@ -1243,7 +1248,8 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
}
+currentTryBlock
} else {
mainBody()
recordActualResult()
generateAssertionsForParameterizedTest()
}
}
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -180,6 +181,9 @@ interface CgVisitor<R> {
// Type cast
fun visit(element: CgTypeCast): R

// isInstance check
fun visit(element: CgIsInstance): R

// This instance
fun visit(element: CgThisInstance): R

Expand Down