11package org.utbot.intellij.plugin.generator
22
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
321import com.intellij.codeInsight.CodeInsightUtil
422import com.intellij.codeInsight.FileModificationService
523import com.intellij.ide.fileTemplates.FileTemplateManager
624import com.intellij.ide.fileTemplates.FileTemplateUtil
725import com.intellij.ide.fileTemplates.JavaTemplateUtil
826import com.intellij.openapi.application.ApplicationManager
27+ import com.intellij.openapi.application.runReadAction
28+ import com.intellij.openapi.application.runWriteAction
929import com.intellij.openapi.command.WriteCommandAction.runWriteCommandAction
1030import com.intellij.openapi.command.executeCommand
1131import com.intellij.openapi.components.service
@@ -21,51 +41,59 @@ import com.intellij.psi.codeStyle.JavaCodeStyleManager
2141import com.intellij.psi.search.GlobalSearchScopesCore
2242import com.intellij.testIntegration.TestIntegrationUtils
2343import com.intellij.util.IncorrectOperationException
44+ import com.intellij.util.concurrency.AppExecutorUtil
2445import com.intellij.util.io.exists
2546import com.siyeh.ig.psiutils.ImportUtils
47+ import java.nio.file.Path
48+ import java.nio.file.Paths
49+ import java.util.concurrent.CountDownLatch
50+ import java.util.concurrent.TimeUnit
2651import org.jetbrains.kotlin.asJava.classes.KtUltraLightClass
2752import org.jetbrains.kotlin.idea.core.ShortenReferences
2853import org.jetbrains.kotlin.idea.core.getPackage
2954import org.jetbrains.kotlin.idea.core.util.toPsiDirectory
3055import org.jetbrains.kotlin.idea.util.ImportInsertHelperImpl
56+ import org.jetbrains.kotlin.idea.util.application.invokeLater
3157import org.jetbrains.kotlin.name.FqName
3258import org.jetbrains.kotlin.psi.KtClass
3359import org.jetbrains.kotlin.psi.KtNamedFunction
3460import org.jetbrains.kotlin.psi.KtPsiFactory
3561import org.jetbrains.kotlin.psi.psiUtil.endOffset
3662import org.jetbrains.kotlin.psi.psiUtil.startOffset
3763import 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
64+ import org.utbot.framework.plugin.api.util.UtContext
65+ import org.utbot.framework.plugin.api.util.withUtContext
5066import 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
67+ import org.utbot.intellij.plugin.generator.TestGenerator.Target.*
68+ import org.utbot.intellij.plugin.ui.GenerateTestsModel
69+ import org.utbot.intellij.plugin.ui.SarifReportNotifier
70+ import org.utbot.intellij.plugin.ui.TestReportUrlOpeningListener
71+ import org.utbot.intellij.plugin.ui.TestsReportNotifier
72+ import org.utbot.intellij.plugin.ui.packageName
5973
6074object TestGenerator {
61- fun generateTests (model : GenerateTestsModel , testCases : Map <PsiClass , List <UtTestCase >>) {
62- generateTestsInternal(model, testCases)
75+ private enum class Target {THREAD_POOL , READ_ACTION , WRITE_ACTION , EDT_LATER }
76+
77+ private fun run (target : Target , runnable : Runnable ) {
78+ UtContext .currentContext()?.let {
79+ when (target) {
80+ THREAD_POOL -> AppExecutorUtil .getAppExecutorService().submit {
81+ withUtContext(it) {
82+ runnable.run ()
83+ }
84+ }
85+ READ_ACTION -> runReadAction { withUtContext(it) { runnable.run () } }
86+ WRITE_ACTION -> runWriteAction { withUtContext(it) { runnable.run () } }
87+ EDT_LATER -> invokeLater { withUtContext(it) { runnable.run () } }
88+ }
89+ } ? : error(" No context in thread ${Thread .currentThread()} " )
6390 }
6491
65- private fun generateTestsInternal (model : GenerateTestsModel , testCasesByClass : Map <PsiClass , List <UtTestCase >>) {
92+ fun generateTests (model : GenerateTestsModel , testCasesByClass : Map <PsiClass , List <UtTestCase >>) {
6693 val baseTestDirectory = model.testSourceRoot?.toPsiDirectory(model.project)
6794 ? : return
6895 val allTestPackages = getPackageDirectories(baseTestDirectory)
96+ val latch = CountDownLatch (testCasesByClass.size)
6997
7098 for (srcClass in testCasesByClass.keys) {
7199 val testCases = testCasesByClass[srcClass] ? : continue
@@ -75,20 +103,37 @@ object TestGenerator {
75103 val testDirectory = allTestPackages[classPackageName] ? : baseTestDirectory
76104 val testClass = createTestClass(srcClass, testDirectory, model) ? : continue
77105 val file = testClass.containingFile
78-
79106 runWriteCommandAction(model.project, " Generate tests with UtBot" , null , {
80- addTestMethodsAndSaveReports(testClass, file, testCases, model)
107+ try {
108+ addTestMethodsAndSaveReports(testClass, file, testCases, model, latch)
109+ } catch (e: IncorrectOperationException ) {
110+ showCreatingClassError(model.project, createTestClassName(srcClass))
111+ }
81112 })
82113 } catch (e: IncorrectOperationException ) {
83114 showCreatingClassError(model.project, createTestClassName(srcClass))
84115 }
85116 }
117+ run (READ_ACTION ) {
118+ val sarifReportsPath = model.testModule.getOrCreateSarifReportsPath(model.testSourceRoot)
119+ run (THREAD_POOL ) {
120+ waitForCountDown(latch, model, sarifReportsPath)
121+ }
122+ }
123+ }
86124
87- mergeSarifReports(model)
125+ private fun waitForCountDown (latch : CountDownLatch , model : GenerateTestsModel , sarifReportsPath : Path ) {
126+ try {
127+ if (! latch.await(5 , TimeUnit .SECONDS )) {
128+ run (THREAD_POOL ) { waitForCountDown(latch, model, sarifReportsPath) }
129+ } else {
130+ mergeSarifReports(model, sarifReportsPath)
131+ }
132+ } catch (ignored: InterruptedException ) {
133+ }
88134 }
89135
90- private fun mergeSarifReports (model : GenerateTestsModel ) {
91- val sarifReportsPath = model.testModule.getOrCreateSarifReportsPath(model.testSourceRoot)
136+ private fun mergeSarifReports (model : GenerateTestsModel , sarifReportsPath : Path ) {
92137 val sarifReports = sarifReportsPath.toFile()
93138 .walkTopDown()
94139 .filter { it.extension == " sarif" }
@@ -177,6 +222,7 @@ object TestGenerator {
177222 file : PsiFile ,
178223 testCases : List <UtTestCase >,
179224 model : GenerateTestsModel ,
225+ reportsCountDown : CountDownLatch ,
180226 ) {
181227 val selectedMethods = TestIntegrationUtils .extractClassMethods(testClass, false )
182228 val testFramework = model.testFramework
@@ -207,33 +253,50 @@ object TestGenerator {
207253 when (generator) {
208254 is ModelBasedTestCodeGenerator -> {
209255 val editor = CodeInsightUtil .positionCursorAtLBrace(testClass.project, file, testClass)
210- val testsCodeWithTestReport = generator.generateAsStringWithTestReport(testCases)
211- val generatedTestsCode = testsCodeWithTestReport.generatedCode
256+ // TODO Use PsiDocumentManager.getInstance(model.project).getDocument(file)
257+ // if we don't want to open _all_ new files with tests in editor one-by-one
258+ run (THREAD_POOL ) {
259+ val testsCodeWithTestReport = generator.generateAsStringWithTestReport(testCases)
260+ val generatedTestsCode = testsCodeWithTestReport.generatedCode
261+ run (EDT_LATER ) {
262+ run (WRITE_ACTION ) {
263+ unblockDocument(testClass.project, editor.document)
264+ // TODO: JIRA:1246 - display warnings if we rewrite the file
265+ executeCommand(testClass.project, " Insert Generated Tests" ) {
266+ editor.document.setText(generatedTestsCode)
267+ }
268+ unblockDocument(testClass.project, editor.document)
269+
270+ // after committing the document the `testClass` is invalid in PsiTree,
271+ // so we have to reload it from the corresponding `file`
272+ val testClassUpdated = (file as PsiClassOwner ).classes.first() // only one class in the file
273+
274+ // reformatting before creating reports due to
275+ // SarifReport requires the final version of the generated tests code
276+ runWriteCommandAction(testClassUpdated.project, " UtBot tests reformatting" , null , {
277+ reformat(model, file, testClassUpdated)
278+ })
279+ unblockDocument(testClassUpdated.project, editor.document)
280+
281+ // uploading formatted code
282+ val testsCodeWithTestReportFormatted =
283+ testsCodeWithTestReport.copy(generatedCode = file.text)
284+
285+ // creating and saving reports
286+ saveSarifAndTestReports(
287+ testClassUpdated,
288+ testCases,
289+ model,
290+ testsCodeWithTestReportFormatted,
291+ reportsCountDown
292+ )
212293
213- unblockDocument(testClass.project, editor.document)
214- // TODO: JIRA:1246 - display warnings if we rewrite the file
215- executeCommand(testClass.project, " Insert Generated Tests" ) {
216- editor.document.setText(generatedTestsCode)
294+ unblockDocument(testClassUpdated.project, editor.document)
295+ }
296+ }
217297 }
218- unblockDocument(testClass.project, editor.document)
219-
220- // after committing the document the `testClass` is invalid in PsiTree,
221- // so we have to reload it from the corresponding `file`
222- val testClassUpdated = (file as PsiClassOwner ).classes.first() // only one class in the file
223-
224- // reformatting before creating reports due to
225- // SarifReport requires the final version of the generated tests code
226- reformat(model, file, testClassUpdated)
227- unblockDocument(testClassUpdated.project, editor.document)
228-
229- // uploading formatted code
230- val testsCodeWithTestReportFormatted = testsCodeWithTestReport.copy(generatedCode = file.text)
231-
232- // creating and saving reports
233- saveSarifAndTestReports(testClassUpdated, testCases, model, testsCodeWithTestReportFormatted)
234-
235- unblockDocument(testClassUpdated.project, editor.document)
236298 }
299+ // Note that reportsCountDown.countDown() has to be called in every generator implementation to complete whole process
237300 else -> TODO (" Only model based code generator supported, but got: ${generator::class } " )
238301 }
239302 }
@@ -259,7 +322,8 @@ object TestGenerator {
259322 testClass : PsiClass ,
260323 testCases : List <UtTestCase >,
261324 model : GenerateTestsModel ,
262- testsCodeWithTestReport : TestsCodeWithTestReport
325+ testsCodeWithTestReport : TestsCodeWithTestReport ,
326+ reportsCountDown : CountDownLatch
263327 ) {
264328 val project = model.project
265329 val generatedTestsCode = testsCodeWithTestReport.generatedCode
@@ -276,6 +340,8 @@ object TestGenerator {
276340 message = " Cannot save Sarif report via generated tests: error occurred '${e.message} '" ,
277341 title = " Failed to save Sarif report"
278342 )
343+ } finally {
344+ reportsCountDown.countDown()
279345 }
280346
281347 try {
0 commit comments