Skip to content

Commit c78fa93

Browse files
authored
Improved get-or-create-variable logic #2104, #2107, #2196 (№2199) (#2199)
Introduce UtModelWrapper to support shared global variables
1 parent b34129b commit c78fa93

File tree

11 files changed

+188
-163
lines changed

11 files changed

+188
-163
lines changed

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/Domain.kt

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import org.utbot.framework.plugin.api.CodeGenerationSettingItem
1212
import org.utbot.framework.plugin.api.MethodId
1313
import org.utbot.framework.plugin.api.TypeParameters
1414
import org.utbot.framework.plugin.api.UtModel
15-
import org.utbot.framework.plugin.api.idOrNull
1615
import org.utbot.framework.plugin.api.isolateCommandLineArgumentsToArgumentFile
1716
import org.utbot.framework.plugin.api.util.booleanArrayClassId
1817
import org.utbot.framework.plugin.api.util.booleanClassId
@@ -730,18 +729,13 @@ object SpringBoot : DependencyInjectionFramework(
730729
)
731730

732731
/**
733-
* Extended id of [UtModel], unique for whole test set.
732+
* Extended [UtModel] model with testSet id and execution id.
734733
*
735-
* Allows distinguishing models from different executions and test sets,
736-
* even if they have the same value of `UtModel.id` that is allowed.
734+
* Used as a key in [valueByUtModelWrapper].
735+
* Was introduced primarily for shared among all test methods global variables.
737736
*/
738-
data class ModelId private constructor(
739-
private val id: Int?,
740-
private val executionId: Int,
741-
private val testSetId: Int,
742-
) {
743-
companion object {
744-
fun create(model: UtModel, executionId: Int = -1, testSetId: Int = -1) = ModelId(model.idOrNull(), executionId, testSetId)
745-
}
746-
}
747-
737+
data class UtModelWrapper(
738+
val testSetId: Int,
739+
val executionId: Int,
740+
val model: UtModel
741+
)

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/context/CgContext.kt

Lines changed: 50 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,13 @@ import org.utbot.framework.codegen.domain.models.CgTestMethod
1515
import org.utbot.framework.codegen.domain.models.CgThisInstance
1616
import org.utbot.framework.codegen.domain.models.CgValue
1717
import org.utbot.framework.codegen.domain.models.CgVariable
18-
import java.util.IdentityHashMap
1918
import kotlinx.collections.immutable.PersistentList
2019
import kotlinx.collections.immutable.PersistentMap
2120
import kotlinx.collections.immutable.PersistentSet
2221
import kotlinx.collections.immutable.persistentListOf
2322
import kotlinx.collections.immutable.persistentMapOf
2423
import kotlinx.collections.immutable.persistentSetOf
25-
import org.utbot.framework.codegen.domain.ModelId
24+
import org.utbot.framework.codegen.domain.UtModelWrapper
2625
import org.utbot.framework.codegen.domain.ProjectType
2726
import org.utbot.framework.codegen.domain.models.CgMethodTestSet
2827
import org.utbot.framework.codegen.domain.builtin.TestClassUtilMethodProvider
@@ -195,10 +194,30 @@ interface CgContextOwner {
195194

196195
val shouldOptimizeImports: Boolean
197196

198-
var valueByModel: IdentityHashMap<UtModel, CgValue>
197+
/**
198+
* Used for differentiating models in codegen
199+
* because comparisons by [UtModel] are not enough,
200+
* especially for Spring related variable processing.
201+
*
202+
* Default value is -1, meaning that we are not processing any test set.
203+
*
204+
* @see [CgContext.withTestSetIdScope]
205+
*/
206+
var currentTestSetId: Int
207+
208+
/**
209+
* Used for differentiating models in codegen
210+
* because comparisons by [UtModel] are not enough,
211+
* especially for Spring related variable processing.
212+
*
213+
* Default value is -1, meaning that we are not processing any execution.
214+
*
215+
* @see [CgContext.withExecutionIdScope]
216+
*/
217+
var currentExecutionId: Int
199218

200-
// use it to compare stateBefore and result variables - in case of equality do not create new variable
201-
var valueByModelId: MutableMap<ModelId, CgValue>
219+
// used for comparing stateBefore and result variables -- in case of equality do not create new variable
220+
var valueByUtModelWrapper: MutableMap<UtModelWrapper, CgValue>
202221

203222
// parameters of the method currently being generated
204223
val currentMethodParameters: MutableMap<CgParameterKind, CgVariable>
@@ -227,12 +246,6 @@ interface CgContextOwner {
227246
*/
228247
var successfulExecutionsModels: List<UtModel>
229248

230-
/**
231-
* Gives a unique identifier to model in test set.
232-
* Determines which execution current model belongs to.
233-
*/
234-
var modelIds: MutableMap<UtModel, ModelId>
235-
236249
fun block(init: () -> Unit): Block {
237250
val prevBlock = currentBlock
238251
return try {
@@ -260,6 +273,16 @@ interface CgContextOwner {
260273
currentExecutable = executableId
261274
}
262275

276+
fun <R> withTestSetIdScope(testSetId: Int, block: () -> R): R {
277+
currentTestSetId = testSetId
278+
return block()
279+
}
280+
281+
fun <R> withExecutionIdScope(executionId: Int, block: () -> R): R {
282+
currentExecutionId = executionId
283+
return block()
284+
}
285+
263286
fun addExceptionIfNeeded(exception: ClassId) {
264287
if (exception !is BuiltinClassId) {
265288
require(exception isSubtypeOf Throwable::class.id) {
@@ -316,11 +339,10 @@ interface CgContextOwner {
316339

317340
fun updateVariableScope(variable: CgVariable, model: UtModel? = null) {
318341
model?.let {
319-
valueByModel[it] = variable
342+
valueByUtModelWrapper[it.wrap()] = variable
320343
(model as UtReferenceModel).let { refModel ->
321344
refModel.id.let {
322-
val modelId = getIdByModel(model)
323-
valueByModelId[modelId] = variable
345+
valueByUtModelWrapper[model.wrap()] = variable
324346
}
325347
}
326348
}
@@ -331,17 +353,15 @@ interface CgContextOwner {
331353
val prevDeclaredClassRefs = declaredClassRefs
332354
val prevDeclaredExecutableRefs = declaredExecutableRefs
333355
val prevDeclaredFieldRefs = declaredFieldRefs
334-
val prevValueByModel = IdentityHashMap(valueByModel)
335-
val prevValueByModelId = valueByModelId.toMutableMap()
356+
val prevValueByCgModel = valueByUtModelWrapper.toMutableMap()
336357
return try {
337358
block()
338359
} finally {
339360
existingVariableNames = prevVariableNames
340361
declaredClassRefs = prevDeclaredClassRefs
341362
declaredExecutableRefs = prevDeclaredExecutableRefs
342363
declaredFieldRefs = prevDeclaredFieldRefs
343-
valueByModel = prevValueByModel
344-
valueByModelId = prevValueByModelId
364+
valueByUtModelWrapper = prevValueByCgModel
345365
}
346366
}
347367

@@ -439,7 +459,7 @@ interface CgContextOwner {
439459
val getLambdaMethod: MethodId
440460
get() = utilMethodProvider.getLambdaMethodMethodId
441461

442-
fun getIdByModel(model: UtModel): ModelId
462+
fun UtModel.wrap(): UtModelWrapper
443463
}
444464

445465
/**
@@ -491,8 +511,6 @@ class CgContext(
491511
override lateinit var actual: CgVariable
492512
override lateinit var successfulExecutionsModels: List<UtModel>
493513

494-
override var modelIds: MutableMap<UtModel, ModelId> = mutableMapOf()
495-
496514
/**
497515
* This property cannot be accessed outside of test class file scope
498516
* (i.e. outside of [CgContextOwner.withTestClassFileScope]).
@@ -570,7 +588,12 @@ class CgContext(
570588
}
571589
}
572590

573-
override fun getIdByModel(model: UtModel): ModelId = modelIds.getOrPut(model) { ModelId.create(model) }
591+
override fun UtModel.wrap(): UtModelWrapper =
592+
UtModelWrapper(
593+
testSetId = currentTestSetId,
594+
executionId = currentExecutionId,
595+
model = this
596+
)
574597

575598
private fun createClassIdForNestedClass(testClassModel: SimpleTestClassModel): ClassId {
576599
val simpleName = "${testClassModel.classUnderTest.simpleName}Test"
@@ -588,15 +611,15 @@ class CgContext(
588611
importedClasses.clear()
589612
testMethods.clear()
590613
requiredUtilMethods.clear()
591-
valueByModel.clear()
592-
valueByModelId.clear()
593-
modelIds.clear()
614+
valueByUtModelWrapper.clear()
594615
mockFrameworkUsed = false
595616
}
596617

597-
override var valueByModel: IdentityHashMap<UtModel, CgValue> = IdentityHashMap()
618+
override var currentTestSetId: Int = -1
619+
620+
override var currentExecutionId: Int = -1
598621

599-
override var valueByModelId: MutableMap<ModelId, CgValue> = mutableMapOf()
622+
override var valueByUtModelWrapper: MutableMap<UtModelWrapper, CgValue> = mutableMapOf()
600623

601624
override val currentMethodParameters: MutableMap<CgParameterKind, CgVariable> = mutableMapOf()
602625

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/TestClassModel.kt

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
package org.utbot.framework.codegen.domain.models
22

3+
import org.utbot.framework.codegen.domain.UtModelWrapper
34
import org.utbot.framework.plugin.api.ClassId
4-
import org.utbot.framework.plugin.api.UtModel
5-
6-
typealias ClassModels = Map<ClassId, Set<UtModel>>
75

86
/**
97
* Stores method test sets in a structure that replicates structure of their methods in [classUnderTest].
@@ -32,7 +30,7 @@ class SpringTestClassModel(
3230
classUnderTest: ClassId,
3331
methodTestSets: List<CgMethodTestSet>,
3432
nestedClasses: List<SimpleTestClassModel>,
35-
val injectedMockModels: ClassModels = mapOf(),
36-
val mockedModels: ClassModels = mapOf(),
33+
val injectedMockModels: Map<ClassId, Set<UtModelWrapper>> = mapOf(),
34+
val mockedModels: Map<ClassId, Set<UtModelWrapper>> = mapOf(),
3735
): TestClassModel(classUnderTest, methodTestSets, nestedClasses)
3836

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/builders/SpringTestClassModelBuilder.kt

Lines changed: 35 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
package org.utbot.framework.codegen.domain.models.builders
22

3-
import org.utbot.framework.codegen.domain.ModelId
3+
import org.utbot.framework.codegen.domain.UtModelWrapper
44
import org.utbot.framework.codegen.domain.context.CgContext
55
import org.utbot.framework.codegen.domain.models.CgMethodTestSet
6-
import org.utbot.framework.codegen.domain.models.ClassModels
76
import org.utbot.framework.codegen.domain.models.SpringTestClassModel
87
import org.utbot.framework.plugin.api.ClassId
98
import org.utbot.framework.plugin.api.UtArrayModel
@@ -27,61 +26,64 @@ class SpringTestClassModelBuilder(val context: CgContext): TestClassModelBuilder
2726
val (injectedModels, mockedModels) = collectInjectedAndMockedModels(testSets)
2827

2928
return SpringTestClassModel(
30-
baseModel.classUnderTest,
31-
baseModel.methodTestSets,
32-
baseModel.nestedClasses,
33-
injectedModels,
34-
mockedModels,
29+
classUnderTest = baseModel.classUnderTest,
30+
methodTestSets = baseModel.methodTestSets,
31+
nestedClasses = baseModel.nestedClasses,
32+
injectedMockModels = injectedModels,
33+
mockedModels = mockedModels
3534
)
3635
}
3736

38-
private fun collectInjectedAndMockedModels(testSets: List<CgMethodTestSet>): Pair<ClassModels, ClassModels> {
39-
val thisInstances = mutableSetOf<UtModel>()
40-
val thisInstancesDependentModels = mutableSetOf<UtModel>()
41-
42-
for ((testSetIndex, testSet) in testSets.withIndex()) {
43-
for ((executionIndex, execution) in testSet.executions.withIndex()) {
44-
45-
setOf(execution.stateBefore.thisInstance, execution.stateAfter.thisInstance)
46-
.filterNotNull()
47-
.forEach { model ->
48-
thisInstances += model
49-
thisInstancesDependentModels += collectByThisInstanceModel(model, executionIndex, testSetIndex)
37+
private fun collectInjectedAndMockedModels(testSets: List<CgMethodTestSet>): Pair<Map<ClassId, Set<UtModelWrapper>>, Map<ClassId, Set<UtModelWrapper>>> {
38+
val thisInstances = mutableSetOf<UtModelWrapper>()
39+
val thisInstancesDependentModels = mutableSetOf<UtModelWrapper>()
40+
41+
with(context) {
42+
for ((testSetIndex, testSet) in testSets.withIndex()) {
43+
withTestSetIdScope(testSetIndex) {
44+
for ((executionIndex, execution) in testSet.executions.withIndex()) {
45+
withExecutionIdScope(executionIndex) {
46+
setOf(execution.stateBefore.thisInstance, execution.stateAfter.thisInstance)
47+
.filterNotNull()
48+
.forEach { model ->
49+
thisInstances += model.wrap()
50+
thisInstancesDependentModels += collectByThisInstanceModel(model)
51+
}
52+
}
5053
}
54+
}
5155
}
5256
}
5357

5458
val dependentMockModels =
55-
thisInstancesDependentModels.filterTo(mutableSetOf()) { it.isMockModel() && it !in thisInstances }
59+
thisInstancesDependentModels
60+
.filterTo(mutableSetOf()) { cgModel ->
61+
cgModel.model.isMockModel() && cgModel !in thisInstances
62+
}
5663

5764
return thisInstances.groupByClassId() to dependentMockModels.groupByClassId()
5865
}
5966

60-
private fun collectByThisInstanceModel(model: UtModel, executionIndex: Int, testSetIndex: Int): Set<UtModel> {
61-
context.modelIds[model] = ModelId.create(model, executionIndex, testSetIndex)
62-
63-
val dependentModels = mutableSetOf<UtModel>()
67+
private fun collectByThisInstanceModel(model: UtModel): Set<UtModelWrapper> {
68+
val dependentModels = mutableSetOf<UtModelWrapper>()
6469
collectRecursively(model, dependentModels)
6570

66-
dependentModels.forEach { model ->
67-
context.modelIds[model] = ModelId.create(model, executionIndex, testSetIndex)
68-
}
69-
7071
return dependentModels
7172
}
7273

73-
private fun Set<UtModel>.groupByClassId(): ClassModels {
74-
val classModels = mutableMapOf<ClassId, Set<UtModel>>()
74+
private fun Set<UtModelWrapper>.groupByClassId(): Map<ClassId, Set<UtModelWrapper>> {
75+
val classModels = mutableMapOf<ClassId, Set<UtModelWrapper>>()
7576

76-
for (modelGroup in this.groupBy { it.classId }) {
77+
for (modelGroup in this.groupBy { it.model.classId }) {
7778
classModels[modelGroup.key] = modelGroup.value.toSet()
7879
}
7980

8081
return classModels
8182
}
8283

83-
private fun collectRecursively(currentModel: UtModel, allModels: MutableSet<UtModel>) {
84-
if (!allModels.add(currentModel)) {
84+
private fun collectRecursively(currentModel: UtModel, allModels: MutableSet<UtModelWrapper>) {
85+
val cgModel = with(context) { currentModel.wrap() }
86+
if (!allModels.add(cgModel)) {
8587
return
8688
}
8789

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,9 @@ abstract class CgAbstractTestClassConstructor<T : TestClassModel>(val context: C
9898
}
9999

100100
for (i in checkedRange) {
101-
currentTestCaseTestMethods += methodConstructor.createTestMethod(methodUnderTest, testSet.executions[i])
101+
withExecutionIdScope(i) {
102+
currentTestCaseTestMethods += methodConstructor.createTestMethod(methodUnderTest, testSet.executions[i])
103+
}
102104
}
103105

104106
val comments = listOf("Actual number of generated tests (${executionIndices.last - executionIndices.first}) exceeds per-method limit (${UtSettings.maxTestsPerMethodInRegion})",

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

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,16 @@ open class CgSimpleTestClassConstructor(context: CgContext): CgAbstractTestClass
4949
}
5050
}
5151

52-
for (testSet in notYetConstructedTestSets) {
52+
for ((testSetIndex, testSet) in notYetConstructedTestSets.withIndex()) {
5353
updateCurrentExecutable(testSet.executableId)
54-
val currentMethodUnderTestRegions = constructTestSet(testSet) ?: continue
55-
val executableUnderTestCluster = CgMethodsCluster(
56-
"Test suites for executable $currentExecutable",
57-
currentMethodUnderTestRegions
58-
)
59-
methodRegions += executableUnderTestCluster
54+
withTestSetIdScope(testSetIndex) {
55+
val currentMethodUnderTestRegions = constructTestSet(testSet) ?: return@withTestSetIdScope
56+
val executableUnderTestCluster = CgMethodsCluster(
57+
"Test suites for executable $currentExecutable",
58+
currentMethodUnderTestRegions
59+
)
60+
methodRegions += executableUnderTestCluster
61+
}
6062
}
6163

6264
val currentTestClassDataProviderMethods = currentTestClassContext.cgDataProviderMethods
@@ -159,8 +161,11 @@ open class CgSimpleTestClassConstructor(context: CgContext): CgAbstractTestClass
159161

160162
testSet.executions
161163
.filterIsInstance<UtFuzzedExecution>()
162-
.forEach { execution ->
163-
testMethods += methodConstructor.createTestMethod(testSet.executableId, execution)
164+
.withIndex()
165+
.forEach { (index, execution) ->
166+
withExecutionIdScope(index) {
167+
testMethods += methodConstructor.createTestMethod(testSet.executableId, execution)
168+
}
164169
}
165170

166171
return testMethods
@@ -176,8 +181,11 @@ open class CgSimpleTestClassConstructor(context: CgContext): CgAbstractTestClass
176181
testSet.executions
177182
.filterIsInstance<UtSymbolicExecution>()
178183
.filter { it.containsMocking }
179-
.forEach { execution ->
180-
testMethods += methodConstructor.createTestMethod(testSet.executableId, execution)
184+
.withIndex()
185+
.forEach { (index, execution) ->
186+
withExecutionIdScope(index) {
187+
testMethods += methodConstructor.createTestMethod(testSet.executableId, execution)
188+
}
181189
}
182190

183191
return testMethods

0 commit comments

Comments
 (0)