1
1
package org.utbot.intellij.plugin.generator
2
2
3
+ import org.utbot.common.HTML_LINE_SEPARATOR
4
+ import org.utbot.common.PathUtil.classFqnToPath
5
+ import org.utbot.common.PathUtil.toHtmlLinkTag
6
+ import org.utbot.common.appendHtmlLine
7
+ import org.utbot.framework.codegen.Import
8
+ import org.utbot.framework.codegen.ParametrizedTestSource
9
+ import org.utbot.framework.codegen.StaticImport
10
+ import org.utbot.framework.codegen.TestsCodeWithTestReport
11
+ import org.utbot.framework.codegen.model.ModelBasedTestCodeGenerator
12
+ import org.utbot.framework.codegen.model.constructor.tree.TestsGenerationReport
13
+ import org.utbot.framework.plugin.api.CodegenLanguage
14
+ import org.utbot.framework.plugin.api.UtTestCase
15
+ import org.utbot.intellij.plugin.sarif.SarifReportIdea
16
+ import org.utbot.intellij.plugin.sarif.SourceFindingStrategyIdea
17
+ import org.utbot.intellij.plugin.settings.Settings
18
+ import org.utbot.intellij.plugin.ui.utils.getOrCreateSarifReportsPath
19
+ import org.utbot.intellij.plugin.ui.utils.getOrCreateTestResourcesPath
20
+ import org.utbot.sarif.SarifReport
3
21
import com.intellij.codeInsight.CodeInsightUtil
4
22
import com.intellij.codeInsight.FileModificationService
5
23
import com.intellij.ide.fileTemplates.FileTemplateManager
6
24
import com.intellij.ide.fileTemplates.FileTemplateUtil
7
25
import com.intellij.ide.fileTemplates.JavaTemplateUtil
8
- import com.intellij.openapi.application.ApplicationManager
9
26
import com.intellij.openapi.command.WriteCommandAction.runWriteCommandAction
10
27
import com.intellij.openapi.command.executeCommand
11
28
import com.intellij.openapi.components.service
@@ -21,51 +38,54 @@ import com.intellij.psi.codeStyle.JavaCodeStyleManager
21
38
import com.intellij.psi.search.GlobalSearchScopesCore
22
39
import com.intellij.testIntegration.TestIntegrationUtils
23
40
import com.intellij.util.IncorrectOperationException
41
+ import com.intellij.util.concurrency.AppExecutorUtil
24
42
import com.intellij.util.io.exists
25
43
import com.siyeh.ig.psiutils.ImportUtils
44
+ import java.nio.file.Paths
45
+ import java.util.concurrent.CountDownLatch
26
46
import org.jetbrains.kotlin.asJava.classes.KtUltraLightClass
27
47
import org.jetbrains.kotlin.idea.core.ShortenReferences
28
48
import org.jetbrains.kotlin.idea.core.getPackage
29
49
import org.jetbrains.kotlin.idea.core.util.toPsiDirectory
30
50
import org.jetbrains.kotlin.idea.util.ImportInsertHelperImpl
51
+ import org.jetbrains.kotlin.idea.util.application.invokeLater
31
52
import org.jetbrains.kotlin.name.FqName
32
53
import org.jetbrains.kotlin.psi.KtClass
33
54
import org.jetbrains.kotlin.psi.KtNamedFunction
34
55
import org.jetbrains.kotlin.psi.KtPsiFactory
35
56
import org.jetbrains.kotlin.psi.psiUtil.endOffset
36
57
import org.jetbrains.kotlin.psi.psiUtil.startOffset
37
58
import org.jetbrains.kotlin.scripting.resolve.classId
38
- import org.utbot.common.HTML_LINE_SEPARATOR
39
- import org.utbot.common.PathUtil.classFqnToPath
40
- import org.utbot.common.PathUtil.toHtmlLinkTag
41
- import org.utbot.common.appendHtmlLine
42
- import org.utbot.framework.codegen.Import
43
- import org.utbot.framework.codegen.ParametrizedTestSource
44
- import org.utbot.framework.codegen.StaticImport
45
- import org.utbot.framework.codegen.TestsCodeWithTestReport
46
- import org.utbot.framework.codegen.model.ModelBasedTestCodeGenerator
47
- import org.utbot.framework.codegen.model.constructor.tree.TestsGenerationReport
48
- import org.utbot.framework.plugin.api.CodegenLanguage
49
- import org.utbot.framework.plugin.api.UtTestCase
50
59
import org.utbot.intellij.plugin.error.showErrorDialogLater
51
- import org.utbot.intellij.plugin.sarif.SarifReportIdea
52
- import org.utbot.intellij.plugin.sarif.SourceFindingStrategyIdea
53
- import org.utbot.intellij.plugin.settings.Settings
54
- import org.utbot.intellij.plugin.ui.*
55
- import org.utbot.intellij.plugin.ui.utils.getOrCreateSarifReportsPath
56
- import org.utbot.intellij.plugin.ui.utils.getOrCreateTestResourcesPath
57
- import org.utbot.sarif.SarifReport
58
- import java.nio.file.Paths
60
+ import org.utbot.intellij.plugin.ui.GenerateTestsModel
61
+ import org.utbot.intellij.plugin.ui.SarifReportNotifier
62
+ import org.utbot.intellij.plugin.ui.TestReportUrlOpeningListener
63
+ import org.utbot.intellij.plugin.ui.TestsReportNotifier
64
+ import org.utbot.intellij.plugin.ui.packageName
59
65
60
66
object TestGenerator {
61
- fun generateTests (model : GenerateTestsModel , testCases : Map <PsiClass , List <UtTestCase >>) {
62
- generateTestsInternal(model, testCases)
67
+ private enum class Target {THREAD_POOL , READ_ACTION , WRITE_ACTION , EDT_LATER }
68
+
69
+ private fun run (target : Target , runnable : Runnable ) {
70
+ UtContext .currentContext()?.let {
71
+ when (target) {
72
+ THREAD_POOL -> AppExecutorUtil .getAppExecutorService().submit {
73
+ withUtContext(it) {
74
+ runnable.run ()
75
+ }
76
+ }
77
+ READ_ACTION -> runReadAction { withUtContext(it) { runnable.run () } }
78
+ WRITE_ACTION -> runWriteAction { withUtContext(it) { runnable.run () } }
79
+ EDT_LATER -> invokeLater { withUtContext(it) { runnable.run () } }
80
+ }
81
+ } ? : error(" No context in thread ${Thread .currentThread()} " )
63
82
}
64
83
65
- private fun generateTestsInternal (model : GenerateTestsModel , testCasesByClass : Map <PsiClass , List <UtTestCase >>) {
84
+ fun generateTests (model : GenerateTestsModel , testCasesByClass : Map <PsiClass , List <UtTestCase >>) {
66
85
val baseTestDirectory = model.testSourceRoot?.toPsiDirectory(model.project)
67
86
? : return
68
87
val allTestPackages = getPackageDirectories(baseTestDirectory)
88
+ val latch = CountDownLatch (testCasesByClass.size)
69
89
70
90
for (srcClass in testCasesByClass.keys) {
71
91
val testCases = testCasesByClass[srcClass] ? : continue
@@ -75,20 +95,37 @@ object TestGenerator {
75
95
val testDirectory = allTestPackages[classPackageName] ? : baseTestDirectory
76
96
val testClass = createTestClass(srcClass, testDirectory, model) ? : continue
77
97
val file = testClass.containingFile
78
-
79
98
runWriteCommandAction(model.project, " Generate tests with UtBot" , null , {
80
- addTestMethodsAndSaveReports(testClass, file, testCases, model)
99
+ try {
100
+ addTestMethodsAndSaveReports(testClass, file, testCases, model, latch)
101
+ } catch (e: IncorrectOperationException ) {
102
+ showCreatingClassError(model.project, createTestClassName(srcClass))
103
+ }
81
104
})
82
105
} catch (e: IncorrectOperationException ) {
83
106
showCreatingClassError(model.project, createTestClassName(srcClass))
84
107
}
85
108
}
109
+ run (Target .READ_ACTION ) {
110
+ val sarifReportsPath = model.testModule.getOrCreateSarifReportsPath(model.testSourceRoot)
111
+ run (Target .THREAD_POOL ) {
112
+ waitForCountDown(latch, model, sarifReportsPath)
113
+ }
114
+ }
115
+ }
86
116
87
- mergeSarifReports(model)
117
+ private fun waitForCountDown (latch : CountDownLatch , model : GenerateTestsModel , sarifReportsPath : Path ) {
118
+ try {
119
+ if (! latch.await(5 , TimeUnit .SECONDS )) {
120
+ run (THREAD_POOL ) { waitForCountDown(latch, model, sarifReportsPath) }
121
+ } else {
122
+ mergeSarifReports(model, sarifReportsPath)
123
+ }
124
+ } catch (ignored: InterruptedException ) {
125
+ }
88
126
}
89
127
90
- private fun mergeSarifReports (model : GenerateTestsModel ) {
91
- val sarifReportsPath = model.testModule.getOrCreateSarifReportsPath(model.testSourceRoot)
128
+ private fun mergeSarifReports (model : GenerateTestsModel , sarifReportsPath : Path ) {
92
129
val sarifReports = sarifReportsPath.toFile()
93
130
.walkTopDown()
94
131
.filter { it.extension == " sarif" }
@@ -175,6 +212,7 @@ object TestGenerator {
175
212
file : PsiFile ,
176
213
testCases : List <UtTestCase >,
177
214
model : GenerateTestsModel ,
215
+ reportsCountDown : CountDownLatch ,
178
216
) {
179
217
val selectedMethods = TestIntegrationUtils .extractClassMethods(testClass, false )
180
218
val testFramework = model.testFramework
@@ -205,33 +243,50 @@ object TestGenerator {
205
243
when (generator) {
206
244
is ModelBasedTestCodeGenerator -> {
207
245
val editor = CodeInsightUtil .positionCursorAtLBrace(testClass.project, file, testClass)
208
- val testsCodeWithTestReport = generator.generateAsStringWithTestReport(testCases)
209
- val generatedTestsCode = testsCodeWithTestReport.generatedCode
246
+ // TODO Use PsiDocumentManager.getInstance(model.project).getDocument(file)
247
+ // if we don't want to open _all_ new files with tests in editor one-by-one
248
+ run (THREAD_POOL ) {
249
+ val testsCodeWithTestReport = generator.generateAsStringWithTestReport(testCases)
250
+ val generatedTestsCode = testsCodeWithTestReport.generatedCode
251
+ run (EDT_LATER ) {
252
+ run (WRITE_ACTION ) {
253
+ unblockDocument(testClass.project, editor.document)
254
+ // TODO: JIRA:1246 - display warnings if we rewrite the file
255
+ executeCommand(testClass.project, " Insert Generated Tests" ) {
256
+ editor.document.setText(generatedTestsCode)
257
+ }
258
+ unblockDocument(testClass.project, editor.document)
259
+
260
+ // after committing the document the `testClass` is invalid in PsiTree,
261
+ // so we have to reload it from the corresponding `file`
262
+ val testClassUpdated = (file as PsiClassOwner ).classes.first() // only one class in the file
263
+
264
+ // reformatting before creating reports due to
265
+ // SarifReport requires the final version of the generated tests code
266
+ runWriteCommandAction(testClassUpdated.project, " UtBot tests reformatting" , null , {
267
+ reformat(model, file, testClassUpdated)
268
+ })
269
+ unblockDocument(testClassUpdated.project, editor.document)
270
+
271
+ // uploading formatted code
272
+ val testsCodeWithTestReportFormatted =
273
+ testsCodeWithTestReport.copy(generatedCode = file.text)
274
+
275
+ // creating and saving reports
276
+ saveSarifAndTestReports(
277
+ testClassUpdated,
278
+ testCases,
279
+ model,
280
+ testsCodeWithTestReportFormatted,
281
+ reportsCountDown
282
+ )
210
283
211
- unblockDocument(testClass.project, editor.document)
212
- // TODO: JIRA:1246 - display warnings if we rewrite the file
213
- executeCommand(testClass.project, " Insert Generated Tests" ) {
214
- editor.document.setText(generatedTestsCode)
284
+ unblockDocument(testClassUpdated.project, editor.document)
285
+ }
286
+ }
215
287
}
216
- unblockDocument(testClass.project, editor.document)
217
-
218
- // after committing the document the `testClass` is invalid in PsiTree,
219
- // so we have to reload it from the corresponding `file`
220
- val testClassUpdated = (file as PsiClassOwner ).classes.first() // only one class in the file
221
-
222
- // reformatting before creating reports due to
223
- // SarifReport requires the final version of the generated tests code
224
- reformat(model, file, testClassUpdated)
225
- unblockDocument(testClassUpdated.project, editor.document)
226
-
227
- // uploading formatted code
228
- val testsCodeWithTestReportFormatted = testsCodeWithTestReport.copy(generatedCode = file.text)
229
-
230
- // creating and saving reports
231
- saveSarifAndTestReports(testClassUpdated, testCases, model, testsCodeWithTestReportFormatted)
232
-
233
- unblockDocument(testClassUpdated.project, editor.document)
234
288
}
289
+ // Note that reportsCountDown.countDown() has to be called in every generator implementation to complete whole process
235
290
else -> TODO (" Only model based code generator supported, but got: ${generator::class } " )
236
291
}
237
292
}
@@ -257,7 +312,8 @@ object TestGenerator {
257
312
testClass : PsiClass ,
258
313
testCases : List <UtTestCase >,
259
314
model : GenerateTestsModel ,
260
- testsCodeWithTestReport : TestsCodeWithTestReport
315
+ testsCodeWithTestReport : TestsCodeWithTestReport ,
316
+ reportsCountDown : CountDownLatch
261
317
) {
262
318
val project = model.project
263
319
val generatedTestsCode = testsCodeWithTestReport.generatedCode
@@ -274,6 +330,8 @@ object TestGenerator {
274
330
message = " Cannot save Sarif report via generated tests: error occurred '${e.message} '" ,
275
331
title = " Failed to save Sarif report"
276
332
)
333
+ } finally {
334
+ reportsCountDown.countDown()
277
335
}
278
336
279
337
try {
0 commit comments