@@ -7,6 +7,7 @@ import com.intellij.ide.fileTemplates.FileTemplateUtil
7
7
import com.intellij.ide.fileTemplates.JavaTemplateUtil
8
8
import com.intellij.ide.highlighter.JavaFileType
9
9
import com.intellij.openapi.application.ApplicationManager
10
+ import com.intellij.openapi.application.runReadAction
10
11
import com.intellij.openapi.application.runWriteAction
11
12
import com.intellij.openapi.command.WriteCommandAction.runWriteCommandAction
12
13
import com.intellij.openapi.command.executeCommand
@@ -56,10 +57,7 @@ import org.utbot.framework.codegen.StaticImport
56
57
import org.utbot.framework.codegen.model.CodeGenerator
57
58
import org.utbot.framework.codegen.model.CodeGenerationResult
58
59
import org.utbot.framework.codegen.model.UtilClassKind
59
- import org.utbot.framework.codegen.model.UtilClassKind.Companion.PACKAGE_DELIMITER
60
60
import org.utbot.framework.codegen.model.UtilClassKind.Companion.UT_UTILS_CLASS_NAME
61
- import org.utbot.framework.codegen.model.UtilClassKind.Companion.UT_UTILS_PACKAGE_NAME
62
- import org.utbot.framework.codegen.model.UtilClassKind.UtUtilsWithMockito
63
61
import org.utbot.framework.codegen.model.constructor.tree.TestsGenerationReport
64
62
import org.utbot.framework.plugin.api.CodegenLanguage
65
63
import org.utbot.framework.plugin.api.ExecutableId
@@ -91,11 +89,13 @@ import org.utbot.intellij.plugin.util.IntelliJApiHelper.run
91
89
92
90
object CodeGenerationController {
93
91
94
- private class UtilMethodListener {
92
+ private class UtilClassListener {
95
93
var requiredUtilClassKind: UtilClassKind ? = null
94
+ var mockFrameworkUsed: Boolean = false
96
95
97
96
fun onTestClassGenerated (result : CodeGenerationResult ) {
98
97
requiredUtilClassKind = maxOfNullable(requiredUtilClassKind, result.utilClassKind)
98
+ mockFrameworkUsed = maxOf(mockFrameworkUsed, result.mockFrameworkUsed)
99
99
}
100
100
101
101
private fun <T : Comparable <T >> maxOfNullable (a : T ? , b : T ? ): T ? {
@@ -115,7 +115,7 @@ object CodeGenerationController {
115
115
116
116
val reports = mutableListOf<TestsGenerationReport >()
117
117
val testFiles = mutableListOf<PsiFile >()
118
- val utilMethodListener = UtilMethodListener ()
118
+ val utilClassListener = UtilClassListener ()
119
119
for (srcClass in testSetsByClass.keys) {
120
120
val testSets = testSetsByClass[srcClass] ? : continue
121
121
try {
@@ -133,7 +133,7 @@ object CodeGenerationController {
133
133
model,
134
134
latch,
135
135
reports,
136
- utilMethodListener
136
+ utilClassListener
137
137
)
138
138
testFiles.add(testClassFile)
139
139
} catch (e: IncorrectOperationException ) {
@@ -148,25 +148,29 @@ object CodeGenerationController {
148
148
149
149
run (EDT_LATER ) {
150
150
waitForCountDown(latch, timeout = 100 , timeUnit = TimeUnit .MILLISECONDS ) {
151
- val utilClassKind = utilMethodListener.requiredUtilClassKind
152
- if (utilClassKind != null ) {
153
- // create a directory to put utils class into
154
- val utilClassDirectory = createUtUtilSubdirectories(baseTestDirectory)
155
-
156
- val language = model.codegenLanguage
157
- val utUtilsName = " $UT_UTILS_CLASS_NAME${language.extension} "
158
-
159
- val utUtilsAlreadyExists = utilClassDirectory.findFile(utUtilsName) != null
160
-
161
- // we generate and write an util class in one of the two cases:
162
- // - util file does not yet exist --> then we generate it, because it is required by generated tests
163
- // - utilClassKind is UtUtilsWithMockito --> then we generate utils class and add it to utils directory.
164
- // If utils file already exists, we overwrite it, because existing utils may be without Mockito,
165
- // and we want to make sure that the generated utils use Mockito.
166
- if (! utUtilsAlreadyExists || utilClassKind is UtUtilsWithMockito ) {
167
- generateAndWriteUtilClass(utUtilsName, utilClassDirectory, model, utilClassKind)
168
- }
151
+ val existingUtilClass = model.codegenLanguage.getUtilClassOrNull(baseTestDirectory)
152
+
153
+ val utilClassKind = utilClassListener.requiredUtilClassKind
154
+ ? : return @waitForCountDown // no util class needed
155
+
156
+ val utilClassExists = existingUtilClass != null
157
+ val mockFrameworkNotUsed = ! utilClassListener.mockFrameworkUsed
158
+
159
+ if (utilClassExists && mockFrameworkNotUsed) {
160
+ // If util class already exists and mock framework is not used,
161
+ // then existing util class is enough, and we don't need to generate a new one.
162
+ // That's because both regular and mock versions of util class can work
163
+ // with tests that do not use mocks, so we do not have to worry about
164
+ // version of util class that we have at the moment.
165
+ return @waitForCountDown
169
166
}
167
+
168
+ createOrUpdateUtilClass(
169
+ testDirectory = baseTestDirectory,
170
+ utilClassKind = utilClassKind,
171
+ existingUtilClass = existingUtilClass,
172
+ model = model
173
+ )
170
174
}
171
175
}
172
176
@@ -198,55 +202,107 @@ object CodeGenerationController {
198
202
}
199
203
200
204
/* *
201
- * Create package directories if needed for UtUtils class.
202
- * Then generate and create a UtUtils class file in the utils package.
203
- * Also run reformatting for the generated class.
205
+ * If [existingUtilClass] is null (no util class exists), then we create package directories for util class,
206
+ * create util class itself, and put it into the corresponding directory.
207
+ * Otherwise, we overwrite the existing util class with a new one.
208
+ * This is necessary in case if existing util class has no mocks support, but the newly generated tests do use mocks.
209
+ * So, we overwrite an util class with a new one that does support mocks.
210
+ *
211
+ * @param testDirectory root test directory where we will put our generated tests.
212
+ * @param utilClassKind kind of util class required by the test class(es) that we generated.
213
+ * @param existingUtilClass util class that already exists or null if it does not yet exist.
214
+ * @param model [GenerateTestsModel] that contains some useful information for util class generation.
204
215
*/
205
- private fun generateAndWriteUtilClass (
206
- utUtilsName : String ,
207
- utilClassDirectory : PsiDirectory ,
208
- model : GenerateTestsModel ,
209
- utilClassKind : UtilClassKind
216
+ private fun createOrUpdateUtilClass (
217
+ testDirectory : PsiDirectory ,
218
+ utilClassKind : UtilClassKind ,
219
+ existingUtilClass : PsiFile ? ,
220
+ model : GenerateTestsModel
210
221
) {
211
- val psiDocumentManager = PsiDocumentManager .getInstance( model.project)
222
+ val language = model.codegenLanguage
212
223
213
- val utUtilsText = utilClassKind.getUtilClassText(model.codegenLanguage)
224
+ val utUtilsFile = if (existingUtilClass == null ) {
225
+ // create a directory to put utils class into
226
+ val utilClassDirectory = createUtUtilSubdirectories(testDirectory)
227
+ // create util class file and put it into utils directory
228
+ createNewUtilClass(utilClassDirectory, language, utilClassKind, model)
229
+ } else {
230
+ overwriteUtilClass(existingUtilClass, utilClassKind, model)
231
+ }
232
+
233
+ val utUtilsClass = runReadAction {
234
+ // there's only one class in the file
235
+ (utUtilsFile as PsiClassOwner ).classes.first()
236
+ }
237
+
238
+ runWriteCommandAction(model.project, " UtBot util class reformatting" , null , {
239
+ reformat(model, utUtilsFile, utUtilsClass)
240
+ })
241
+
242
+ val utUtilsDocument = PsiDocumentManager
243
+ .getInstance(model.project)
244
+ .getDocument(utUtilsFile) ? : error(" Failed to get a Document for UtUtils file" )
245
+
246
+ unblockDocument(model.project, utUtilsDocument)
247
+ }
214
248
215
- val existingUtUtilsDocument = utilClassDirectory
216
- .findFile(utUtilsName)
217
- ?.let { psiDocumentManager.getDocument(it) }
249
+ private fun overwriteUtilClass (
250
+ existingUtilClass : PsiFile ,
251
+ utilClassKind : UtilClassKind ,
252
+ model : GenerateTestsModel
253
+ ): PsiFile {
254
+ val utilsClassDocument = PsiDocumentManager
255
+ .getInstance(model.project)
256
+ .getDocument(existingUtilClass)
257
+ ? : error(" Failed to get Document for UtUtils class PsiFile: ${existingUtilClass.name} " )
218
258
219
- val utUtilsFile = if (existingUtUtilsDocument != null ) {
220
- executeCommand {
221
- existingUtUtilsDocument.setText(utUtilsText)
259
+ val utUtilsText = utilClassKind.getUtilClassText(model.codegenLanguage)
260
+
261
+ run (EDT_LATER ) {
262
+ run (WRITE_ACTION ) {
263
+ unblockDocument(model.project, utilsClassDocument)
264
+ executeCommand {
265
+ utilsClassDocument.setText(utUtilsText)
266
+ }
267
+ unblockDocument(model.project, utilsClassDocument)
222
268
}
223
- unblockDocument(model.project, existingUtUtilsDocument)
224
- psiDocumentManager.getPsiFile(existingUtUtilsDocument)
225
- } else {
226
- val utUtilsFile = PsiFileFactory .getInstance(model.project)
269
+ }
270
+ return existingUtilClass
271
+ }
272
+
273
+ /* *
274
+ * This method creates an util class file and adds it into [utilClassDirectory].
275
+ *
276
+ * @param utilClassDirectory directory to put util class into.
277
+ * @param language language of util class.
278
+ * @param utilClassKind kind of util class required by the test class(es) that we generated.
279
+ * @param model [GenerateTestsModel] that contains some useful information for util class generation.
280
+ */
281
+ private fun createNewUtilClass (
282
+ utilClassDirectory : PsiDirectory ,
283
+ language : CodegenLanguage ,
284
+ utilClassKind : UtilClassKind ,
285
+ model : GenerateTestsModel ,
286
+ ): PsiFile {
287
+ val utUtilsName = language.utilClassFileName
288
+
289
+ val utUtilsText = utilClassKind.getUtilClassText(model.codegenLanguage)
290
+
291
+ val utUtilsFile = runReadAction {
292
+ PsiFileFactory .getInstance(model.project)
227
293
.createFileFromText(
228
294
utUtilsName,
229
295
model.codegenLanguage.fileType,
230
296
utUtilsText
231
297
)
232
- // add the UtUtils class file into the utils directory
233
- runWriteCommandAction(model.project) {
234
- utilClassDirectory.add(utUtilsFile)
235
- }
236
- utUtilsFile
237
298
}
238
299
239
- // there's only one class in the file
240
- val utUtilsClass = (utUtilsFile as PsiClassOwner ).classes.first()
241
-
242
- runWriteCommandAction(model.project, " UtBot util class reformatting" , null , {
243
- reformat(model, utUtilsFile, utUtilsClass)
244
- })
245
-
246
- val utUtilsDocument = psiDocumentManager.getDocument(utUtilsFile)
247
- ? : error(" Failed to get a Document for UtUtils file" )
300
+ // add UtUtils class file into the utils directory
301
+ runWriteCommandAction(model.project) {
302
+ utilClassDirectory.add(utUtilsFile)
303
+ }
248
304
249
- unblockDocument(model.project, utUtilsDocument)
305
+ return utUtilsFile
250
306
}
251
307
252
308
/* *
@@ -261,12 +317,40 @@ object CodeGenerationController {
261
317
}
262
318
}
263
319
320
+ private val CodegenLanguage .utilClassFileName: String
321
+ get() = " $UT_UTILS_CLASS_NAME${this .extension} "
322
+
323
+ /* *
324
+ * @param testDirectory root test directory where we will put our generated tests.
325
+ * @return directory for util class if it exists or null otherwise.
326
+ */
327
+ private fun getUtilDirectoryOrNull (testDirectory : PsiDirectory ): PsiDirectory ? {
328
+ val directoryNames = UtilClassKind .utilsPackages
329
+ var currentDirectory = testDirectory
330
+ for (name in directoryNames) {
331
+ val subdirectory = runReadAction { currentDirectory.findSubdirectory(name) } ? : return null
332
+ currentDirectory = subdirectory
333
+ }
334
+ return currentDirectory
335
+ }
336
+
337
+ /* *
338
+ * @param testDirectory root test directory where we will put our generated tests.
339
+ * @return file of util class if it exists or null otherwise.
340
+ */
341
+ private fun CodegenLanguage.getUtilClassOrNull (testDirectory : PsiDirectory ): PsiFile ? {
342
+ return runReadAction {
343
+ val utilDirectory = getUtilDirectoryOrNull(testDirectory)
344
+ utilDirectory?.findFile(this .utilClassFileName)
345
+ }
346
+ }
347
+
264
348
/* *
265
349
* Create all package directories for UtUtils class.
266
350
* @return the innermost directory - utils from `org.utbot.runtime.utils`
267
351
*/
268
352
private fun createUtUtilSubdirectories (baseTestDirectory : PsiDirectory ): PsiDirectory {
269
- val directoryNames = UT_UTILS_PACKAGE_NAME .split( PACKAGE_DELIMITER )
353
+ val directoryNames = UtilClassKind .utilsPackages
270
354
var currentDirectory = baseTestDirectory
271
355
runWriteCommandAction(baseTestDirectory.project) {
272
356
for (name in directoryNames) {
@@ -393,7 +477,7 @@ object CodeGenerationController {
393
477
model : GenerateTestsModel ,
394
478
reportsCountDown : CountDownLatch ,
395
479
reports : MutableList <TestsGenerationReport >,
396
- utilMethodListener : UtilMethodListener
480
+ utilClassListener : UtilClassListener
397
481
) {
398
482
val classUnderTest = testSets.first().method.clazz
399
483
val classMethods = TestIntegrationUtils .extractClassMethods(srcClass, false )
@@ -422,7 +506,7 @@ object CodeGenerationController {
422
506
// if we don't want to open _all_ new files with tests in editor one-by-one
423
507
run (THREAD_POOL ) {
424
508
val codeGenerationResult = codeGenerator.generateAsStringWithTestReport(testSets)
425
- utilMethodListener .onTestClassGenerated(codeGenerationResult)
509
+ utilClassListener .onTestClassGenerated(codeGenerationResult)
426
510
val generatedTestsCode = codeGenerationResult.generatedCode
427
511
run (EDT_LATER ) {
428
512
run (WRITE_ACTION ) {
0 commit comments