Skip to content

Commit 41e76c8

Browse files
Introduce a draft implementation of CgSpringTestClassConstructor (#1800)
1 parent f25dcc9 commit 41e76c8

File tree

7 files changed

+260
-206
lines changed

7 files changed

+260
-206
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,9 @@ class CgClassBody(
154154
val classId: ClassId,
155155
val methodRegions: List<CgMethodsCluster>,
156156
val staticDeclarationRegions: List<CgStaticsRegion>,
157-
val nestedClassRegions: List<CgNestedClassesRegion<*>>
157+
val nestedClassRegions: List<CgNestedClassesRegion<*>>,
158+
//TODO: use [CgFieldDeclaration] after PR-1788 merge
159+
val fields: List<CgDeclaration> = emptyList(),
158160
) : CgElement
159161

160162
/**

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,9 @@ class CgClassBodyBuilder(val classId: ClassId) : CgBuilder<CgClassBody> {
6666
val methodRegions: MutableList<CgMethodsCluster> = mutableListOf()
6767
val staticDeclarationRegions: MutableList<CgStaticsRegion> = mutableListOf()
6868
val nestedClassRegions: MutableList<CgNestedClassesRegion<*>> = mutableListOf()
69+
val fields: MutableList<CgDeclaration> = mutableListOf()
6970

70-
override fun build() = CgClassBody(classId, methodRegions, staticDeclarationRegions, nestedClassRegions)
71+
override fun build() = CgClassBody(classId, methodRegions, staticDeclarationRegions, nestedClassRegions, fields)
7172
}
7273

7374
fun buildClassBody(classId: ClassId, init: CgClassBodyBuilder.() -> Unit) = CgClassBodyBuilder(classId).apply(init).build()
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
package org.utbot.framework.codegen.tree
2+
3+
import org.utbot.framework.UtSettings
4+
import org.utbot.framework.codegen.domain.builtin.TestClassUtilMethodProvider
5+
import org.utbot.framework.codegen.domain.context.CgContext
6+
import org.utbot.framework.codegen.domain.context.CgContextOwner
7+
import org.utbot.framework.codegen.domain.models.CgAuxiliaryClass
8+
import org.utbot.framework.codegen.domain.models.CgClass
9+
import org.utbot.framework.codegen.domain.models.CgClassBody
10+
import org.utbot.framework.codegen.domain.models.CgClassFile
11+
import org.utbot.framework.codegen.domain.models.CgMethod
12+
import org.utbot.framework.codegen.domain.models.CgMethodTestSet
13+
import org.utbot.framework.codegen.domain.models.CgRegion
14+
import org.utbot.framework.codegen.domain.models.CgTestMethod
15+
import org.utbot.framework.codegen.domain.models.CgTestMethodCluster
16+
import org.utbot.framework.codegen.domain.models.CgTripleSlashMultilineComment
17+
import org.utbot.framework.codegen.domain.models.CgUtilEntity
18+
import org.utbot.framework.codegen.domain.models.CgUtilMethod
19+
import org.utbot.framework.codegen.domain.models.SimpleTestClassModel
20+
import org.utbot.framework.codegen.domain.models.TestClassModel
21+
import org.utbot.framework.codegen.renderer.importUtilMethodDependencies
22+
import org.utbot.framework.codegen.reports.TestsGenerationReport
23+
import org.utbot.framework.codegen.services.CgNameGenerator
24+
import org.utbot.framework.codegen.services.framework.TestFrameworkManager
25+
import org.utbot.framework.plugin.api.ClassId
26+
import org.utbot.framework.plugin.api.MethodId
27+
import org.utbot.framework.plugin.api.UtMethodTestSet
28+
import org.utbot.framework.plugin.api.util.description
29+
30+
abstract class CgAbstractTestClassConstructor<T : TestClassModel>(val context: CgContext):
31+
CgContextOwner by context,
32+
CgStatementConstructor by CgComponents.getStatementConstructorBy(context){
33+
34+
init {
35+
CgComponents.clearContextRelatedStorage()
36+
}
37+
38+
val testsGenerationReport = TestsGenerationReport()
39+
40+
protected val methodConstructor: CgMethodConstructor = CgComponents.getMethodConstructorBy(context)
41+
protected val nameGenerator: CgNameGenerator = CgComponents.getNameGeneratorBy(context)
42+
protected val testFrameworkManager: TestFrameworkManager = CgComponents.getTestFrameworkManagerBy(context)
43+
44+
/**
45+
* Constructs a file with the test class corresponding to [TestClassModel].
46+
*/
47+
open fun construct(testClassModel: T): CgClassFile {
48+
return buildClassFile {
49+
this.declaredClass = withTestClassScope { constructTestClass(testClassModel) }
50+
imports += context.collectedImports
51+
}
52+
}
53+
54+
/**
55+
* Constructs [CgClass] corresponding to [TestClassModel].
56+
*/
57+
open fun constructTestClass(testClassModel: T): CgClass {
58+
return buildClass {
59+
id = currentTestClass
60+
61+
if (currentTestClass != outerMostTestClass) {
62+
isNested = true
63+
isStatic = testFramework.nestedClassesShouldBeStatic
64+
testFrameworkManager.annotationForNestedClasses?.let {
65+
currentTestClassContext.collectedTestClassAnnotations += it
66+
}
67+
}
68+
69+
body = constructTestClassBody(testClassModel)
70+
71+
// It is important that annotations, superclass and interfaces assignment is run after
72+
// all methods are generated so that all necessary info is already present in the context
73+
with (currentTestClassContext) {
74+
annotations += collectedTestClassAnnotations
75+
superclass = testClassSuperclass
76+
interfaces += collectedTestClassInterfaces
77+
}
78+
}
79+
}
80+
81+
abstract fun constructTestClassBody(testClassModel: T): CgClassBody
82+
83+
abstract fun constructTestSet(testSet: CgMethodTestSet): List<CgRegion<CgMethod>>?
84+
85+
protected fun createTest(
86+
testSet: CgMethodTestSet,
87+
regions: MutableList<CgRegion<CgMethod>>
88+
) {
89+
val (methodUnderTest, _, clustersInfo) = testSet
90+
91+
for ((clusterSummary, executionIndices) in clustersInfo) {
92+
val currentTestCaseTestMethods = mutableListOf<CgTestMethod>()
93+
emptyLineIfNeeded()
94+
val (checkedRange, needLimitExceedingComments) = if (executionIndices.last - executionIndices.first >= UtSettings.maxTestsPerMethodInRegion) {
95+
IntRange(executionIndices.first, executionIndices.first + (UtSettings.maxTestsPerMethodInRegion - 1).coerceAtLeast(0)) to true
96+
} else {
97+
executionIndices to false
98+
}
99+
100+
for (i in checkedRange) {
101+
currentTestCaseTestMethods += methodConstructor.createTestMethod(methodUnderTest, testSet.executions[i])
102+
}
103+
104+
val comments = listOf("Actual number of generated tests (${executionIndices.last - executionIndices.first}) exceeds per-method limit (${UtSettings.maxTestsPerMethodInRegion})",
105+
"The limit can be configured in '{HOME_DIR}/.utbot/settings.properties' with 'maxTestsPerMethod' property")
106+
107+
val clusterHeader = clusterSummary?.header
108+
var clusterContent = clusterSummary?.content
109+
?.split('\n')
110+
?.let { CgTripleSlashMultilineComment(if (needLimitExceedingComments) {it.toMutableList() + comments} else {it}) }
111+
if (clusterContent == null && needLimitExceedingComments) {
112+
clusterContent = CgTripleSlashMultilineComment(comments)
113+
}
114+
regions += CgTestMethodCluster(clusterHeader, clusterContent, currentTestCaseTestMethods)
115+
116+
testsGenerationReport.addTestsByType(testSet, currentTestCaseTestMethods)
117+
}
118+
}
119+
120+
protected fun processFailure(testSet: CgMethodTestSet, failure: Throwable) {
121+
codeGenerationErrors
122+
.getOrPut(testSet) { mutableMapOf() }
123+
.merge(failure.description, 1, Int::plus)
124+
}
125+
126+
/**
127+
* This method collects a list of util entities (methods and classes) needed by the class.
128+
* By the end of the test method generation [requiredUtilMethods] may not contain all the needed.
129+
* That's because some util methods may not be directly used in tests, but they may be used from other util methods.
130+
* We define such method dependencies in [MethodId.methodDependencies].
131+
*
132+
* Once all dependencies are collected, required methods are added back to [requiredUtilMethods],
133+
* because during the work of this method they are being removed from this list, so we have to put them back in.
134+
*
135+
* Also, some util methods may use some classes that also need to be generated.
136+
* That is why we collect information about required classes using [MethodId.classDependencies].
137+
*
138+
* @return a list of [CgUtilEntity] representing required util methods and classes (including their own dependencies).
139+
*/
140+
protected fun collectUtilEntities(): List<CgUtilEntity> {
141+
val utilMethods = mutableListOf<CgUtilMethod>()
142+
// Some util methods depend on other util methods or some auxiliary classes.
143+
// Using this loop we make sure that all the util method dependencies are taken into account.
144+
val requiredClasses = mutableSetOf<ClassId>()
145+
while (requiredUtilMethods.isNotEmpty()) {
146+
val method = requiredUtilMethods.first()
147+
requiredUtilMethods.remove(method)
148+
if (method.name !in existingMethodNames) {
149+
utilMethods += CgUtilMethod(method)
150+
// we only need imports from util methods if these util methods are declared in the test class
151+
if (utilMethodProvider is TestClassUtilMethodProvider) {
152+
importUtilMethodDependencies(method)
153+
}
154+
existingMethodNames += method.name
155+
requiredUtilMethods += method.methodDependencies()
156+
requiredClasses += method.classDependencies()
157+
}
158+
}
159+
// Collect all util methods back into requiredUtilMethods.
160+
// Now there will also be util methods that weren't present in requiredUtilMethods at first,
161+
// but were needed for the present util methods to work.
162+
requiredUtilMethods += utilMethods.map { method -> method.id }
163+
164+
val auxiliaryClasses = requiredClasses.map { CgAuxiliaryClass(it) }
165+
166+
return utilMethods + auxiliaryClasses
167+
}
168+
169+
/**
170+
* Engine errors + codegen errors for a given [UtMethodTestSet]
171+
*/
172+
protected val CgMethodTestSet.allErrors: Map<String, Int>
173+
get() = errors + codeGenerationErrors.getOrDefault(this, mapOf())
174+
175+
/**
176+
* If @receiver is an util method, then returns a list of util method ids that @receiver depends on
177+
* Otherwise, an empty list is returned
178+
*/
179+
private fun MethodId.methodDependencies(): List<MethodId> = when (this) {
180+
createInstance -> listOf(getUnsafeInstance)
181+
deepEquals -> listOf(arraysDeepEquals, iterablesDeepEquals, streamsDeepEquals, mapsDeepEquals, hasCustomEquals)
182+
arraysDeepEquals, iterablesDeepEquals, streamsDeepEquals, mapsDeepEquals -> listOf(deepEquals)
183+
buildLambda, buildStaticLambda -> listOf(
184+
getLookupIn, getSingleAbstractMethod, getLambdaMethod,
185+
getLambdaCapturedArgumentTypes, getInstantiatedMethodType, getLambdaCapturedArgumentValues
186+
)
187+
else -> emptyList()
188+
}
189+
190+
/**
191+
* If @receiver is an util method, then returns a list of auxiliary class ids that @receiver depends on.
192+
* Otherwise, an empty list is returned.
193+
*/
194+
private fun MethodId.classDependencies(): List<ClassId> = when (this) {
195+
buildLambda, buildStaticLambda -> listOf(capturedArgumentClass)
196+
else -> emptyList()
197+
}
198+
}

0 commit comments

Comments
 (0)