diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/CgMethodTestSet.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/CgMethodTestSet.kt index cf78fe3279..a58b1007b5 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/CgMethodTestSet.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/CgMethodTestSet.kt @@ -2,12 +2,14 @@ package org.utbot.framework.codegen.model.constructor import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.FieldId import org.utbot.framework.plugin.api.UtClusterInfo import org.utbot.framework.plugin.api.UtExecution import org.utbot.framework.plugin.api.UtExecutionSuccess import org.utbot.framework.plugin.api.UtMethodTestSet import org.utbot.framework.plugin.api.util.executableId import org.utbot.framework.plugin.api.util.objectClassId +import org.utbot.framework.plugin.api.util.voidClassId import soot.jimple.JimpleBody data class CgMethodTestSet private constructor( @@ -44,22 +46,46 @@ data class CgMethodTestSet private constructor( return executionsByResult.map{ (_, executions) -> substituteExecutions(executions) } } + /** + * Splits [CgMethodTestSet] test sets by affected static fields statics. + * + * A separate test set is created for each combination of modified statics. + */ + fun splitExecutionsByChangedStatics(): List { + val successfulExecutions = executions.filter { it.result is UtExecutionSuccess } + val executionsByStaticsUsage: Map, List> = + if (successfulExecutions.isNotEmpty()) { + successfulExecutions.groupBy { + it.stateBefore.statics.keys + } + } else { + mapOf(executions.first().stateBefore.statics.keys to executions) + } + + return executionsByStaticsUsage.map { (_, executions) -> substituteExecutions(executions) } + } + /** * Finds a [ClassId] of all result models in executions. * - * Tries to find an unique result type in testSets or + * Tries to find a unique result type in testSets or * gets executable return type. */ fun resultType(): ClassId { - val successfulExecutions = executions.filter { it.result is UtExecutionSuccess } - return if (successfulExecutions.isNotEmpty()) { - successfulExecutions - .map { (it.result as UtExecutionSuccess).model.classId } - .distinct() - .singleOrNull() - ?: executableId.returnType - } else { - executableId.returnType + return when (executableId.returnType) { + voidClassId -> executableId.returnType + else -> { + val successfulExecutions = executions.filter { it.result is UtExecutionSuccess } + if (successfulExecutions.isNotEmpty()) { + successfulExecutions + .map { (it.result as UtExecutionSuccess).model.classId } + .distinct() + .singleOrNull() + ?: executableId.returnType + } else { + executableId.returnType + } + } } } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/name/CgNameGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/name/CgNameGenerator.kt index 3fd130deec..50254afc03 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/name/CgNameGenerator.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/name/CgNameGenerator.kt @@ -18,8 +18,9 @@ internal interface CgNameGenerator { /** * Generate a variable name given a [base] name. * @param isMock denotes whether a variable represents a mock object or not + * @param isStatic denotes whether a variable represents a static variable or not */ - fun variableName(base: String, isMock: Boolean = false): String + fun variableName(base: String, isMock: Boolean = false, isStatic: Boolean = false): String /** * Convert a given class id to a string that can serve @@ -69,8 +70,12 @@ internal interface CgNameGenerator { internal class CgNameGeneratorImpl(private val context: CgContext) : CgNameGenerator, CgContextOwner by context { - override fun variableName(base: String, isMock: Boolean): String { - val baseName = if (isMock) base + "Mock" else base + override fun variableName(base: String, isMock: Boolean, isStatic: Boolean): String { + val baseName = when { + isMock -> base + "Mock" + isStatic -> base + "Static" + else -> base + } return when { baseName in existingVariableNames -> nextIndexedVarName(baseName) isLanguageKeyword(baseName, codegenLanguage) -> createNameFromKeyword(baseName) 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 42397768d3..b6f8bb3148 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 @@ -212,8 +212,9 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c * Thus, this method only caches an actual initial static fields state in order to recover it * at the end of the test, and it has nothing to do with the 'before' and 'after' caches. */ - private fun rememberInitialStaticFields() { - for ((field, _) in currentExecution!!.stateBefore.statics.accessibleFields()) { + private fun rememberInitialStaticFields(statics: Map) { + val accessibleStaticFields = statics.accessibleFields() + for ((field, _) in accessibleStaticFields) { val declaringClass = field.declaringClass val fieldAccessible = field.isAccessibleFrom(testClassPackageName) @@ -236,11 +237,18 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c } } - private fun mockStaticFields() { - for ((field, model) in currentExecution!!.stateBefore.statics.accessibleFields()) { + private fun substituteStaticFields(statics: Map, isParametrized: Boolean = false) { + val accessibleStaticFields = statics.accessibleFields() + for ((field, model) in accessibleStaticFields) { val declaringClass = field.declaringClass val fieldAccessible = field.canBeSetIn(testClassPackageName) - val fieldValue = variableConstructor.getOrCreateVariable(model, field.name) + + val fieldValue = if (isParametrized) { + currentMethodParameters[CgParameterKind.Statics(model)] + } else { + variableConstructor.getOrCreateVariable(model, field.name) + } + if (fieldAccessible) { declaringClass[field] `=` fieldValue } else { @@ -1097,12 +1105,13 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c // TODO: remove this line when SAT-1273 is completed execution.displayName = execution.displayName?.let { "${executableId.name}: $it" } testMethod(testMethodName, execution.displayName) { - rememberInitialStaticFields() + val statics = currentExecution!!.stateBefore.statics + rememberInitialStaticFields(statics) val stateAnalyzer = ExecutionStateAnalyzer(execution) val modificationInfo = stateAnalyzer.findModifiedFields() // TODO: move such methods to another class and leave only 2 public methods: remember initial and final states val mainBody = { - mockStaticFields() + substituteStaticFields(statics) setupInstrumentation() // build this instance thisInstance = execution.stateBefore.thisInstance?.let { @@ -1120,7 +1129,6 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c generateFieldStateAssertions() } - val statics = currentExecution!!.stateBefore.statics if (statics.isNotEmpty()) { +tryBlock { mainBody() @@ -1177,11 +1185,14 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c .firstOrNull { it.result is UtExecutionSuccess && (it.result as UtExecutionSuccess).model !is UtNullModel } ?: testSet.executions.first() + val statics = genericExecution.stateBefore.statics + return withTestMethodScope(genericExecution) { 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] } @@ -1203,9 +1214,15 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c parameterized = true, dataProviderMethodName ) { - if (containsFailureExecution(testSet)) { - +tryBlock(mainBody) - .catch(Throwable::class.java.id) { e -> + rememberInitialStaticFields(statics) + + if (containsFailureExecution(testSet) || statics.isNotEmpty()) { + var currentTryBlock = tryBlock { + mainBody() + } + + 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()})" @@ -1213,6 +1230,14 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c testFrameworkManager.assertBoolean(CgVariable(pseudoExceptionVarName, booleanClassId)) } + } + + if (statics.isNotEmpty()) { + currentTryBlock = currentTryBlock.finally { + recoverStaticFields() + } + } + +currentTryBlock } else { mainBody() } @@ -1265,6 +1290,22 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c currentMethodParameters[CgParameterKind.Argument(index)] = argument.parameter } + val statics = genericExecution.stateBefore.statics + if (statics.isNotEmpty()) { + for ((fieldId, model) in statics) { + val staticType = wrapTypeIfRequired(model.classId) + val static = CgParameterDeclaration( + parameter = declareParameter( + type = staticType, + name = nameGenerator.variableName(fieldId.name, isStatic = true) + ), + isReferenceType = staticType.isRefType + ) + this += static + currentMethodParameters[CgParameterKind.Statics(model)] = static.parameter + } + } + val expectedResultClassId = wrapTypeIfRequired(testSet.resultType()) if (expectedResultClassId != voidClassId) { val wrappedType = wrapIfPrimitive(expectedResultClassId) @@ -1347,6 +1388,12 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c arguments += variableConstructor.getOrCreateVariable(paramModel, argumentName) } + val statics = execution.stateBefore.statics + for ((field, model) in statics) { + arguments += variableConstructor.getOrCreateVariable(model, field.name) + } + + val method = currentExecutable!! val needsReturnValue = method.returnType != voidClassId val containsFailureExecution = containsFailureExecution(testSet) 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 bbdfa26b19..eca33c5b77 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 @@ -107,8 +107,15 @@ internal class CgTestClassConstructor(val context: CgContext) : } } ParametrizedTestSource.PARAMETRIZE -> { - for (currentTestSet in testSet.splitExecutionsByResult()) { - createParametrizedTestAndDataProvider(currentTestSet, requiredFields, regions, methodUnderTest) + for (splitByExecutionTestSet in testSet.splitExecutionsByResult()) { + for (splitByChangedStaticsTestSet in splitByExecutionTestSet.splitExecutionsByChangedStatics()) { + createParametrizedTestAndDataProvider( + splitByChangedStaticsTestSet, + requiredFields, + regions, + methodUnderTest + ) + } } } } 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 ff80e387b0..4530c4e5eb 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 @@ -19,6 +19,7 @@ import org.utbot.framework.plugin.api.ExecutableId import org.utbot.framework.plugin.api.FieldId import org.utbot.framework.plugin.api.MethodId import org.utbot.framework.plugin.api.TypeParameters +import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.util.booleanClassId import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.intClassId @@ -630,6 +631,7 @@ data class CgParameterDeclaration( sealed class CgParameterKind { object ThisInstance : CgParameterKind() data class Argument(val index: Int) : CgParameterKind() + data class Statics(val model: UtModel) : CgParameterKind() object ExpectedResult : CgParameterKind() object ExpectedException : CgParameterKind() }