Skip to content

Commit 2a3cf3f

Browse files
committed
Store kind of util class in a comment to obtain the kind of an existing util class
1 parent 0b84407 commit 2a3cf3f

File tree

3 files changed

+100
-44
lines changed

3 files changed

+100
-44
lines changed

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

Lines changed: 36 additions & 6 deletions
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],

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

Lines changed: 1 addition & 0 deletions
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
}

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

Lines changed: 63 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -96,19 +96,9 @@ object CodeGenerationController {
9696

9797
private class UtilClassListener {
9898
var requiredUtilClassKind: UtilClassKind? = null
99-
var mockFrameworkUsed: Boolean = false
10099

101100
fun onTestClassGenerated(result: CodeGeneratorResult) {
102101
requiredUtilClassKind = maxOfNullable(requiredUtilClassKind, result.utilClassKind)
103-
mockFrameworkUsed = maxOf(mockFrameworkUsed, result.mockFrameworkUsed)
104-
}
105-
106-
private fun <T : Comparable<T>> maxOfNullable(a: T?, b: T?): T? {
107-
return when {
108-
a == null -> b
109-
b == null -> a
110-
else -> maxOf(a, b)
111-
}
112102
}
113103
}
114104

@@ -153,12 +143,12 @@ object CodeGenerationController {
153143

154144
run(EDT_LATER) {
155145
waitForCountDown(latch, timeout = 100, timeUnit = TimeUnit.MILLISECONDS) {
156-
val mockFrameworkUsed = utilClassListener.mockFrameworkUsed
157-
val utilClassKind = utilClassListener.requiredUtilClassKind
146+
val requiredUtilClassKind = utilClassListener.requiredUtilClassKind
158147
?: return@waitForCountDown // no util class needed
159148

160149
val existingUtilClass = model.codegenLanguage.getUtilClassOrNull(model.project, model.testModule)
161-
if (shouldCreateOrUpdateUtilClass(existingUtilClass, mockFrameworkUsed, utilClassKind)) {
150+
val utilClassKind = newUtilClassKindOrNull(existingUtilClass, requiredUtilClassKind)
151+
if (utilClassKind != null) {
162152
createOrUpdateUtilClass(
163153
testDirectory = baseTestDirectory,
164154
utilClassKind = utilClassKind,
@@ -196,40 +186,50 @@ object CodeGenerationController {
196186
}
197187
}
198188

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

213-
val existingUtilClassVersion = existingUtilClass?.utilClassVersionOrNull
209+
val existingUtilClassVersion = existingUtilClass.utilClassVersionOrNull ?: return requiredUtilClassKind
214210
val newUtilClassVersion = requiredUtilClassKind.utilClassVersion
215211
val versionIsUpdated = existingUtilClassVersion != newUtilClassVersion
216212

213+
val existingUtilClassKind = existingUtilClass.utilClassKindOrNull ?: return requiredUtilClassKind
214+
217215
if (versionIsUpdated) {
218-
// If an existing util class is out of date,
219-
// then we must overwrite it with a newer version.
220-
return true
216+
// If an existing util class is out of date, then we must overwrite it with a newer version.
217+
// But we choose the kind with more priority, because it is possible that
218+
// the existing util class needed mocks, but the new one doesn't.
219+
// In this case we still want to support mocks, because the previously generated tests
220+
// expect that the util class does support them.
221+
return maxOfNullable(existingUtilClassKind, requiredUtilClassKind)
221222
}
222223

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

232-
return true
230+
// The last case. The existing util class has a strictly less priority than the new one.
231+
// So we generate the new one to overwrite the previous one with it.
232+
return requiredUtilClassKind
233233
}
234234

235235
/**
@@ -337,7 +337,7 @@ object CodeGenerationController {
337337
}
338338

339339
/**
340-
* Util class must have a comment that specifies the version of UTBot it was generated with.
340+
* Util class must have a comment that specifies its version.
341341
* This property represents the version specified by this comment if it exists. Otherwise, the property is `null`.
342342
*/
343343
private val PsiFile.utilClassVersionOrNull: String?
@@ -354,6 +354,23 @@ object CodeGenerationController {
354354
?.trim()
355355
}
356356

357+
/**
358+
* Util class must have a comment that specifies its kind.
359+
* This property obtains the kind specified by this comment if it exists. Otherwise, the property is `null`.
360+
*/
361+
private val PsiFile.utilClassKindOrNull: UtilClassKind?
362+
get() = runReadAction {
363+
val utilClass = (this as? PsiClassOwner)
364+
?.classes
365+
?.firstOrNull()
366+
?: return@runReadAction null
367+
368+
utilClass.childrenOfType<PsiComment>()
369+
.map { comment -> comment.text }
370+
.mapNotNull { text -> UtilClassKind.utilClassKindByCommentOrNull(text) }
371+
.firstOrNull()
372+
}
373+
357374
/**
358375
* @param srcClass class under test
359376
* @return name of the package of a given [srcClass].
@@ -878,4 +895,12 @@ object CodeGenerationController {
878895
title = "Failed to Create Class"
879896
)
880897
}
898+
899+
private fun <T : Comparable<T>> maxOfNullable(a: T?, b: T?): T? {
900+
return when {
901+
a == null -> b
902+
b == null -> a
903+
else -> maxOf(a, b)
904+
}
905+
}
881906
}

0 commit comments

Comments
 (0)