Skip to content

Commit d463ae2

Browse files
committed
Store kind of util class in a comment to obtain the kind of an existing util class
1 parent b9c740d commit d463ae2

File tree

3 files changed

+100
-44
lines changed

3 files changed

+100
-44
lines changed

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

+36-6
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,7 @@ class CodeGenerator(
8282
CodeGeneratorResult(
8383
generatedCode = renderClassFile(testClassFile),
8484
utilClassKind = UtilClassKind.fromCgContextOrNull(context),
85-
testsGenerationReport = testClassFile.testsGenerationReport,
86-
mockFrameworkUsed = context.mockFrameworkUsed
85+
testsGenerationReport = testClassFile.testsGenerationReport
8786
)
8887
}
8988
}
@@ -117,14 +116,12 @@ class CodeGenerator(
117116
* @property generatedCode the source code of the test class
118117
* @property utilClassKind the kind of util class if it is required, otherwise - null
119118
* @property testsGenerationReport some info about test generation process
120-
* @property mockFrameworkUsed flag indicating whether any mock objects have been created during code generation ot not
121119
*/
122120
data class CodeGeneratorResult(
123121
val generatedCode: String,
124122
// null if no util class needed, e.g. when we are generating utils directly into test class
125123
val utilClassKind: UtilClassKind?,
126124
val testsGenerationReport: TestsGenerationReport,
127-
val mockFrameworkUsed: Boolean = false
128125
)
129126

130127
/**
@@ -162,15 +159,40 @@ sealed class UtilClassKind(
162159
val utilClassVersionComment: CgComment
163160
get() = CgSingleLineComment("$UTIL_CLASS_VERSION_COMMENT_PREFIX${utilClassVersion}")
164161

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
180+
165181
/**
166182
* A kind of regular UtUtils class. "Regular" here means that this class does not use a mock framework.
167183
*/
168-
object RegularUtUtils : UtilClassKind(UtilClassFileMethodProvider, mockFrameworkUsed = false, priority = 0)
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+
}
169188

170189
/**
171190
* A kind of UtUtils class that uses a mock framework. At the moment the framework is Mockito.
172191
*/
173-
object UtUtilsWithMockito : UtilClassKind(UtilClassFileMethodProvider, mockFrameworkUsed = true, priority = 1)
192+
object UtUtilsWithMockito : UtilClassKind(UtilClassFileMethodProvider, mockFrameworkUsed = true, priority = 1) {
193+
override val utilClassKindCommentText: String
194+
get() = "This is UtUtils class with Mockito support"
195+
}
174196

175197
override fun compareTo(other: UtilClassKind): Int {
176198
return priority.compareTo(other.priority)
@@ -196,6 +218,14 @@ sealed class UtilClassKind(
196218
*/
197219
const val UTIL_CLASS_VERSION_COMMENT_PREFIX = "UtUtils class version: "
198220

221+
fun utilClassKindByCommentOrNull(comment: String): UtilClassKind? {
222+
return when (comment) {
223+
RegularUtUtils.utilClassKindCommentText -> RegularUtUtils
224+
UtUtilsWithMockito.utilClassKindCommentText -> UtUtilsWithMockito
225+
else -> null
226+
}
227+
}
228+
199229
/**
200230
* Check if an util class is required, and if so, what kind.
201231
* @return `null` if [CgContext.utilMethodProvider] is not [UtilClassFileMethodProvider],

Diff for: utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgUtilClassConstructor.kt

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ internal object CgUtilClassConstructor {
2323
id = utUtilsClassId
2424
body = buildRegularClassBody {
2525
content += utilClassKind.utilClassVersionComment
26+
content += utilClassKind.utilClassKindComment
2627
content += utilMethodProvider.utilMethodIds.map { CgUtilMethod(it) }
2728
}
2829
}

Diff for: utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt

+63-38
Original file line numberDiff line numberDiff line change
@@ -98,19 +98,9 @@ object CodeGenerationController {
9898

9999
private class UtilClassListener {
100100
var requiredUtilClassKind: UtilClassKind? = null
101-
var mockFrameworkUsed: Boolean = false
102101

103102
fun onTestClassGenerated(result: CodeGeneratorResult) {
104103
requiredUtilClassKind = maxOfNullable(requiredUtilClassKind, result.utilClassKind)
105-
mockFrameworkUsed = maxOf(mockFrameworkUsed, result.mockFrameworkUsed)
106-
}
107-
108-
private fun <T : Comparable<T>> maxOfNullable(a: T?, b: T?): T? {
109-
return when {
110-
a == null -> b
111-
b == null -> a
112-
else -> maxOf(a, b)
113-
}
114104
}
115105
}
116106

@@ -161,12 +151,12 @@ object CodeGenerationController {
161151

162152
run(EDT_LATER) {
163153
waitForCountDown(latch, timeout = 100, timeUnit = TimeUnit.MILLISECONDS) {
164-
val mockFrameworkUsed = utilClassListener.mockFrameworkUsed
165-
val utilClassKind = utilClassListener.requiredUtilClassKind
154+
val requiredUtilClassKind = utilClassListener.requiredUtilClassKind
166155
?: return@waitForCountDown // no util class needed
167156

168157
val existingUtilClass = model.codegenLanguage.getUtilClassOrNull(model.project, model.testModule)
169-
if (shouldCreateOrUpdateUtilClass(existingUtilClass, mockFrameworkUsed, utilClassKind)) {
158+
val utilClassKind = newUtilClassKindOrNull(existingUtilClass, requiredUtilClassKind)
159+
if (utilClassKind != null) {
170160
createOrUpdateUtilClass(
171161
testDirectory = baseTestDirectory,
172162
utilClassKind = utilClassKind,
@@ -204,40 +194,50 @@ object CodeGenerationController {
204194
}
205195
}
206196

207-
private fun shouldCreateOrUpdateUtilClass(
208-
existingUtilClass: PsiFile?,
209-
mockFrameworkUsed: Boolean,
210-
requiredUtilClassKind: UtilClassKind
211-
): Boolean {
212-
val mockFrameworkNotUsed = !mockFrameworkUsed
213-
214-
val utilClassExists = existingUtilClass != null
215-
216-
if (!utilClassExists) {
217-
// If no util class exists, then we should create a new one.
218-
return true
197+
/**
198+
* This method decides whether to overwrite an existing util class with a new one. And if so, then with what kind of util class.
199+
* - If no util class exists, then we generate a new one.
200+
* - If existing util class' version is out of date, then we overwrite it with a new one.
201+
* But we use the maximum of two kinds (existing and the new one) to avoid problems with mocks.
202+
* - If existing util class is up-to-date **and** has a greater or equal priority than the new one,
203+
* then we do not need to overwrite it (return null).
204+
* - Lastly, if the new util class kind has a greater priority than the existing one,
205+
* then we do overwrite it with a newer version.
206+
*
207+
* @param existingUtilClass a [PsiFile] representing a file of an existing util class. If it does not exist, then [existingUtilClass] is `null`.
208+
* @param requiredUtilClassKind the kind of the new util class that we attempt to generate.
209+
* @return an [UtilClassKind] of a new util class that will be created or `null`, if no new util class is needed.
210+
*/
211+
private fun newUtilClassKindOrNull(existingUtilClass: PsiFile?, requiredUtilClassKind: UtilClassKind): UtilClassKind? {
212+
if (existingUtilClass == null) {
213+
// If no util class exists, then we should create a new one with the given kind.
214+
return requiredUtilClassKind
219215
}
220216

221-
val existingUtilClassVersion = existingUtilClass?.utilClassVersionOrNull
217+
val existingUtilClassVersion = existingUtilClass.utilClassVersionOrNull ?: return requiredUtilClassKind
222218
val newUtilClassVersion = requiredUtilClassKind.utilClassVersion
223219
val versionIsUpdated = existingUtilClassVersion != newUtilClassVersion
224220

221+
val existingUtilClassKind = existingUtilClass.utilClassKindOrNull ?: return requiredUtilClassKind
222+
225223
if (versionIsUpdated) {
226-
// If an existing util class is out of date,
227-
// then we must overwrite it with a newer version.
228-
return true
224+
// If an existing util class is out of date, then we must overwrite it with a newer version.
225+
// But we choose the kind with more priority, because it is possible that
226+
// the existing util class needed mocks, but the new one doesn't.
227+
// In this case we still want to support mocks, because the previously generated tests
228+
// expect that the util class does support them.
229+
return maxOfNullable(existingUtilClassKind, requiredUtilClassKind)
229230
}
230231

231-
if (mockFrameworkNotUsed) {
232-
// If util class already exists and mock framework is not used,
233-
// then existing util class is enough, and we don't need to generate a new one.
234-
// That's because both regular and mock versions of util class can work
235-
// with tests that do not use mocks, so we do not have to worry about
236-
// version of util class that we have at the moment.
237-
return false
232+
if (requiredUtilClassKind <= existingUtilClassKind) {
233+
// If the existing util class kind has a greater or equal priority than the new one we attempt to generate,
234+
// then we should not do anything. The existing util class is already enough.
235+
return null
238236
}
239237

240-
return true
238+
// The last case. The existing util class has a strictly less priority than the new one.
239+
// So we generate the new one to overwrite the previous one with it.
240+
return requiredUtilClassKind
241241
}
242242

243243
/**
@@ -345,7 +345,7 @@ object CodeGenerationController {
345345
}
346346

347347
/**
348-
* Util class must have a comment that specifies the version of UTBot it was generated with.
348+
* Util class must have a comment that specifies its version.
349349
* This property represents the version specified by this comment if it exists. Otherwise, the property is `null`.
350350
*/
351351
private val PsiFile.utilClassVersionOrNull: String?
@@ -362,6 +362,23 @@ object CodeGenerationController {
362362
?.trim()
363363
}
364364

365+
/**
366+
* Util class must have a comment that specifies its kind.
367+
* This property obtains the kind specified by this comment if it exists. Otherwise, the property is `null`.
368+
*/
369+
private val PsiFile.utilClassKindOrNull: UtilClassKind?
370+
get() = runReadAction {
371+
val utilClass = (this as? PsiClassOwner)
372+
?.classes
373+
?.firstOrNull()
374+
?: return@runReadAction null
375+
376+
utilClass.childrenOfType<PsiComment>()
377+
.map { comment -> comment.text }
378+
.mapNotNull { text -> UtilClassKind.utilClassKindByCommentOrNull(text) }
379+
.firstOrNull()
380+
}
381+
365382
/**
366383
* @param srcClass class under test
367384
* @return name of the package of a given [srcClass].
@@ -886,4 +903,12 @@ object CodeGenerationController {
886903
title = "Failed to Create Class"
887904
)
888905
}
906+
907+
private fun <T : Comparable<T>> maxOfNullable(a: T?, b: T?): T? {
908+
return when {
909+
a == null -> b
910+
b == null -> a
911+
else -> maxOf(a, b)
912+
}
913+
}
889914
}

0 commit comments

Comments
 (0)