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