Skip to content

Commit dd8d491

Browse files
committed
Collect mocked class variables in Spring tests
1 parent 5e964a3 commit dd8d491

4 files changed

Lines changed: 168 additions & 49 deletions

File tree

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/CodeGenerator.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import org.utbot.framework.codegen.domain.RuntimeExceptionTestsBehaviour
88
import org.utbot.framework.codegen.domain.StaticsMocking
99
import org.utbot.framework.codegen.domain.TestFramework
1010
import org.utbot.framework.codegen.domain.models.CgMethodTestSet
11-
import org.utbot.framework.codegen.domain.models.TestClassModel
1211
import org.utbot.framework.codegen.domain.context.CgContext
12+
import org.utbot.framework.codegen.domain.models.TestClassModelBuilder
1313
import org.utbot.framework.codegen.renderer.CgAbstractRenderer
1414
import org.utbot.framework.codegen.reports.TestsGenerationReport
1515
import org.utbot.framework.codegen.tree.CgSimpleTestClassConstructor
@@ -75,7 +75,9 @@ open class CodeGenerator(
7575
context.withTestClassFileScope {
7676
val astConstructor = CgSimpleTestClassConstructor(context)
7777
val renderer = CgAbstractRenderer.makeRenderer(context)
78-
val testClassModel = TestClassModel.fromTestSets(classUnderTest, cgTestSets)
78+
val testClassModelBuilder = TestClassModelBuilder(context.isSpringClass)
79+
80+
val testClassModel = testClassModelBuilder.createClassModel(classUnderTest, cgTestSets)
7981

8082
fun now() = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss.SSS"))
8183

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ interface CgContextOwner {
6666
// current class under test
6767
val classUnderTest: ClassId
6868

69+
// If test class is configured with Spring, we should do some extra analysis
70+
val isSpringClass: Boolean
71+
6972
// test class currently being generated (if series of nested classes is generated, it is the outermost one)
7073
val outerMostTestClass: ClassId
7174

@@ -431,6 +434,7 @@ interface CgContextOwner {
431434
*/
432435
data class CgContext(
433436
override val classUnderTest: ClassId,
437+
override val isSpringClass: Boolean = true,
434438
val generateUtilClassFile: Boolean = false,
435439
override var currentExecutable: ExecutableId? = null,
436440
override val collectedExceptions: MutableSet<ClassId> = mutableSetOf(),
Lines changed: 9 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,20 @@
11
package org.utbot.framework.codegen.domain.models
22

33
import org.utbot.framework.plugin.api.ClassId
4-
import org.utbot.framework.plugin.api.util.enclosingClass
54

65
// TODO: seems like this class needs to be renamed
76
/**
8-
* Stores method testsets in a structure that replicates structure of their methods in [classUnderTest].
7+
* Stores method test sets in a structure that replicates structure of their methods in [classUnderTest].
98
* I.e., if some method is declared in nested class of [classUnderTest], its testset will be put
109
* in [TestClassModel] in one of [nestedClasses]
10+
*
11+
* @param injectingMocksClass a class to inject other mocks into
12+
* @param mockedClasses variables of test class to represent mocked instances
1113
*/
12-
data class TestClassModel(
14+
class TestClassModel(
1315
val classUnderTest: ClassId,
1416
val methodTestSets: List<CgMethodTestSet>,
15-
val nestedClasses: List<TestClassModel> = listOf()
16-
) {
17-
companion object {
18-
fun fromTestSets(classUnderTest: ClassId, testSets: List<CgMethodTestSet>): TestClassModel {
19-
// For each class stores list of methods declared in this class (methods from nested classes are excluded)
20-
val class2methodTestSets = testSets.groupBy { it.executableId.classId }
21-
22-
val classesWithMethodsUnderTest = testSets
23-
.map { it.executableId.classId }
24-
.distinct()
25-
26-
// For each class stores list of its "direct" nested classes
27-
val class2nestedClasses = mutableMapOf<ClassId, MutableSet<ClassId>>()
28-
29-
for (classId in classesWithMethodsUnderTest) {
30-
var currentClass = classId
31-
var enclosingClass = currentClass.enclosingClass
32-
// while we haven't reached the top of nested class hierarchy or the main class under test
33-
while (enclosingClass != null && currentClass != classUnderTest) {
34-
class2nestedClasses.getOrPut(enclosingClass) { mutableSetOf() } += currentClass
35-
currentClass = enclosingClass
36-
enclosingClass = enclosingClass.enclosingClass
37-
}
38-
}
39-
return constructRecursively(classUnderTest, class2methodTestSets, class2nestedClasses)
40-
}
41-
42-
private fun constructRecursively(
43-
clazz: ClassId,
44-
class2methodTestSets: Map<ClassId, List<CgMethodTestSet>>,
45-
class2nestedClasses: Map<ClassId, Set<ClassId>>
46-
): TestClassModel {
47-
val currentNestedClasses = class2nestedClasses.getOrDefault(clazz, listOf())
48-
val currentMethodTestSets = class2methodTestSets.getOrDefault(clazz, listOf())
49-
return TestClassModel(
50-
clazz,
51-
currentMethodTestSets,
52-
currentNestedClasses.map {
53-
constructRecursively(it, class2methodTestSets, class2nestedClasses)
54-
}
55-
)
56-
}
57-
}
58-
}
17+
val nestedClasses: List<TestClassModel> = listOf(),
18+
val injectingMocksClass: ClassId? = null,
19+
val mockedClasses: Set<ClassId> = setOf(),
20+
)
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package org.utbot.framework.codegen.domain.models
2+
3+
import org.utbot.framework.plugin.api.ClassId
4+
import org.utbot.framework.plugin.api.UtArrayModel
5+
import org.utbot.framework.plugin.api.UtAssembleModel
6+
import org.utbot.framework.plugin.api.UtClassRefModel
7+
import org.utbot.framework.plugin.api.UtCompositeModel
8+
import org.utbot.framework.plugin.api.UtDirectSetFieldModel
9+
import org.utbot.framework.plugin.api.UtEnumConstantModel
10+
import org.utbot.framework.plugin.api.UtExecutableCallModel
11+
import org.utbot.framework.plugin.api.UtLambdaModel
12+
import org.utbot.framework.plugin.api.UtModel
13+
import org.utbot.framework.plugin.api.UtNullModel
14+
import org.utbot.framework.plugin.api.UtPrimitiveModel
15+
import org.utbot.framework.plugin.api.UtVoidModel
16+
import org.utbot.framework.plugin.api.util.enclosingClass
17+
18+
class TestClassModelBuilder(
19+
private val isSpringClass: Boolean
20+
) {
21+
fun createClassModel(classUnderTest: ClassId, testSets: List<CgMethodTestSet>): TestClassModel {
22+
// For each class stores list of methods declared in this class (methods from nested classes are excluded)
23+
val class2methodTestSets = testSets.groupBy { it.executableId.classId }
24+
25+
val classesWithMethodsUnderTest = testSets
26+
.map { it.executableId.classId }
27+
.distinct()
28+
29+
// For each class stores list of its "direct" nested classes
30+
val class2nestedClasses = mutableMapOf<ClassId, MutableSet<ClassId>>()
31+
32+
for (classId in classesWithMethodsUnderTest) {
33+
var currentClass = classId
34+
var enclosingClass = currentClass.enclosingClass
35+
// while we haven't reached the top of nested class hierarchy or the main class under test
36+
while (enclosingClass != null && currentClass != classUnderTest) {
37+
class2nestedClasses.getOrPut(enclosingClass) { mutableSetOf() } += currentClass
38+
currentClass = enclosingClass
39+
enclosingClass = enclosingClass.enclosingClass
40+
}
41+
}
42+
43+
val baseModel = constructRecursively(classUnderTest, class2methodTestSets, class2nestedClasses)
44+
45+
return if (!isSpringClass) {
46+
baseModel
47+
} else {
48+
val mockedClasses = collectMockedClassIds(classUnderTest, testSets)
49+
50+
TestClassModel(
51+
baseModel.classUnderTest,
52+
baseModel.methodTestSets,
53+
baseModel.nestedClasses,
54+
classUnderTest,
55+
mockedClasses,
56+
)
57+
}
58+
}
59+
60+
private fun constructRecursively(
61+
clazz: ClassId,
62+
class2methodTestSets: Map<ClassId, List<CgMethodTestSet>>,
63+
class2nestedClasses: Map<ClassId, Set<ClassId>>
64+
): TestClassModel {
65+
val currentNestedClasses = class2nestedClasses.getOrDefault(clazz, listOf())
66+
val currentMethodTestSets = class2methodTestSets.getOrDefault(clazz, listOf())
67+
return TestClassModel(
68+
clazz,
69+
currentMethodTestSets,
70+
currentNestedClasses.map {
71+
constructRecursively(it, class2methodTestSets, class2nestedClasses)
72+
}
73+
)
74+
}
75+
76+
private fun collectMockedClassIds(
77+
classUnderTest: ClassId,
78+
testSets: List<CgMethodTestSet>,
79+
): Set<ClassId> {
80+
val allModelsInExecution = mutableListOf<UtModel>()
81+
82+
for (testSet in testSets) {
83+
for (execution in testSet.executions) {
84+
execution.stateBefore.thisInstance?.let { allModelsInExecution += it }
85+
execution.stateAfter.thisInstance?.let { allModelsInExecution += it }
86+
allModelsInExecution += execution.stateBefore.parameters
87+
allModelsInExecution += execution.stateAfter.parameters
88+
}
89+
}
90+
91+
val allConstructedModels = HashSet<UtModel>()
92+
allModelsInExecution.forEach { model -> collectRecursively(model, allConstructedModels) }
93+
94+
return allConstructedModels
95+
.filter { it.isMockComposite() || it.isMockAssemble() }
96+
.map { it.classId }
97+
.filter { it != classUnderTest }
98+
.toSet()
99+
100+
}
101+
102+
private fun collectRecursively(currentModel: UtModel, allModels: HashSet<UtModel>) {
103+
if (currentModel in allModels) {
104+
return
105+
}
106+
107+
allModels += currentModel
108+
109+
when (currentModel) {
110+
is UtNullModel,
111+
is UtPrimitiveModel,
112+
is UtClassRefModel,
113+
is UtVoidModel,
114+
is UtEnumConstantModel -> {}
115+
is UtLambdaModel -> {
116+
currentModel.capturedValues.forEach { collectRecursively(it, allModels) }
117+
}
118+
is UtArrayModel -> {
119+
collectRecursively(currentModel.constModel, allModels)
120+
currentModel.stores.values.forEach { collectRecursively(it, allModels) }
121+
}
122+
is UtCompositeModel -> {
123+
currentModel.fields.values.forEach { collectRecursively(it, allModels) }
124+
currentModel.mocks.values.flatten().forEach { collectRecursively(it, allModels) }
125+
}
126+
is UtAssembleModel -> {
127+
if (currentModel.origin != null) {
128+
collectRecursively(currentModel.origin!!, allModels)
129+
} else {
130+
currentModel.instantiationCall.instance?.let { collectRecursively(it, allModels) }
131+
currentModel.instantiationCall.params.forEach { collectRecursively(it, allModels) }
132+
133+
currentModel.modificationsChain.forEach { stmt ->
134+
stmt.instance?.let { collectRecursively(it, allModels) }
135+
when (stmt) {
136+
is UtExecutableCallModel -> stmt.params.forEach { collectRecursively(it, allModels) }
137+
is UtDirectSetFieldModel -> collectRecursively(stmt.fieldModel, allModels)
138+
}
139+
}
140+
}
141+
}
142+
//Python, JavaScript, Go models are not required in Spring
143+
else -> {}
144+
}
145+
}
146+
147+
private fun UtModel.isMockComposite(): Boolean = this is UtCompositeModel && this.isMock
148+
149+
private fun UtModel.isMockAssemble(): Boolean =
150+
this is UtAssembleModel && this.origin?.let { it.isMock } == true
151+
}

0 commit comments

Comments
 (0)