Skip to content

Commit a088bda

Browse files
IDE fatal error during tests generation #68
1 parent 4c6d73b commit a088bda

File tree

1 file changed

+99
-35
lines changed
  • utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator

1 file changed

+99
-35
lines changed

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

Lines changed: 99 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import com.intellij.codeInsight.FileModificationService
2323
import com.intellij.ide.fileTemplates.FileTemplateManager
2424
import com.intellij.ide.fileTemplates.FileTemplateUtil
2525
import com.intellij.ide.fileTemplates.JavaTemplateUtil
26+
import com.intellij.openapi.application.runReadAction
27+
import com.intellij.openapi.application.runWriteAction
2628
import com.intellij.openapi.command.WriteCommandAction.runWriteCommandAction
2729
import com.intellij.openapi.command.executeCommand
2830
import com.intellij.openapi.components.service
@@ -44,39 +46,62 @@ import com.intellij.psi.codeStyle.JavaCodeStyleManager
4446
import com.intellij.psi.search.GlobalSearchScopesCore
4547
import com.intellij.testIntegration.TestIntegrationUtils
4648
import com.intellij.util.IncorrectOperationException
49+
import com.intellij.util.concurrency.AppExecutorUtil
4750
import com.intellij.util.io.exists
4851
import com.siyeh.ig.psiutils.ImportUtils
52+
import java.nio.file.Path
4953
import java.nio.file.Paths
54+
import java.util.concurrent.CountDownLatch
55+
import java.util.concurrent.TimeUnit
5056
import org.jetbrains.kotlin.asJava.classes.KtUltraLightClass
5157
import org.jetbrains.kotlin.idea.core.ShortenReferences
5258
import org.jetbrains.kotlin.idea.core.getPackage
5359
import org.jetbrains.kotlin.idea.core.util.toPsiDirectory
5460
import org.jetbrains.kotlin.idea.util.ImportInsertHelperImpl
61+
import org.jetbrains.kotlin.idea.util.application.invokeLater
5562
import org.jetbrains.kotlin.name.FqName
5663
import org.jetbrains.kotlin.psi.KtClass
5764
import org.jetbrains.kotlin.psi.KtNamedFunction
5865
import org.jetbrains.kotlin.psi.KtPsiFactory
5966
import org.jetbrains.kotlin.psi.psiUtil.endOffset
6067
import org.jetbrains.kotlin.psi.psiUtil.startOffset
6168
import org.jetbrains.kotlin.scripting.resolve.classId
69+
import org.utbot.framework.plugin.api.util.UtContext
70+
import org.utbot.framework.plugin.api.util.withUtContext
6271
import org.utbot.intellij.plugin.error.showErrorDialogLater
6372
import org.utbot.intellij.plugin.ui.GenerateTestsModel
6473
import org.utbot.intellij.plugin.ui.SarifReportNotifier
6574
import org.utbot.intellij.plugin.ui.TestReportUrlOpeningListener
6675
import org.utbot.intellij.plugin.ui.TestsReportNotifier
6776
import org.utbot.intellij.plugin.ui.packageName
77+
import org.utbot.intellij.plugin.generator.TestGenerator.Target.THREAD_POOL
78+
import org.utbot.intellij.plugin.generator.TestGenerator.Target.EDT_LATER
79+
import org.utbot.intellij.plugin.generator.TestGenerator.Target.READ_ACTION
80+
import org.utbot.intellij.plugin.generator.TestGenerator.Target.WRITE_ACTION
6881

6982
object TestGenerator {
70-
fun generateTests(model: GenerateTestsModel, testCases: Map<PsiClass, List<UtTestCase>>) {
71-
runWriteCommandAction(model.project, "Generate tests with UtBot", null, {
72-
generateTestsInternal(model, testCases)
73-
})
83+
private enum class Target {THREAD_POOL, READ_ACTION, WRITE_ACTION, EDT_LATER}
84+
85+
private fun run(target: Target, runnable: Runnable) {
86+
UtContext.currentContext()?.let {
87+
when (target) {
88+
THREAD_POOL -> AppExecutorUtil.getAppExecutorService().submit{
89+
withUtContext(it) {
90+
runnable.run()
91+
}
92+
}
93+
READ_ACTION -> runReadAction { withUtContext(it) { runnable.run() } }
94+
WRITE_ACTION -> runWriteAction { withUtContext(it) { runnable.run() } }
95+
EDT_LATER -> invokeLater { withUtContext(it) { runnable.run() } }
96+
}
97+
} ?: error("No context in thread ${Thread.currentThread()}")
7498
}
7599

76-
private fun generateTestsInternal(model: GenerateTestsModel, testCasesByClass: Map<PsiClass, List<UtTestCase>>) {
100+
fun generateTests(model: GenerateTestsModel, testCasesByClass: Map<PsiClass, List<UtTestCase>>) {
77101
val baseTestDirectory = model.testSourceRoot?.toPsiDirectory(model.project)
78102
?: return
79103
val allTestPackages = getPackageDirectories(baseTestDirectory)
104+
val latch = CountDownLatch(testCasesByClass.size)
80105

81106
for (srcClass in testCasesByClass.keys) {
82107
val testCases = testCasesByClass[srcClass] ?: continue
@@ -86,18 +111,37 @@ object TestGenerator {
86111
val testDirectory = allTestPackages[classPackageName] ?: baseTestDirectory
87112
val testClass = createTestClass(srcClass, testDirectory, model) ?: continue
88113
val file = testClass.containingFile
89-
90-
addTestMethodsAndSaveReports(testClass, file, testCases, model)
114+
runWriteCommandAction(model.project, "Generate tests with UtBot", null, {
115+
try {
116+
addTestMethodsAndSaveReports(testClass, file, testCases, model, latch)
117+
} catch (e: IncorrectOperationException) {
118+
showCreatingClassError(model.project, createTestClassName(srcClass))
119+
}
120+
})
91121
} catch (e: IncorrectOperationException) {
92122
showCreatingClassError(model.project, createTestClassName(srcClass))
93123
}
94124
}
125+
run(READ_ACTION) {
126+
val sarifReportsPath = model.testModule.getOrCreateSarifReportsPath(model.testSourceRoot)
127+
run(THREAD_POOL) {
128+
waitForCountDown(latch, model, sarifReportsPath)
129+
}
130+
}
131+
}
95132

96-
mergeSarifReports(model)
133+
private fun waitForCountDown(latch: CountDownLatch, model: GenerateTestsModel, sarifReportsPath : Path) {
134+
try {
135+
if (!latch.await(5, TimeUnit.SECONDS)) {
136+
run(THREAD_POOL) { waitForCountDown(latch, model, sarifReportsPath) }
137+
} else {
138+
mergeSarifReports(model, sarifReportsPath)
139+
}
140+
} catch (ignored: InterruptedException) {
141+
}
97142
}
98143

99-
private fun mergeSarifReports(model: GenerateTestsModel) {
100-
val sarifReportsPath = model.testModule.getOrCreateSarifReportsPath(model.testSourceRoot)
144+
private fun mergeSarifReports(model: GenerateTestsModel, sarifReportsPath : Path) {
101145
val sarifReports = sarifReportsPath.toFile()
102146
.walkTopDown()
103147
.filter { it.extension == "sarif" }
@@ -183,6 +227,7 @@ object TestGenerator {
183227
file: PsiFile,
184228
testCases: List<UtTestCase>,
185229
model: GenerateTestsModel,
230+
reportsCountDown: CountDownLatch,
186231
) {
187232
val selectedMethods = TestIntegrationUtils.extractClassMethods(testClass, false)
188233
val testFramework = model.testFramework
@@ -213,32 +258,48 @@ object TestGenerator {
213258
when (generator) {
214259
is ModelBasedTestCodeGenerator -> {
215260
val editor = CodeInsightUtil.positionCursorAtLBrace(testClass.project, file, testClass)
216-
val testsCodeWithTestReport = generator.generateAsStringWithTestReport(testCases)
217-
val generatedTestsCode = testsCodeWithTestReport.generatedCode
261+
//TODO Use PsiDocumentManager.getInstance(model.project).getDocument(file)
262+
// if we don't want to open _all_ new files with tests in editor one-by-one
263+
run(THREAD_POOL) {
264+
val testsCodeWithTestReport = generator.generateAsStringWithTestReport(testCases)
265+
val generatedTestsCode = testsCodeWithTestReport.generatedCode
266+
run(EDT_LATER) {
267+
run(WRITE_ACTION) {
268+
unblockDocument(testClass.project, editor.document)
269+
// TODO: JIRA:1246 - display warnings if we rewrite the file
270+
executeCommand(testClass.project, "Insert Generated Tests") {
271+
editor.document.setText(generatedTestsCode)
272+
}
273+
unblockDocument(testClass.project, editor.document)
274+
275+
// after committing the document the `testClass` is invalid in PsiTree,
276+
// so we have to reload it from the corresponding `file`
277+
val testClassUpdated = (file as PsiClassOwner).classes.first() // only one class in the file
278+
279+
// reformatting before creating reports due to
280+
// SarifReport requires the final version of the generated tests code
281+
runWriteCommandAction(testClassUpdated.project, "UtBot tests reformatting", null, {
282+
reformat(model, file, testClassUpdated)
283+
})
284+
unblockDocument(testClassUpdated.project, editor.document)
285+
286+
// uploading formatted code
287+
val testsCodeWithTestReportFormatted =
288+
testsCodeWithTestReport.copy(generatedCode = file.text)
289+
290+
// creating and saving reports
291+
saveSarifAndTestReports(
292+
testClassUpdated,
293+
testCases,
294+
model,
295+
testsCodeWithTestReportFormatted,
296+
reportsCountDown
297+
)
218298

219-
unblockDocument(testClass.project, editor.document)
220-
// TODO: JIRA:1246 - display warnings if we rewrite the file
221-
executeCommand(testClass.project, "Insert Generated Tests") {
222-
editor.document.setText(generatedTestsCode)
299+
unblockDocument(testClassUpdated.project, editor.document)
300+
}
301+
}
223302
}
224-
unblockDocument(testClass.project, editor.document)
225-
226-
// after committing the document the `testClass` is invalid in PsiTree,
227-
// so we have to reload it from the corresponding `file`
228-
val testClassUpdated = (file as PsiClassOwner).classes.first() // only one class in the file
229-
230-
// reformatting before creating reports due to
231-
// SarifReport requires the final version of the generated tests code
232-
reformat(model, file, testClassUpdated)
233-
unblockDocument(testClassUpdated.project, editor.document)
234-
235-
// uploading formatted code
236-
val testsCodeWithTestReportFormatted = testsCodeWithTestReport.copy(generatedCode = file.text)
237-
238-
// creating and saving reports
239-
saveSarifAndTestReports(testClassUpdated, testCases, model, testsCodeWithTestReportFormatted)
240-
241-
unblockDocument(testClassUpdated.project, editor.document)
242303
}
243304
else -> TODO("Only model based code generator supported, but got: ${generator::class}")
244305
}
@@ -265,7 +326,8 @@ object TestGenerator {
265326
testClass: PsiClass,
266327
testCases: List<UtTestCase>,
267328
model: GenerateTestsModel,
268-
testsCodeWithTestReport: TestsCodeWithTestReport
329+
testsCodeWithTestReport: TestsCodeWithTestReport,
330+
reportsCountDown: CountDownLatch
269331
) {
270332
val project = model.project
271333
val generatedTestsCode = testsCodeWithTestReport.generatedCode
@@ -282,6 +344,8 @@ object TestGenerator {
282344
message = "Cannot save Sarif report via generated tests: error occurred '${e.message}'",
283345
title = "Failed to save Sarif report"
284346
)
347+
} finally {
348+
reportsCountDown.countDown()
285349
}
286350

287351
try {

0 commit comments

Comments
 (0)