Skip to content

Commit c2673df

Browse files
IDE fatal error during tests generation #68 (#379)
1 parent 43693fb commit c2673df

File tree

1 file changed

+119
-53
lines changed
  • utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator

1 file changed

+119
-53
lines changed

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

Lines changed: 119 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,31 @@
11
package 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
321
import com.intellij.codeInsight.CodeInsightUtil
422
import com.intellij.codeInsight.FileModificationService
523
import com.intellij.ide.fileTemplates.FileTemplateManager
624
import com.intellij.ide.fileTemplates.FileTemplateUtil
725
import com.intellij.ide.fileTemplates.JavaTemplateUtil
826
import com.intellij.openapi.application.ApplicationManager
27+
import com.intellij.openapi.application.runReadAction
28+
import com.intellij.openapi.application.runWriteAction
929
import com.intellij.openapi.command.WriteCommandAction.runWriteCommandAction
1030
import com.intellij.openapi.command.executeCommand
1131
import com.intellij.openapi.components.service
@@ -21,51 +41,59 @@ import com.intellij.psi.codeStyle.JavaCodeStyleManager
2141
import com.intellij.psi.search.GlobalSearchScopesCore
2242
import com.intellij.testIntegration.TestIntegrationUtils
2343
import com.intellij.util.IncorrectOperationException
44+
import com.intellij.util.concurrency.AppExecutorUtil
2445
import com.intellij.util.io.exists
2546
import 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
2651
import org.jetbrains.kotlin.asJava.classes.KtUltraLightClass
2752
import org.jetbrains.kotlin.idea.core.ShortenReferences
2853
import org.jetbrains.kotlin.idea.core.getPackage
2954
import org.jetbrains.kotlin.idea.core.util.toPsiDirectory
3055
import org.jetbrains.kotlin.idea.util.ImportInsertHelperImpl
56+
import org.jetbrains.kotlin.idea.util.application.invokeLater
3157
import org.jetbrains.kotlin.name.FqName
3258
import org.jetbrains.kotlin.psi.KtClass
3359
import org.jetbrains.kotlin.psi.KtNamedFunction
3460
import org.jetbrains.kotlin.psi.KtPsiFactory
3561
import org.jetbrains.kotlin.psi.psiUtil.endOffset
3662
import org.jetbrains.kotlin.psi.psiUtil.startOffset
3763
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
64+
import org.utbot.framework.plugin.api.util.UtContext
65+
import org.utbot.framework.plugin.api.util.withUtContext
5066
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
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

6074
object 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

Comments
 (0)