Skip to content

Commit 1ca23e7

Browse files
authored
Add statics support in parametrized tests (#671)
1 parent 1321946 commit 1ca23e7

File tree

5 files changed

+113
-26
lines changed

5 files changed

+113
-26
lines changed

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/CgMethodTestSet.kt

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ package org.utbot.framework.codegen.model.constructor
22

33
import org.utbot.framework.plugin.api.ClassId
44
import org.utbot.framework.plugin.api.ExecutableId
5+
import org.utbot.framework.plugin.api.FieldId
56
import org.utbot.framework.plugin.api.UtClusterInfo
67
import org.utbot.framework.plugin.api.UtExecution
78
import org.utbot.framework.plugin.api.UtExecutionSuccess
89
import org.utbot.framework.plugin.api.UtMethodTestSet
910
import org.utbot.framework.plugin.api.util.executableId
1011
import org.utbot.framework.plugin.api.util.objectClassId
12+
import org.utbot.framework.plugin.api.util.voidClassId
1113
import soot.jimple.JimpleBody
1214

1315
data class CgMethodTestSet private constructor(
@@ -44,22 +46,46 @@ data class CgMethodTestSet private constructor(
4446
return executionsByResult.map{ (_, executions) -> substituteExecutions(executions) }
4547
}
4648

49+
/**
50+
* Splits [CgMethodTestSet] test sets by affected static fields statics.
51+
*
52+
* A separate test set is created for each combination of modified statics.
53+
*/
54+
fun splitExecutionsByChangedStatics(): List<CgMethodTestSet> {
55+
val successfulExecutions = executions.filter { it.result is UtExecutionSuccess }
56+
val executionsByStaticsUsage: Map<Set<FieldId>, List<UtExecution>> =
57+
if (successfulExecutions.isNotEmpty()) {
58+
successfulExecutions.groupBy {
59+
it.stateBefore.statics.keys
60+
}
61+
} else {
62+
mapOf(executions.first().stateBefore.statics.keys to executions)
63+
}
64+
65+
return executionsByStaticsUsage.map { (_, executions) -> substituteExecutions(executions) }
66+
}
67+
4768
/**
4869
* Finds a [ClassId] of all result models in executions.
4970
*
50-
* Tries to find an unique result type in testSets or
71+
* Tries to find a unique result type in testSets or
5172
* gets executable return type.
5273
*/
5374
fun resultType(): ClassId {
54-
val successfulExecutions = executions.filter { it.result is UtExecutionSuccess }
55-
return if (successfulExecutions.isNotEmpty()) {
56-
successfulExecutions
57-
.map { (it.result as UtExecutionSuccess).model.classId }
58-
.distinct()
59-
.singleOrNull()
60-
?: executableId.returnType
61-
} else {
62-
executableId.returnType
75+
return when (executableId.returnType) {
76+
voidClassId -> executableId.returnType
77+
else -> {
78+
val successfulExecutions = executions.filter { it.result is UtExecutionSuccess }
79+
if (successfulExecutions.isNotEmpty()) {
80+
successfulExecutions
81+
.map { (it.result as UtExecutionSuccess).model.classId }
82+
.distinct()
83+
.singleOrNull()
84+
?: executableId.returnType
85+
} else {
86+
executableId.returnType
87+
}
88+
}
6389
}
6490
}
6591

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/name/CgNameGenerator.kt

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ internal interface CgNameGenerator {
1818
/**
1919
* Generate a variable name given a [base] name.
2020
* @param isMock denotes whether a variable represents a mock object or not
21+
* @param isStatic denotes whether a variable represents a static variable or not
2122
*/
22-
fun variableName(base: String, isMock: Boolean = false): String
23+
fun variableName(base: String, isMock: Boolean = false, isStatic: Boolean = false): String
2324

2425
/**
2526
* Convert a given class id to a string that can serve
@@ -69,8 +70,12 @@ internal interface CgNameGenerator {
6970
internal class CgNameGeneratorImpl(private val context: CgContext)
7071
: CgNameGenerator, CgContextOwner by context {
7172

72-
override fun variableName(base: String, isMock: Boolean): String {
73-
val baseName = if (isMock) base + "Mock" else base
73+
override fun variableName(base: String, isMock: Boolean, isStatic: Boolean): String {
74+
val baseName = when {
75+
isMock -> base + "Mock"
76+
isStatic -> base + "Static"
77+
else -> base
78+
}
7479
return when {
7580
baseName in existingVariableNames -> nextIndexedVarName(baseName)
7681
isLanguageKeyword(baseName, codegenLanguage) -> createNameFromKeyword(baseName)

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -212,8 +212,9 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
212212
* Thus, this method only caches an actual initial static fields state in order to recover it
213213
* at the end of the test, and it has nothing to do with the 'before' and 'after' caches.
214214
*/
215-
private fun rememberInitialStaticFields() {
216-
for ((field, _) in currentExecution!!.stateBefore.statics.accessibleFields()) {
215+
private fun rememberInitialStaticFields(statics: Map<FieldId, UtModel>) {
216+
val accessibleStaticFields = statics.accessibleFields()
217+
for ((field, _) in accessibleStaticFields) {
217218
val declaringClass = field.declaringClass
218219
val fieldAccessible = field.isAccessibleFrom(testClassPackageName)
219220

@@ -236,11 +237,18 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
236237
}
237238
}
238239

239-
private fun mockStaticFields() {
240-
for ((field, model) in currentExecution!!.stateBefore.statics.accessibleFields()) {
240+
private fun substituteStaticFields(statics: Map<FieldId, UtModel>, isParametrized: Boolean = false) {
241+
val accessibleStaticFields = statics.accessibleFields()
242+
for ((field, model) in accessibleStaticFields) {
241243
val declaringClass = field.declaringClass
242244
val fieldAccessible = field.canBeSetIn(testClassPackageName)
243-
val fieldValue = variableConstructor.getOrCreateVariable(model, field.name)
245+
246+
val fieldValue = if (isParametrized) {
247+
currentMethodParameters[CgParameterKind.Statics(model)]
248+
} else {
249+
variableConstructor.getOrCreateVariable(model, field.name)
250+
}
251+
244252
if (fieldAccessible) {
245253
declaringClass[field] `=` fieldValue
246254
} else {
@@ -1097,12 +1105,13 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
10971105
// TODO: remove this line when SAT-1273 is completed
10981106
execution.displayName = execution.displayName?.let { "${executableId.name}: $it" }
10991107
testMethod(testMethodName, execution.displayName) {
1100-
rememberInitialStaticFields()
1108+
val statics = currentExecution!!.stateBefore.statics
1109+
rememberInitialStaticFields(statics)
11011110
val stateAnalyzer = ExecutionStateAnalyzer(execution)
11021111
val modificationInfo = stateAnalyzer.findModifiedFields()
11031112
// TODO: move such methods to another class and leave only 2 public methods: remember initial and final states
11041113
val mainBody = {
1105-
mockStaticFields()
1114+
substituteStaticFields(statics)
11061115
setupInstrumentation()
11071116
// build this instance
11081117
thisInstance = execution.stateBefore.thisInstance?.let {
@@ -1120,7 +1129,6 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
11201129
generateFieldStateAssertions()
11211130
}
11221131

1123-
val statics = currentExecution!!.stateBefore.statics
11241132
if (statics.isNotEmpty()) {
11251133
+tryBlock {
11261134
mainBody()
@@ -1177,11 +1185,14 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
11771185
.firstOrNull { it.result is UtExecutionSuccess && (it.result as UtExecutionSuccess).model !is UtNullModel }
11781186
?: testSet.executions.first()
11791187

1188+
val statics = genericExecution.stateBefore.statics
1189+
11801190
return withTestMethodScope(genericExecution) {
11811191
val testName = nameGenerator.parameterizedTestMethodName(dataProviderMethodName)
11821192
withNameScope {
11831193
val testParameterDeclarations = createParameterDeclarations(testSet, genericExecution)
11841194
val mainBody = {
1195+
substituteStaticFields(statics, isParametrized = true)
11851196
// build this instance
11861197
thisInstance = genericExecution.stateBefore.thisInstance?.let { currentMethodParameters[CgParameterKind.ThisInstance] }
11871198

@@ -1203,16 +1214,30 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
12031214
parameterized = true,
12041215
dataProviderMethodName
12051216
) {
1206-
if (containsFailureExecution(testSet)) {
1207-
+tryBlock(mainBody)
1208-
.catch(Throwable::class.java.id) { e ->
1217+
rememberInitialStaticFields(statics)
1218+
1219+
if (containsFailureExecution(testSet) || statics.isNotEmpty()) {
1220+
var currentTryBlock = tryBlock {
1221+
mainBody()
1222+
}
1223+
1224+
if (containsFailureExecution(testSet)) {
1225+
currentTryBlock = currentTryBlock.catch(Throwable::class.java.id) { e ->
12091226
val pseudoExceptionVarName = when (codegenLanguage) {
12101227
CodegenLanguage.JAVA -> "${expectedErrorVarName}.isInstance(${e.name.decapitalize()})"
12111228
CodegenLanguage.KOTLIN -> "${expectedErrorVarName}!!.isInstance(${e.name.decapitalize()})"
12121229
}
12131230

12141231
testFrameworkManager.assertBoolean(CgVariable(pseudoExceptionVarName, booleanClassId))
12151232
}
1233+
}
1234+
1235+
if (statics.isNotEmpty()) {
1236+
currentTryBlock = currentTryBlock.finally {
1237+
recoverStaticFields()
1238+
}
1239+
}
1240+
+currentTryBlock
12161241
} else {
12171242
mainBody()
12181243
}
@@ -1265,6 +1290,22 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
12651290
currentMethodParameters[CgParameterKind.Argument(index)] = argument.parameter
12661291
}
12671292

1293+
val statics = genericExecution.stateBefore.statics
1294+
if (statics.isNotEmpty()) {
1295+
for ((fieldId, model) in statics) {
1296+
val staticType = wrapTypeIfRequired(model.classId)
1297+
val static = CgParameterDeclaration(
1298+
parameter = declareParameter(
1299+
type = staticType,
1300+
name = nameGenerator.variableName(fieldId.name, isStatic = true)
1301+
),
1302+
isReferenceType = staticType.isRefType
1303+
)
1304+
this += static
1305+
currentMethodParameters[CgParameterKind.Statics(model)] = static.parameter
1306+
}
1307+
}
1308+
12681309
val expectedResultClassId = wrapTypeIfRequired(testSet.resultType())
12691310
if (expectedResultClassId != voidClassId) {
12701311
val wrappedType = wrapIfPrimitive(expectedResultClassId)
@@ -1347,6 +1388,12 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
13471388
arguments += variableConstructor.getOrCreateVariable(paramModel, argumentName)
13481389
}
13491390

1391+
val statics = execution.stateBefore.statics
1392+
for ((field, model) in statics) {
1393+
arguments += variableConstructor.getOrCreateVariable(model, field.name)
1394+
}
1395+
1396+
13501397
val method = currentExecutable!!
13511398
val needsReturnValue = method.returnType != voidClassId
13521399
val containsFailureExecution = containsFailureExecution(testSet)

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,15 @@ internal class CgTestClassConstructor(val context: CgContext) :
107107
}
108108
}
109109
ParametrizedTestSource.PARAMETRIZE -> {
110-
for (currentTestSet in testSet.splitExecutionsByResult()) {
111-
createParametrizedTestAndDataProvider(currentTestSet, requiredFields, regions, methodUnderTest)
110+
for (splitByExecutionTestSet in testSet.splitExecutionsByResult()) {
111+
for (splitByChangedStaticsTestSet in splitByExecutionTestSet.splitExecutionsByChangedStatics()) {
112+
createParametrizedTestAndDataProvider(
113+
splitByChangedStaticsTestSet,
114+
requiredFields,
115+
regions,
116+
methodUnderTest
117+
)
118+
}
112119
}
113120
}
114121
}

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/CgElement.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import org.utbot.framework.plugin.api.ExecutableId
1919
import org.utbot.framework.plugin.api.FieldId
2020
import org.utbot.framework.plugin.api.MethodId
2121
import org.utbot.framework.plugin.api.TypeParameters
22+
import org.utbot.framework.plugin.api.UtModel
2223
import org.utbot.framework.plugin.api.util.booleanClassId
2324
import org.utbot.framework.plugin.api.util.id
2425
import org.utbot.framework.plugin.api.util.intClassId
@@ -630,6 +631,7 @@ data class CgParameterDeclaration(
630631
sealed class CgParameterKind {
631632
object ThisInstance : CgParameterKind()
632633
data class Argument(val index: Int) : CgParameterKind()
634+
data class Statics(val model: UtModel) : CgParameterKind()
633635
object ExpectedResult : CgParameterKind()
634636
object ExpectedException : CgParameterKind()
635637
}

0 commit comments

Comments
 (0)