Skip to content

Commit 779e053

Browse files
authored
Generate util methods as a separate class in IntelliJ plugin (#689)
1 parent c9e755a commit 779e053

29 files changed

+1412
-455
lines changed

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

Lines changed: 162 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,32 @@ import org.utbot.framework.codegen.ParametrizedTestSource
66
import org.utbot.framework.codegen.RuntimeExceptionTestsBehaviour
77
import org.utbot.framework.codegen.StaticsMocking
88
import org.utbot.framework.codegen.TestFramework
9+
import org.utbot.framework.codegen.model.constructor.builtin.TestClassUtilMethodProvider
10+
import org.utbot.framework.codegen.model.constructor.builtin.UtilClassFileMethodProvider
911
import org.utbot.framework.codegen.model.constructor.CgMethodTestSet
1012
import org.utbot.framework.codegen.model.constructor.context.CgContext
13+
import org.utbot.framework.codegen.model.constructor.context.CgContextOwner
1114
import org.utbot.framework.codegen.model.constructor.tree.CgTestClassConstructor
15+
import org.utbot.framework.codegen.model.constructor.tree.CgUtilClassConstructor
1216
import org.utbot.framework.codegen.model.constructor.tree.TestsGenerationReport
13-
import org.utbot.framework.codegen.model.tree.CgTestClassFile
17+
import org.utbot.framework.codegen.model.tree.AbstractCgClassFile
18+
import org.utbot.framework.codegen.model.tree.CgRegularClassFile
1419
import org.utbot.framework.codegen.model.visitor.CgAbstractRenderer
1520
import org.utbot.framework.plugin.api.ClassId
1621
import org.utbot.framework.plugin.api.CodegenLanguage
1722
import org.utbot.framework.plugin.api.ExecutableId
1823
import org.utbot.framework.plugin.api.MockFramework
1924
import org.utbot.framework.plugin.api.UtMethodTestSet
2025
import org.utbot.framework.codegen.model.constructor.TestClassModel
26+
import org.utbot.framework.codegen.model.tree.CgComment
27+
import org.utbot.framework.codegen.model.tree.CgSingleLineComment
2128

2229
class CodeGenerator(
2330
private val classUnderTest: ClassId,
2431
paramNames: MutableMap<ExecutableId, List<String>> = mutableMapOf(),
32+
generateUtilClassFile: Boolean = false,
2533
testFramework: TestFramework = TestFramework.defaultItem,
26-
mockFramework: MockFramework? = MockFramework.defaultItem,
34+
mockFramework: MockFramework = MockFramework.defaultItem,
2735
staticsMocking: StaticsMocking = StaticsMocking.defaultItem,
2836
forceStaticMocking: ForceStaticMocking = ForceStaticMocking.defaultItem,
2937
generateWarningsForStaticMocking: Boolean = true,
@@ -36,9 +44,10 @@ class CodeGenerator(
3644
) {
3745
private var context: CgContext = CgContext(
3846
classUnderTest = classUnderTest,
47+
generateUtilClassFile = generateUtilClassFile,
3948
paramNames = paramNames,
4049
testFramework = testFramework,
41-
mockFramework = mockFramework ?: MockFramework.MOCKITO,
50+
mockFramework = mockFramework,
4251
codegenLanguage = codegenLanguage,
4352
parametrizedTestSource = parameterizedTestSource,
4453
staticsMocking = staticsMocking,
@@ -58,19 +67,23 @@ class CodeGenerator(
5867
fun generateAsStringWithTestReport(
5968
testSets: Collection<UtMethodTestSet>,
6069
testClassCustomName: String? = null,
61-
): TestsCodeWithTestReport {
70+
): CodeGeneratorResult {
6271
val cgTestSets = testSets.map { CgMethodTestSet(it) }.toList()
6372
return generateAsStringWithTestReport(cgTestSets, testClassCustomName)
6473
}
6574

6675
private fun generateAsStringWithTestReport(
6776
cgTestSets: List<CgMethodTestSet>,
6877
testClassCustomName: String? = null,
69-
): TestsCodeWithTestReport = withCustomContext(testClassCustomName) {
78+
): CodeGeneratorResult = withCustomContext(testClassCustomName) {
7079
context.withTestClassFileScope {
7180
val testClassModel = TestClassModel.fromTestSets(classUnderTest, cgTestSets)
7281
val testClassFile = CgTestClassConstructor(context).construct(testClassModel)
73-
TestsCodeWithTestReport(renderClassFile(testClassFile), testClassFile.testsGenerationReport)
82+
CodeGeneratorResult(
83+
generatedCode = renderClassFile(testClassFile),
84+
utilClassKind = UtilClassKind.fromCgContextOrNull(context),
85+
testsGenerationReport = testClassFile.testsGenerationReport
86+
)
7487
}
7588
}
7689

@@ -92,12 +105,153 @@ class CodeGenerator(
92105
}
93106
}
94107

95-
private fun renderClassFile(file: CgTestClassFile): String {
108+
private fun renderClassFile(file: AbstractCgClassFile<*>): String {
96109
val renderer = CgAbstractRenderer.makeRenderer(context)
97110
file.accept(renderer)
98111
return renderer.toString()
99112
}
100113
}
101114

102-
data class TestsCodeWithTestReport(val generatedCode: String, val testsGenerationReport: TestsGenerationReport)
115+
/**
116+
* @property generatedCode the source code of the test class
117+
* @property utilClassKind the kind of util class if it is required, otherwise - null
118+
* @property testsGenerationReport some info about test generation process
119+
*/
120+
data class CodeGeneratorResult(
121+
val generatedCode: String,
122+
// null if no util class needed, e.g. when we are generating utils directly into test class
123+
val utilClassKind: UtilClassKind?,
124+
val testsGenerationReport: TestsGenerationReport,
125+
)
126+
127+
/**
128+
* A kind of util class. See the description of each kind at their respective classes.
129+
* @property utilMethodProvider a [UtilClassFileMethodProvider] containing information about
130+
* utilities that come from a separately generated UtUtils class
131+
* (as opposed to utils that are declared directly in the test class, for example).
132+
* @property mockFrameworkUsed a flag indicating if a mock framework was used.
133+
* For detailed description see [CgContextOwner.mockFrameworkUsed].
134+
* @property mockFramework a framework used to create mocks
135+
* @property priority when we generate multiple test classes, they can require different [UtilClassKind].
136+
* We will generate an util class corresponding to the kind with the greatest priority.
137+
* For example, one test class may not use mocks, but the other one does.
138+
* Then we will generate an util class with mocks, because it has a greater priority (see [UtUtilsWithMockito]).
139+
*/
140+
sealed class UtilClassKind(
141+
internal val utilMethodProvider: UtilClassFileMethodProvider,
142+
internal val mockFrameworkUsed: Boolean,
143+
internal val mockFramework: MockFramework = MockFramework.MOCKITO,
144+
private val priority: Int
145+
) : Comparable<UtilClassKind> {
146+
147+
/**
148+
* The version of util class being generated.
149+
* For more details see [UtilClassFileMethodProvider.UTIL_CLASS_VERSION].
150+
*/
151+
val utilClassVersion: String
152+
get() = UtilClassFileMethodProvider.UTIL_CLASS_VERSION
153+
154+
/**
155+
* The comment specifying the version of util class being generated.
156+
*
157+
* @see UtilClassFileMethodProvider.UTIL_CLASS_VERSION
158+
*/
159+
val utilClassVersionComment: CgComment
160+
get() = CgSingleLineComment("$UTIL_CLASS_VERSION_COMMENT_PREFIX${utilClassVersion}")
161+
162+
163+
/**
164+
* The comment specifying the kind of util class being generated.
165+
*
166+
* @see utilClassKindCommentText
167+
*/
168+
val utilClassKindComment: CgComment
169+
get() = CgSingleLineComment(utilClassKindCommentText)
170+
171+
/**
172+
* The text of comment specifying the kind of util class.
173+
* At the moment, there are two kinds: [RegularUtUtils] (without Mockito) and [UtUtilsWithMockito].
174+
*
175+
* This comment is needed when the plugin decides whether to overwrite an existing util class or not.
176+
* When making that decision, it is important to determine if the existing class uses mocks or not,
177+
* and this comment will help do that.
178+
*/
179+
abstract val utilClassKindCommentText: String
103180

181+
/**
182+
* A kind of regular UtUtils class. "Regular" here means that this class does not use a mock framework.
183+
*/
184+
object RegularUtUtils : UtilClassKind(UtilClassFileMethodProvider, mockFrameworkUsed = false, priority = 0) {
185+
override val utilClassKindCommentText: String
186+
get() = "This is a regular UtUtils class (without mock framework usage)"
187+
}
188+
189+
/**
190+
* A kind of UtUtils class that uses a mock framework. At the moment the framework is Mockito.
191+
*/
192+
object UtUtilsWithMockito : UtilClassKind(UtilClassFileMethodProvider, mockFrameworkUsed = true, priority = 1) {
193+
override val utilClassKindCommentText: String
194+
get() = "This is UtUtils class with Mockito support"
195+
}
196+
197+
override fun compareTo(other: UtilClassKind): Int {
198+
return priority.compareTo(other.priority)
199+
}
200+
201+
/**
202+
* Construct an util class file as a [CgRegularClassFile] and render it.
203+
* @return the text of the generated util class file.
204+
*/
205+
fun getUtilClassText(codegenLanguage: CodegenLanguage): String {
206+
val utilClassFile = CgUtilClassConstructor.constructUtilsClassFile(this)
207+
val renderer = CgAbstractRenderer.makeRenderer(this, codegenLanguage)
208+
utilClassFile.accept(renderer)
209+
return renderer.toString()
210+
}
211+
212+
companion object {
213+
214+
/**
215+
* Class UtUtils will contain a comment specifying the version of this util class
216+
* (if we ever change util methods, then util class will be different, hence the update of its version).
217+
* This is a prefix that will go before the version in the comment.
218+
*/
219+
const val UTIL_CLASS_VERSION_COMMENT_PREFIX = "UtUtils class version: "
220+
221+
fun utilClassKindByCommentOrNull(comment: String): UtilClassKind? {
222+
return when (comment) {
223+
RegularUtUtils.utilClassKindCommentText -> RegularUtUtils
224+
UtUtilsWithMockito.utilClassKindCommentText -> UtUtilsWithMockito
225+
else -> null
226+
}
227+
}
228+
229+
/**
230+
* Check if an util class is required, and if so, what kind.
231+
* @return `null` if [CgContext.utilMethodProvider] is not [UtilClassFileMethodProvider],
232+
* because it means that util methods will be taken from some other provider (e.g. [TestClassUtilMethodProvider]).
233+
*/
234+
internal fun fromCgContextOrNull(context: CgContext): UtilClassKind? {
235+
if (context.requiredUtilMethods.isEmpty()) return null
236+
if (!context.mockFrameworkUsed) {
237+
return RegularUtUtils
238+
}
239+
return when (context.mockFramework) {
240+
MockFramework.MOCKITO -> UtUtilsWithMockito
241+
// in case we will add any other mock frameworks, newer Kotlin compiler versions
242+
// will report a non-exhaustive 'when', so we will not forget to support them here as well
243+
}
244+
}
245+
246+
const val UT_UTILS_PACKAGE_NAME = "org.utbot.runtime.utils"
247+
const val UT_UTILS_CLASS_NAME = "UtUtils"
248+
const val PACKAGE_DELIMITER = "."
249+
250+
/**
251+
* List of package names of UtUtils class.
252+
* See whole package name at [UT_UTILS_PACKAGE_NAME].
253+
*/
254+
val utilsPackages: List<String>
255+
get() = UT_UTILS_PACKAGE_NAME.split(PACKAGE_DELIMITER)
256+
}
257+
}

0 commit comments

Comments
 (0)