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
8- import com.intellij.openapi.application.ApplicationManager
926import com.intellij.openapi.command.WriteCommandAction.runWriteCommandAction
1027import com.intellij.openapi.command.executeCommand
1128import com.intellij.openapi.components.service
@@ -21,51 +38,54 @@ import com.intellij.psi.codeStyle.JavaCodeStyleManager
2138import com.intellij.psi.search.GlobalSearchScopesCore
2239import com.intellij.testIntegration.TestIntegrationUtils
2340import com.intellij.util.IncorrectOperationException
41+ import com.intellij.util.concurrency.AppExecutorUtil
2442import com.intellij.util.io.exists
2543import com.siyeh.ig.psiutils.ImportUtils
44+ import java.nio.file.Paths
45+ import java.util.concurrent.CountDownLatch
2646import org.jetbrains.kotlin.asJava.classes.KtUltraLightClass
2747import org.jetbrains.kotlin.idea.core.ShortenReferences
2848import org.jetbrains.kotlin.idea.core.getPackage
2949import org.jetbrains.kotlin.idea.core.util.toPsiDirectory
3050import org.jetbrains.kotlin.idea.util.ImportInsertHelperImpl
51+ import org.jetbrains.kotlin.idea.util.application.invokeLater
3152import org.jetbrains.kotlin.name.FqName
3253import org.jetbrains.kotlin.psi.KtClass
3354import org.jetbrains.kotlin.psi.KtNamedFunction
3455import org.jetbrains.kotlin.psi.KtPsiFactory
3556import org.jetbrains.kotlin.psi.psiUtil.endOffset
3657import org.jetbrains.kotlin.psi.psiUtil.startOffset
3758import 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
5059import 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
5965
6066object 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()} " )
6382 }
6483
65- private fun generateTestsInternal (model : GenerateTestsModel , testCasesByClass : Map <PsiClass , List <UtTestCase >>) {
84+ fun generateTests (model : GenerateTestsModel , testCasesByClass : Map <PsiClass , List <UtTestCase >>) {
6685 val baseTestDirectory = model.testSourceRoot?.toPsiDirectory(model.project)
6786 ? : return
6887 val allTestPackages = getPackageDirectories(baseTestDirectory)
88+ val latch = CountDownLatch (testCasesByClass.size)
6989
7090 for (srcClass in testCasesByClass.keys) {
7191 val testCases = testCasesByClass[srcClass] ? : continue
@@ -75,20 +95,37 @@ object TestGenerator {
7595 val testDirectory = allTestPackages[classPackageName] ? : baseTestDirectory
7696 val testClass = createTestClass(srcClass, testDirectory, model) ? : continue
7797 val file = testClass.containingFile
78-
7998 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+ }
81104 })
82105 } catch (e: IncorrectOperationException ) {
83106 showCreatingClassError(model.project, createTestClassName(srcClass))
84107 }
85108 }
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+ }
86116
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+ }
88126 }
89127
90- private fun mergeSarifReports (model : GenerateTestsModel ) {
91- val sarifReportsPath = model.testModule.getOrCreateSarifReportsPath(model.testSourceRoot)
128+ private fun mergeSarifReports (model : GenerateTestsModel , sarifReportsPath : Path ) {
92129 val sarifReports = sarifReportsPath.toFile()
93130 .walkTopDown()
94131 .filter { it.extension == " sarif" }
@@ -175,6 +212,7 @@ object TestGenerator {
175212 file : PsiFile ,
176213 testCases : List <UtTestCase >,
177214 model : GenerateTestsModel ,
215+ reportsCountDown : CountDownLatch ,
178216 ) {
179217 val selectedMethods = TestIntegrationUtils .extractClassMethods(testClass, false )
180218 val testFramework = model.testFramework
@@ -205,33 +243,50 @@ object TestGenerator {
205243 when (generator) {
206244 is ModelBasedTestCodeGenerator -> {
207245 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+ )
210283
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+ }
215287 }
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)
234288 }
289+ // Note that reportsCountDown.countDown() has to be called in every generator implementation to complete whole process
235290 else -> TODO (" Only model based code generator supported, but got: ${generator::class } " )
236291 }
237292 }
@@ -257,7 +312,8 @@ object TestGenerator {
257312 testClass : PsiClass ,
258313 testCases : List <UtTestCase >,
259314 model : GenerateTestsModel ,
260- testsCodeWithTestReport : TestsCodeWithTestReport
315+ testsCodeWithTestReport : TestsCodeWithTestReport ,
316+ reportsCountDown : CountDownLatch
261317 ) {
262318 val project = model.project
263319 val generatedTestsCode = testsCodeWithTestReport.generatedCode
@@ -274,6 +330,8 @@ object TestGenerator {
274330 message = " Cannot save Sarif report via generated tests: error occurred '${e.message} '" ,
275331 title = " Failed to save Sarif report"
276332 )
333+ } finally {
334+ reportsCountDown.countDown()
277335 }
278336
279337 try {
0 commit comments