Skip to content

Commit 051b293

Browse files
authored
Support Spring autowire two beans of one type and one UtModel (#2438)
* Support autowiring more than one different bean of one type, when Engine output one model for variables of one type. * Add tests for two cases: 1) the Engine reproduces two models on two @Autowired variables of the same type, 2) the Engine reproduces one model on two @Autowired variables of the same type * Some fixes * Small refactoring * Fix test with two models of the same type
1 parent 16e81ee commit 051b293

File tree

14 files changed

+267
-90
lines changed

14 files changed

+267
-90
lines changed

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -757,9 +757,12 @@ object SpringBoot : DependencyInjectionFramework(
757757
*
758758
* Used as a key in [valueByUtModelWrapper].
759759
* Was introduced primarily for shared among all test methods global variables.
760+
*
761+
* `modelTagName` is used to distinguish between variables with annotation @Mock that have the same model
760762
*/
761763
data class UtModelWrapper(
762764
val testSetId: Int,
763765
val executionId: Int,
764-
val model: UtModel
766+
val model: UtModel,
767+
val modelTagName: String?
765768
)

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,7 @@ interface CgContextOwner {
451451
val getLambdaMethod: MethodId
452452
get() = utilMethodProvider.getLambdaMethodMethodId
453453

454-
fun UtModel.wrap(): UtModelWrapper
454+
fun UtModel.wrap(modelTagName: String? = null): UtModelWrapper
455455
}
456456

457457
/**
@@ -580,11 +580,12 @@ class CgContext(
580580
}
581581
}
582582

583-
override fun UtModel.wrap(): UtModelWrapper =
583+
override fun UtModel.wrap(modelTagName: String?): UtModelWrapper =
584584
UtModelWrapper(
585585
testSetId = currentTestSetId,
586586
executionId = currentExecutionId,
587-
model = this
587+
model = this,
588+
modelTagName = modelTagName
588589
)
589590

590591
private fun createClassIdForNestedClass(testClassModel: SimpleTestClassModel): ClassId {

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

Lines changed: 43 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,10 @@ class SpringTestClassModelBuilder(val context: CgContext): TestClassModelBuilder
8383

8484
private fun collectByModel(model: UtModel): Set<UtModelWrapper> {
8585
val dependentModels = mutableSetOf<UtModelWrapper>()
86-
collectRecursively(model, dependentModels)
86+
87+
with(context){
88+
collectRecursively(model.wrap(), dependentModels)
89+
}
8790

8891
return dependentModels
8992
}
@@ -98,49 +101,54 @@ class SpringTestClassModelBuilder(val context: CgContext): TestClassModelBuilder
98101
return classModels
99102
}
100103

101-
private fun collectRecursively(currentModel: UtModel, allModels: MutableSet<UtModelWrapper>) {
102-
val cgModel = with(context) { currentModel.wrap() }
103-
if (!allModels.add(cgModel)) {
104+
private fun collectRecursively(currentModelWrapper: UtModelWrapper, allModels: MutableSet<UtModelWrapper>) {
105+
if (!allModels.add(currentModelWrapper)) {
104106
return
105107
}
106108

107-
when (currentModel) {
108-
is UtNullModel,
109-
is UtPrimitiveModel,
110-
is UtClassRefModel,
111-
is UtVoidModel,
112-
is UtEnumConstantModel,
113-
is UtSpringContextModel -> {}
114-
is UtLambdaModel -> {
115-
currentModel.capturedValues.forEach { collectRecursively(it, allModels) }
116-
}
117-
is UtArrayModel -> {
118-
currentModel.stores.values.forEach { collectRecursively(it, allModels) }
119-
if (currentModel.stores.count() < currentModel.length) {
120-
collectRecursively(currentModel.constModel, allModels)
109+
with(context) {
110+
when (val currentModel = currentModelWrapper.model) {
111+
is UtNullModel,
112+
is UtPrimitiveModel,
113+
is UtClassRefModel,
114+
is UtVoidModel,
115+
is UtEnumConstantModel,
116+
is UtSpringContextModel -> {}
117+
is UtLambdaModel -> {
118+
currentModel.capturedValues.forEach { collectRecursively(it.wrap(), allModels) }
121119
}
122-
}
123-
is UtCompositeModel -> {
124-
// Here we traverse fields only.
125-
// Traversing mocks as well will result in wrong models playing
126-
// a role of class fields with @Mock annotation.
127-
currentModel.fields.values.forEach { collectRecursively(it, allModels) }
128-
}
129-
is UtAssembleModel -> {
130-
currentModel.origin?.let { collectRecursively(it, allModels) }
120+
is UtArrayModel -> {
121+
currentModel.stores.values.forEach { collectRecursively(it.wrap(), allModels) }
122+
if (currentModel.stores.count() < currentModel.length) {
123+
collectRecursively(currentModel.constModel.wrap(), allModels)
124+
}
125+
}
126+
is UtCompositeModel -> {
127+
// Here we traverse fields only.
128+
// Traversing mocks as well will result in wrong models playing
129+
// a role of class fields with @Mock annotation.
130+
currentModel.fields.forEach { (fieldId, model) ->
131+
// We use `modelTagName` in order to distinguish mock models
132+
val modeTagName = if(model.isMockModel()) fieldId.name else null
133+
collectRecursively(model.wrap(modeTagName), allModels)
134+
}
135+
}
136+
is UtAssembleModel -> {
137+
currentModel.origin?.let { collectRecursively(it.wrap(), allModels) }
131138

132-
currentModel.instantiationCall.instance?.let { collectRecursively(it, allModels) }
133-
currentModel.instantiationCall.params.forEach { collectRecursively(it, allModels) }
139+
currentModel.instantiationCall.instance?.let { collectRecursively(it.wrap(), allModels) }
140+
currentModel.instantiationCall.params.forEach { collectRecursively(it.wrap(), allModels) }
134141

135-
currentModel.modificationsChain.forEach { stmt ->
136-
stmt.instance?.let { collectRecursively(it, allModels) }
137-
when (stmt) {
138-
is UtStatementCallModel -> stmt.params.forEach { collectRecursively(it, allModels) }
139-
is UtDirectSetFieldModel -> collectRecursively(stmt.fieldModel, allModels)
142+
currentModel.modificationsChain.forEach { stmt ->
143+
stmt.instance?.let { collectRecursively(it.wrap(), allModels) }
144+
when (stmt) {
145+
is UtStatementCallModel -> stmt.params.forEach { collectRecursively(it.wrap(), allModels) }
146+
is UtDirectSetFieldModel -> collectRecursively(stmt.fieldModel.wrap(), allModels)
147+
}
140148
}
141149
}
150+
//Python, JavaScript, Go models are not required in Spring
142151
}
143-
//Python, JavaScript, Go models are not required in Spring
144152
}
145153
}
146154
}

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.utbot.framework.codegen.tree
22

3+
import org.utbot.framework.codegen.domain.UtModelWrapper
34
import org.utbot.framework.codegen.domain.builtin.TestClassUtilMethodProvider
45
import org.utbot.framework.codegen.domain.context.CgContext
56
import org.utbot.framework.codegen.domain.models.AnnotationTarget.*
@@ -21,7 +22,6 @@ import org.utbot.framework.plugin.api.UtSpringContextModel
2122
import org.utbot.framework.plugin.api.util.SpringModelUtils.getBeanNameOrNull
2223
import org.utbot.framework.plugin.api.util.id
2324
import java.lang.Exception
24-
import java.util.Collections.max
2525

2626
abstract class CgAbstractSpringTestClassConstructor(context: CgContext) :
2727
CgAbstractTestClassConstructor<SpringTestClassModel>(context) {
@@ -125,10 +125,18 @@ abstract class CgAbstractSpringTestClassConstructor(context: CgContext) :
125125

126126
modelWrappers
127127
.forEach { modelWrapper ->
128-
modelWrapper.let {
129-
valueByUtModelWrapper[modelWrapper] = createdVariable
130-
variableConstructor.annotatedModelGroups.getOrPut(annotationClassId) { mutableSetOf() } += modelWrapper
131-
}
128+
129+
val modelWrapperWithNullTagName = UtModelWrapper(
130+
testSetId = modelWrapper.testSetId,
131+
executionId = modelWrapper.executionId,
132+
model = modelWrapper.model,
133+
modelTagName = null,
134+
)
135+
136+
valueByUtModelWrapper[modelWrapperWithNullTagName] = createdVariable
137+
138+
variableConstructor.annotatedModelGroups
139+
.getOrPut(annotationClassId) { mutableSetOf() } += modelWrapperWithNullTagName
132140
}
133141
}
134142

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import org.utbot.framework.plugin.api.UtModel
1414
import org.utbot.framework.plugin.api.isMockModel
1515
import org.utbot.framework.plugin.api.util.SpringModelUtils.autowiredClassId
1616
import org.utbot.framework.plugin.api.util.SpringModelUtils.isAutowiredFromContext
17-
import java.util.*
17+
import java.util.Collections.max
1818
import kotlin.collections.HashMap
1919

2020
sealed interface CgClassFieldManager : CgContextOwner {
@@ -88,15 +88,16 @@ class CgMockedFieldsManager(context: CgContext) : CgClassFieldManagerImpl(contex
8888
override fun fieldWithAnnotationIsRequired(modelWrappers: Set<UtModelWrapper>): Boolean {
8989
// group [listOfUtModels] by `testSetId` and `executionId`
9090
// to check how many instances of one type are used in each execution
91-
val modelsByExecutions = modelWrappers
91+
val modelWrappersByExecutions = modelWrappers
9292
.groupByTo(HashMap()) { Pair(it.testSetId, it.executionId) }
9393

94-
// maximal instances of one type amount in one execution
95-
val instanceMaxCount = Collections.max(modelsByExecutions.map { (_, modelsList) -> modelsList.size })
94+
// maximal count of instances of the same type amount in one execution
95+
// we use `modelTagName` in order to distinguish mock models by their name
96+
val maxCountOfInstancesOfTheSameTypeByExecution = max(modelWrappersByExecutions.map { (_, modelsList) -> modelsList.size })
9697

97-
// if [instanceCount] is 1, then we mock variable by @Mock annotation
98+
// if [maxCountOfInstancesOfTheSameTypeByExecution] is 1, then we mock variable by @Mock annotation
9899
// Otherwise we will mock variable by simple mock later
99-
return instanceMaxCount == 1
100+
return maxCountOfInstancesOfTheSameTypeByExecution == 1
100101
}
101102

102103
}

utbot-spring-sample/src/main/java/org/utbot/examples/spring/autowiring/twoAndMoreBeansForOneType/Person.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ public Person(String firstName, String secondName, Integer age) {
1212
this.age = age;
1313
}
1414

15-
public Integer getAge(){
16-
return age;
15+
public String getName() {
16+
return firstName + " " + lastName;
1717
}
1818

19-
public String name() {
20-
return firstName + " " + lastName;
19+
public Integer getAge(){
20+
return age;
2121
}
2222
}

utbot-spring-sample/src/main/java/org/utbot/examples/spring/autowiring/twoAndMoreBeansForOneType/PersonConfig.java

Lines changed: 0 additions & 17 deletions
This file was deleted.

utbot-spring-sample/src/main/java/org/utbot/examples/spring/autowiring/twoAndMoreBeansForOneType/PersonService.java

Lines changed: 0 additions & 17 deletions
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package org.utbot.examples.spring.autowiring.twoAndMoreBeansForOneType;
2+
3+
import org.springframework.beans.factory.annotation.Autowired;
4+
import org.springframework.stereotype.Service;
5+
6+
import java.util.ArrayList;
7+
import java.util.List;
8+
9+
@Service
10+
public class ServiceOfBeansWithSameType {
11+
@Autowired
12+
private Person personOne;
13+
14+
@Autowired
15+
private Person personTwo;
16+
17+
public final List<String> baseOrders = new ArrayList<>();
18+
19+
// a method for testing the case when the Engine produces one model on @Autowired variables of the same type
20+
public Integer ageSum(){
21+
return personOne.getAge() + personTwo.getAge();
22+
}
23+
24+
// a method for testing the case when the Engine produces two models on @Autowired variables of the same type
25+
public Boolean checker() {
26+
return personOne.getName().equals("k") && personTwo.getName().length() > 5 && baseOrders.isEmpty();
27+
}
28+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package org.utbot.examples.spring.autowiring.oneBeanForOneType
1+
package org.utbot.examples.spring.autowiring
22

33
import org.utbot.examples.spring.utils.standardSpringTestingConfigurations
44
import org.utbot.testing.UtValueTestCaseChecker

0 commit comments

Comments
 (0)