Skip to content

Commit 6b9a15e

Browse files
committed
Add statics support in parametrized tests
1 parent 1321946 commit 6b9a15e

File tree

5 files changed

+97
-16
lines changed

5 files changed

+97
-16
lines changed

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ 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
@@ -44,6 +45,25 @@ data class CgMethodTestSet private constructor(
4445
return executionsByResult.map{ (_, executions) -> substituteExecutions(executions) }
4546
}
4647

48+
/**
49+
* Splits [CgMethodTestSet] test sets by affected static fields statics.
50+
*
51+
* A separate test set is created for each combination of modified statics.
52+
*/
53+
fun splitExecutionsByChangedStatics(): List<CgMethodTestSet> {
54+
val successfulExecutions = executions.filter { it.result is UtExecutionSuccess }
55+
val executionsByStaticsUsage: Map<Set<FieldId>, List<UtExecution>> =
56+
if (successfulExecutions.isNotEmpty()) {
57+
successfulExecutions.groupBy {
58+
it.stateBefore.statics.keys
59+
}
60+
} else {
61+
mapOf(executions.first().stateBefore.statics.keys to executions)
62+
}
63+
64+
return executionsByStaticsUsage.map { (_, executions) -> substituteExecutions(executions) }
65+
}
66+
4767
/**
4868
* Finds a [ClassId] of all result models in executions.
4969
*

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)