Skip to content

Commit bd3ba61

Browse files
sofurihafedenis-fokin
authored andcommitted
Add statics support in parametrized tests (#671)
1 parent 20d1652 commit bd3ba61

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
@@ -216,8 +216,9 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
216216
* Thus, this method only caches an actual initial static fields state in order to recover it
217217
* at the end of the test, and it has nothing to do with the 'before' and 'after' caches.
218218
*/
219-
private fun rememberInitialStaticFields() {
220-
for ((field, _) in currentExecution!!.stateBefore.statics.accessibleFields()) {
219+
private fun rememberInitialStaticFields(statics: Map<FieldId, UtModel>) {
220+
val accessibleStaticFields = statics.accessibleFields()
221+
for ((field, _) in accessibleStaticFields) {
221222
val declaringClass = field.declaringClass
222223
val fieldAccessible = field.isAccessibleFrom(testClassPackageName)
223224

@@ -240,11 +241,18 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
240241
}
241242
}
242243

243-
private fun mockStaticFields() {
244-
for ((field, model) in currentExecution!!.stateBefore.statics.accessibleFields()) {
244+
private fun substituteStaticFields(statics: Map<FieldId, UtModel>, isParametrized: Boolean = false) {
245+
val accessibleStaticFields = statics.accessibleFields()
246+
for ((field, model) in accessibleStaticFields) {
245247
val declaringClass = field.declaringClass
246248
val fieldAccessible = field.canBeSetIn(testClassPackageName)
247-
val fieldValue = variableConstructor.getOrCreateVariable(model, field.name)
249+
250+
val fieldValue = if (isParametrized) {
251+
currentMethodParameters[CgParameterKind.Statics(model)]
252+
} else {
253+
variableConstructor.getOrCreateVariable(model, field.name)
254+
}
255+
248256
if (fieldAccessible) {
249257
declaringClass[field] `=` fieldValue
250258
} else {
@@ -1100,12 +1108,13 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
11001108
// TODO: remove this line when SAT-1273 is completed
11011109
execution.displayName = execution.displayName?.let { "${utMethod.callable.name}: $it" }
11021110
testMethod(testMethodName, execution.displayName) {
1103-
rememberInitialStaticFields()
1111+
val statics = currentExecution!!.stateBefore.statics
1112+
rememberInitialStaticFields(statics)
11041113
val stateAnalyzer = ExecutionStateAnalyzer(execution)
11051114
val modificationInfo = stateAnalyzer.findModifiedFields()
11061115
// TODO: move such methods to another class and leave only 2 public methods: remember initial and final states
11071116
val mainBody = {
1108-
mockStaticFields()
1117+
substituteStaticFields(statics)
11091118
setupInstrumentation()
11101119
// build this instance
11111120
thisInstance = execution.stateBefore.thisInstance?.let {
@@ -1123,7 +1132,6 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
11231132
generateFieldStateAssertions()
11241133
}
11251134

1126-
val statics = currentExecution!!.stateBefore.statics
11271135
if (statics.isNotEmpty()) {
11281136
+tryBlock {
11291137
mainBody()
@@ -1180,11 +1188,14 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
11801188
.firstOrNull { it.result is UtExecutionSuccess && (it.result as UtExecutionSuccess).model !is UtNullModel }
11811189
?: testSet.executions.first()
11821190

1191+
val statics = genericExecution.stateBefore.statics
1192+
11831193
return withTestMethodScope(genericExecution) {
11841194
val testName = nameGenerator.parameterizedTestMethodName(dataProviderMethodName)
11851195
withNameScope {
11861196
val testParameterDeclarations = createParameterDeclarations(testSet, genericExecution)
11871197
val mainBody = {
1198+
substituteStaticFields(statics, isParametrized = true)
11881199
// build this instance
11891200
thisInstance = genericExecution.stateBefore.thisInstance?.let { currentMethodParameters[CgParameterKind.ThisInstance] }
11901201

@@ -1206,16 +1217,30 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
12061217
parameterized = true,
12071218
dataProviderMethodName
12081219
) {
1209-
if (containsFailureExecution(testSet)) {
1210-
+tryBlock(mainBody)
1211-
.catch(Throwable::class.java.id) { e ->
1220+
rememberInitialStaticFields(statics)
1221+
1222+
if (containsFailureExecution(testSet) || statics.isNotEmpty()) {
1223+
var currentTryBlock = tryBlock {
1224+
mainBody()
1225+
}
1226+
1227+
if (containsFailureExecution(testSet)) {
1228+
currentTryBlock = currentTryBlock.catch(Throwable::class.java.id) { e ->
12121229
val pseudoExceptionVarName = when (codegenLanguage) {
12131230
CodegenLanguage.JAVA -> "${expectedErrorVarName}.isInstance(${e.name.decapitalize()})"
12141231
CodegenLanguage.KOTLIN -> "${expectedErrorVarName}!!.isInstance(${e.name.decapitalize()})"
12151232
}
12161233

12171234
testFrameworkManager.assertBoolean(CgVariable(pseudoExceptionVarName, booleanClassId))
12181235
}
1236+
}
1237+
1238+
if (statics.isNotEmpty()) {
1239+
currentTryBlock = currentTryBlock.finally {
1240+
recoverStaticFields()
1241+
}
1242+
}
1243+
+currentTryBlock
12191244
} else {
12201245
mainBody()
12211246
}
@@ -1269,6 +1294,22 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
12691294
currentMethodParameters[CgParameterKind.Argument(index)] = argument.parameter
12701295
}
12711296

1297+
val statics = genericExecution.stateBefore.statics
1298+
if (statics.isNotEmpty()) {
1299+
for ((fieldId, model) in statics) {
1300+
val staticType = wrapTypeIfRequired(model.classId)
1301+
val static = CgParameterDeclaration(
1302+
parameter = declareParameter(
1303+
type = staticType,
1304+
name = nameGenerator.variableName(fieldId.name, isStatic = true)
1305+
),
1306+
isReferenceType = staticType.isRefType
1307+
)
1308+
this += static
1309+
currentMethodParameters[CgParameterKind.Statics(model)] = static.parameter
1310+
}
1311+
}
1312+
12721313
val expectedResultClassId = wrapTypeIfRequired(testSet.resultType())
12731314
if (expectedResultClassId != voidClassId) {
12741315
val wrappedType = wrapIfPrimitive(expectedResultClassId)
@@ -1351,6 +1392,12 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
13511392
arguments += variableConstructor.getOrCreateVariable(paramModel, argumentName)
13521393
}
13531394

1395+
val statics = execution.stateBefore.statics
1396+
for ((field, model) in statics) {
1397+
arguments += variableConstructor.getOrCreateVariable(model, field.name)
1398+
}
1399+
1400+
13541401
val method = currentExecutable!!
13551402
val needsReturnValue = method.returnType != voidClassId
13561403
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)