diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt index 18489e9094..f069490ddf 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt @@ -282,12 +282,12 @@ sealed class UtReferenceModel( ) : UtModel(classId) /** - * Checks if [UtModel] is a null. + * Checks if [UtModel] is a [UtNullModel]. */ fun UtModel.isNull() = this is UtNullModel /** - * Checks if [UtModel] is not a null. + * Checks if [UtModel] is not a [UtNullModel]. */ fun UtModel.isNotNull() = !isNull() diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt index 6e4b997cff..bf6fd56194 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt @@ -40,7 +40,7 @@ class CodeGenerator( testFramework = testFramework, mockFramework = mockFramework ?: MockFramework.MOCKITO, codegenLanguage = codegenLanguage, - parameterizedTestSource = parameterizedTestSource, + parametrizedTestSource = parameterizedTestSource, staticsMocking = staticsMocking, forceStaticMocking = forceStaticMocking, generateWarningsForStaticMocking = generateWarningsForStaticMocking, 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 7192e34e12..17512357d6 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 @@ -139,7 +139,7 @@ internal interface CgContextOwner { val codegenLanguage: CodegenLanguage - val parameterizedTestSource: ParametrizedTestSource + val parametrizedTestSource: ParametrizedTestSource // flag indicating whether a mock framework is used in the generated code var mockFrameworkUsed: Boolean @@ -214,6 +214,8 @@ internal interface CgContextOwner { var statesCache: EnvironmentFieldStateCache + var allExecutions: List + fun block(init: () -> Unit): Block { val prevBlock = currentBlock return try { @@ -407,7 +409,7 @@ internal data class CgContext( override val forceStaticMocking: ForceStaticMocking, override val generateWarningsForStaticMocking: Boolean, override val codegenLanguage: CodegenLanguage = CodegenLanguage.defaultItem, - override val parameterizedTestSource: ParametrizedTestSource = ParametrizedTestSource.DO_NOT_PARAMETRIZE, + override val parametrizedTestSource: ParametrizedTestSource = ParametrizedTestSource.DO_NOT_PARAMETRIZE, override var mockFrameworkUsed: Boolean = false, override var currentBlock: PersistentList = persistentListOf(), override var existingVariableNames: PersistentSet = persistentSetOf(), @@ -427,6 +429,7 @@ internal data class CgContext( ) : CgContextOwner { override lateinit var statesCache: EnvironmentFieldStateCache override lateinit var actual: CgVariable + override lateinit var allExecutions: List /** * This property cannot be accessed outside of test class file scope 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 1ab2f70fe7..4f4f18d119 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 @@ -37,7 +37,6 @@ 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 @@ -111,9 +110,10 @@ import org.utbot.framework.plugin.api.UtStaticMethodInstrumentation import org.utbot.framework.plugin.api.UtSymbolicExecution import org.utbot.framework.plugin.api.UtTimeoutException import org.utbot.framework.plugin.api.UtVoidModel +import org.utbot.framework.plugin.api.isNotNull +import org.utbot.framework.plugin.api.isNull import org.utbot.framework.plugin.api.onFailure import org.utbot.framework.plugin.api.onSuccess -import org.utbot.framework.plugin.api.util.booleanClassId import org.utbot.framework.plugin.api.util.doubleArrayClassId import org.utbot.framework.plugin.api.util.doubleClassId import org.utbot.framework.plugin.api.util.doubleWrapperClassId @@ -144,7 +144,6 @@ import org.utbot.summary.SummarySentenceConstants.TAB import java.lang.reflect.InvocationTargetException import java.security.AccessControlException import java.lang.reflect.ParameterizedType -import kotlin.reflect.jvm.javaType private const val DEEP_EQUALS_MAX_DEPTH = 5 // TODO move it to plugin settings? @@ -168,6 +167,8 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c private lateinit var methodType: CgTestMethodType + private val fieldsOfExecutionResults = mutableMapOf, MutableList>() + private fun setupInstrumentation() { if (currentExecution is UtSymbolicExecution) { val execution = currentExecution as UtSymbolicExecution @@ -445,7 +446,6 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c val expectedExpression = CgNotNullAssertion(expectedVariable) assertEquality(expectedExpression, actual) - println() } } .onFailure { thisInstance[method](*methodArguments.toTypedArray()).intercepted() } @@ -537,7 +537,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c doubleDelta ) expectedModel.value is Boolean -> { - when (parameterizedTestSource) { + when (parametrizedTestSource) { ParametrizedTestSource.DO_NOT_PARAMETRIZE -> if (expectedModel.value as Boolean) { assertions[assertTrue](actual) @@ -842,6 +842,25 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c return } + when (parametrizedTestSource) { + ParametrizedTestSource.DO_NOT_PARAMETRIZE -> { + traverseField(fieldId, fieldModel, expected, actual, depth, visitedModels) + } + + ParametrizedTestSource.PARAMETRIZE -> { + traverseFieldForParametrizedTest(fieldId, fieldModel, expected, actual, depth, visitedModels) + } + } + } + + private fun traverseField( + fieldId: FieldId, + fieldModel: UtModel, + expected: CgVariable, + actual: CgVariable, + depth: Int, + visitedModels: MutableSet + ) { // fieldModel is not visited and will be marked in assertDeepEquals call val fieldName = fieldId.name var expectedVariable: CgVariable? = null @@ -866,6 +885,140 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c emptyLineIfNeeded() } + private fun traverseFieldForParametrizedTest( + fieldId: FieldId, + fieldModel: UtModel, + expected: CgVariable, + actual: CgVariable, + depth: Int, + visitedModels: MutableSet + ) { + val fieldResultModels = fieldsOfExecutionResults[fieldId to depth] + val nullResultModelInExecutions = fieldResultModels?.find { it.isNull() } + val notNullResultModelInExecutions = fieldResultModels?.find { it.isNotNull() } + + val hasNullResultModel = nullResultModelInExecutions != null + val hasNotNullResultModel = notNullResultModelInExecutions != null + + val needToSubstituteFieldModel = fieldModel is UtNullModel && hasNotNullResultModel + + val fieldModelForAssert = if (needToSubstituteFieldModel) notNullResultModelInExecutions!! else fieldModel + + // fieldModel is not visited and will be marked in assertDeepEquals call + val fieldName = fieldId.name + var expectedVariable: CgVariable? = null + + val needExpectedDeclaration = needExpectedDeclaration(fieldModelForAssert) + if (needExpectedDeclaration) { + val expectedFieldDeclaration = createDeclarationForFieldFromVariable(fieldId, expected, fieldName) + + currentBlock += expectedFieldDeclaration + expectedVariable = expectedFieldDeclaration.variable + } + + val actualFieldDeclaration = createDeclarationForFieldFromVariable(fieldId, actual, fieldName) + currentBlock += actualFieldDeclaration + + if (needExpectedDeclaration && hasNullResultModel) { + ifStatement( + CgEqualTo(expectedVariable!!, nullLiteral()), + trueBranch = { +testFrameworkManager.assertions[testFramework.assertNull](actualFieldDeclaration.variable).toStatement() }, + falseBranch = { + assertDeepEquals( + fieldModelForAssert, + expectedVariable, + actualFieldDeclaration.variable, + depth + 1, + visitedModels, + ) + } + ) + } else { + assertDeepEquals( + fieldModelForAssert, + expectedVariable, + actualFieldDeclaration.variable, + depth + 1, + visitedModels, + ) + } + emptyLineIfNeeded() + } + + private fun collectExecutionsResultFields() { + val successfulExecutionsModels = allExecutions + .filter { + it.result is UtExecutionSuccess + }.map { + (it.result as UtExecutionSuccess).model + } + + for (model in successfulExecutionsModels) { + when (model) { + is UtCompositeModel -> { + for ((fieldId, fieldModel) in model.fields) { + collectExecutionsResultFieldsRecursively(fieldId, fieldModel, 0) + } + } + + is UtAssembleModel -> { + model.origin?.let { + for ((fieldId, fieldModel) in it.fields) { + collectExecutionsResultFieldsRecursively(fieldId, fieldModel, 0) + } + } + } + + is UtNullModel, + is UtPrimitiveModel, + is UtArrayModel, + is UtClassRefModel, + is UtEnumConstantModel, + is UtVoidModel -> { + // only [UtCompositeModel] and [UtAssembleModel] have fields to traverse + } + } + } + } + + private fun collectExecutionsResultFieldsRecursively( + fieldId: FieldId, + fieldModel: UtModel, + depth: Int, + ) { + if (depth >= DEEP_EQUALS_MAX_DEPTH) { + return + } + + val fieldKey = fieldId to depth + fieldsOfExecutionResults.getOrPut(fieldKey) { mutableListOf() } += fieldModel + + when (fieldModel) { + is UtCompositeModel -> { + for ((id, model) in fieldModel.fields) { + collectExecutionsResultFieldsRecursively(id, model, depth + 1) + } + } + + is UtAssembleModel -> { + fieldModel.origin?.let { + for ((id, model) in it.fields) { + collectExecutionsResultFieldsRecursively(id, model, depth + 1) + } + } + } + + is UtNullModel, + is UtPrimitiveModel, + is UtArrayModel, + is UtClassRefModel, + is UtEnumConstantModel, + is UtVoidModel -> { + // only [UtCompositeModel] and [UtAssembleModel] have fields to traverse + } + } + } + @Suppress("UNUSED_ANONYMOUS_PARAMETER") private fun createDeclarationForFieldFromVariable( fieldId: FieldId, @@ -999,7 +1152,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c } expected == nullLiteral() -> testFrameworkManager.assertNull(actual) expected is CgLiteral && expected.value is Boolean -> { - when (parameterizedTestSource) { + when (parametrizedTestSource) { ParametrizedTestSource.DO_NOT_PARAMETRIZE -> testFrameworkManager.assertBoolean(expected.value, actual) ParametrizedTestSource.PARAMETRIZE -> @@ -1054,15 +1207,19 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c expected: CgValue, actual: CgVariable, ) { - when (parameterizedTestSource) { + when (parametrizedTestSource) { ParametrizedTestSource.DO_NOT_PARAMETRIZE -> generateDeepEqualsAssertion(expected, actual) - ParametrizedTestSource.PARAMETRIZE -> when { - actual.type.isPrimitive -> generateDeepEqualsAssertion(expected, actual) - else -> ifStatement( - CgEqualTo(expected, nullLiteral()), - trueBranch = { +testFrameworkManager.assertions[testFramework.assertNull](actual).toStatement() }, - falseBranch = { generateDeepEqualsAssertion(expected, actual) } - ) + ParametrizedTestSource.PARAMETRIZE -> { + collectExecutionsResultFields() + + when { + actual.type.isPrimitive -> generateDeepEqualsAssertion(expected, actual) + else -> ifStatement( + CgEqualTo(expected, nullLiteral()), + trueBranch = { +testFrameworkManager.assertions[testFramework.assertNull](actual).toStatement() }, + falseBranch = { generateDeepEqualsAssertion(expected, actual) } + ) + } } } } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt index bd331e99a0..815110caa4 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt @@ -118,11 +118,13 @@ internal class CgTestClassConstructor(val context: CgContext) : return null } + allExecutions = testSet.executions + val (methodUnderTest, _, _, clustersInfo) = testSet val regions = mutableListOf>() val requiredFields = mutableListOf() - when (context.parameterizedTestSource) { + when (context.parametrizedTestSource) { ParametrizedTestSource.DO_NOT_PARAMETRIZE -> { for ((clusterSummary, executionIndices) in clustersInfo) { val currentTestCaseTestMethods = mutableListOf() diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/codegen/deepequals/ClassWithNullableFieldTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/codegen/deepequals/ClassWithNullableFieldTest.kt index 9882b16bc7..ec33b743f1 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/codegen/deepequals/ClassWithNullableFieldTest.kt +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/codegen/deepequals/ClassWithNullableFieldTest.kt @@ -23,4 +23,13 @@ class ClassWithNullableFieldTest : UtValueTestCaseChecker( coverage = DoNotCalculate ) } + + @Test + fun testClassWithNullableField1() { + check( + ClassWithNullableField::returnGreatCompoundWithNullableField, + eq(3), + coverage = DoNotCalculate + ) + } } \ No newline at end of file diff --git a/utbot-sample/src/main/java/org/utbot/examples/codegen/deepequals/ClassWithNullableField.java b/utbot-sample/src/main/java/org/utbot/examples/codegen/deepequals/ClassWithNullableField.java index be72bcf3ca..cfb2ca5c65 100644 --- a/utbot-sample/src/main/java/org/utbot/examples/codegen/deepequals/ClassWithNullableField.java +++ b/utbot-sample/src/main/java/org/utbot/examples/codegen/deepequals/ClassWithNullableField.java @@ -12,9 +12,23 @@ class Compound { } } +class GreatCompound { + Compound compound; + + GreatCompound(Compound compound) { + this.compound = compound; + } +} + public class ClassWithNullableField { public Compound returnCompoundWithNullableField(int value) { if (value > 0) return new Compound(null); else return new Compound(new Component()); } + + public GreatCompound returnGreatCompoundWithNullableField(int value) { + if (value > 0) return new GreatCompound(null); + else if (value == 0) return new GreatCompound(new Compound(new Component())); + else return new GreatCompound(new Compound(null)); + } }