From 7f8ab198448154093412bc88e6002933ea6a9a2d Mon Sep 17 00:00:00 2001 From: "Vassiliy.Kudryashov" Date: Fri, 1 Jul 2022 19:42:59 +0300 Subject: [PATCH 1/2] IDE fatal error during tests generation #68 --- .../plugin/generator/TestGenerator.kt | 166 ++++++++++++------ 1 file changed, 112 insertions(+), 54 deletions(-) diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/TestGenerator.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/TestGenerator.kt index ddbeb6f4b2..8f459638af 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/TestGenerator.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/TestGenerator.kt @@ -1,11 +1,28 @@ package org.utbot.intellij.plugin.generator +import org.utbot.common.HTML_LINE_SEPARATOR +import org.utbot.common.PathUtil.classFqnToPath +import org.utbot.common.PathUtil.toHtmlLinkTag +import org.utbot.common.appendHtmlLine +import org.utbot.framework.codegen.Import +import org.utbot.framework.codegen.ParametrizedTestSource +import org.utbot.framework.codegen.StaticImport +import org.utbot.framework.codegen.TestsCodeWithTestReport +import org.utbot.framework.codegen.model.ModelBasedTestCodeGenerator +import org.utbot.framework.codegen.model.constructor.tree.TestsGenerationReport +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.framework.plugin.api.UtTestCase +import org.utbot.intellij.plugin.sarif.SarifReportIdea +import org.utbot.intellij.plugin.sarif.SourceFindingStrategyIdea +import org.utbot.intellij.plugin.settings.Settings +import org.utbot.intellij.plugin.ui.utils.getOrCreateSarifReportsPath +import org.utbot.intellij.plugin.ui.utils.getOrCreateTestResourcesPath +import org.utbot.sarif.SarifReport import com.intellij.codeInsight.CodeInsightUtil import com.intellij.codeInsight.FileModificationService import com.intellij.ide.fileTemplates.FileTemplateManager import com.intellij.ide.fileTemplates.FileTemplateUtil import com.intellij.ide.fileTemplates.JavaTemplateUtil -import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.command.WriteCommandAction.runWriteCommandAction import com.intellij.openapi.command.executeCommand import com.intellij.openapi.components.service @@ -21,13 +38,17 @@ import com.intellij.psi.codeStyle.JavaCodeStyleManager import com.intellij.psi.search.GlobalSearchScopesCore import com.intellij.testIntegration.TestIntegrationUtils import com.intellij.util.IncorrectOperationException +import com.intellij.util.concurrency.AppExecutorUtil import com.intellij.util.io.exists import com.siyeh.ig.psiutils.ImportUtils +import java.nio.file.Paths +import java.util.concurrent.CountDownLatch import org.jetbrains.kotlin.asJava.classes.KtUltraLightClass import org.jetbrains.kotlin.idea.core.ShortenReferences import org.jetbrains.kotlin.idea.core.getPackage import org.jetbrains.kotlin.idea.core.util.toPsiDirectory import org.jetbrains.kotlin.idea.util.ImportInsertHelperImpl +import org.jetbrains.kotlin.idea.util.application.invokeLater import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtNamedFunction @@ -35,37 +56,36 @@ import org.jetbrains.kotlin.psi.KtPsiFactory import org.jetbrains.kotlin.psi.psiUtil.endOffset import org.jetbrains.kotlin.psi.psiUtil.startOffset import org.jetbrains.kotlin.scripting.resolve.classId -import org.utbot.common.HTML_LINE_SEPARATOR -import org.utbot.common.PathUtil.classFqnToPath -import org.utbot.common.PathUtil.toHtmlLinkTag -import org.utbot.common.appendHtmlLine -import org.utbot.framework.codegen.Import -import org.utbot.framework.codegen.ParametrizedTestSource -import org.utbot.framework.codegen.StaticImport -import org.utbot.framework.codegen.TestsCodeWithTestReport -import org.utbot.framework.codegen.model.ModelBasedTestCodeGenerator -import org.utbot.framework.codegen.model.constructor.tree.TestsGenerationReport -import org.utbot.framework.plugin.api.CodegenLanguage -import org.utbot.framework.plugin.api.UtTestCase import org.utbot.intellij.plugin.error.showErrorDialogLater -import org.utbot.intellij.plugin.sarif.SarifReportIdea -import org.utbot.intellij.plugin.sarif.SourceFindingStrategyIdea -import org.utbot.intellij.plugin.settings.Settings -import org.utbot.intellij.plugin.ui.* -import org.utbot.intellij.plugin.ui.utils.getOrCreateSarifReportsPath -import org.utbot.intellij.plugin.ui.utils.getOrCreateTestResourcesPath -import org.utbot.sarif.SarifReport -import java.nio.file.Paths +import org.utbot.intellij.plugin.ui.GenerateTestsModel +import org.utbot.intellij.plugin.ui.SarifReportNotifier +import org.utbot.intellij.plugin.ui.TestReportUrlOpeningListener +import org.utbot.intellij.plugin.ui.TestsReportNotifier +import org.utbot.intellij.plugin.ui.packageName object TestGenerator { - fun generateTests(model: GenerateTestsModel, testCases: Map>) { - generateTestsInternal(model, testCases) + private enum class Target {THREAD_POOL, READ_ACTION, WRITE_ACTION, EDT_LATER} + + private fun run(target: Target, runnable: Runnable) { + UtContext.currentContext()?.let { + when (target) { + THREAD_POOL -> AppExecutorUtil.getAppExecutorService().submit { + withUtContext(it) { + runnable.run() + } + } + READ_ACTION -> runReadAction { withUtContext(it) { runnable.run() } } + WRITE_ACTION -> runWriteAction { withUtContext(it) { runnable.run() } } + EDT_LATER -> invokeLater { withUtContext(it) { runnable.run() } } + } + } ?: error("No context in thread ${Thread.currentThread()}") } - private fun generateTestsInternal(model: GenerateTestsModel, testCasesByClass: Map>) { + fun generateTests(model: GenerateTestsModel, testCasesByClass: Map>) { val baseTestDirectory = model.testSourceRoot?.toPsiDirectory(model.project) ?: return val allTestPackages = getPackageDirectories(baseTestDirectory) + val latch = CountDownLatch(testCasesByClass.size) for (srcClass in testCasesByClass.keys) { val testCases = testCasesByClass[srcClass] ?: continue @@ -75,20 +95,37 @@ object TestGenerator { val testDirectory = allTestPackages[classPackageName] ?: baseTestDirectory val testClass = createTestClass(srcClass, testDirectory, model) ?: continue val file = testClass.containingFile - runWriteCommandAction(model.project, "Generate tests with UtBot", null, { - addTestMethodsAndSaveReports(testClass, file, testCases, model) + try { + addTestMethodsAndSaveReports(testClass, file, testCases, model, latch) + } catch (e: IncorrectOperationException) { + showCreatingClassError(model.project, createTestClassName(srcClass)) + } }) } catch (e: IncorrectOperationException) { showCreatingClassError(model.project, createTestClassName(srcClass)) } } + run(Target.READ_ACTION) { + val sarifReportsPath = model.testModule.getOrCreateSarifReportsPath(model.testSourceRoot) + run(Target.THREAD_POOL) { + waitForCountDown(latch, model, sarifReportsPath) + } + } + } - mergeSarifReports(model) + private fun waitForCountDown(latch: CountDownLatch, model: GenerateTestsModel, sarifReportsPath : Path) { + try { + if (!latch.await(5, TimeUnit.SECONDS)) { + run(THREAD_POOL) { waitForCountDown(latch, model, sarifReportsPath) } + } else { + mergeSarifReports(model, sarifReportsPath) + } + } catch (ignored: InterruptedException) { + } } - private fun mergeSarifReports(model: GenerateTestsModel) { - val sarifReportsPath = model.testModule.getOrCreateSarifReportsPath(model.testSourceRoot) + private fun mergeSarifReports(model: GenerateTestsModel, sarifReportsPath : Path) { val sarifReports = sarifReportsPath.toFile() .walkTopDown() .filter { it.extension == "sarif" } @@ -175,6 +212,7 @@ object TestGenerator { file: PsiFile, testCases: List, model: GenerateTestsModel, + reportsCountDown: CountDownLatch, ) { val selectedMethods = TestIntegrationUtils.extractClassMethods(testClass, false) val testFramework = model.testFramework @@ -205,33 +243,50 @@ object TestGenerator { when (generator) { is ModelBasedTestCodeGenerator -> { val editor = CodeInsightUtil.positionCursorAtLBrace(testClass.project, file, testClass) - val testsCodeWithTestReport = generator.generateAsStringWithTestReport(testCases) - val generatedTestsCode = testsCodeWithTestReport.generatedCode + //TODO Use PsiDocumentManager.getInstance(model.project).getDocument(file) + // if we don't want to open _all_ new files with tests in editor one-by-one + run(THREAD_POOL) { + val testsCodeWithTestReport = generator.generateAsStringWithTestReport(testCases) + val generatedTestsCode = testsCodeWithTestReport.generatedCode + run(EDT_LATER) { + run(WRITE_ACTION) { + unblockDocument(testClass.project, editor.document) + // TODO: JIRA:1246 - display warnings if we rewrite the file + executeCommand(testClass.project, "Insert Generated Tests") { + editor.document.setText(generatedTestsCode) + } + unblockDocument(testClass.project, editor.document) + + // after committing the document the `testClass` is invalid in PsiTree, + // so we have to reload it from the corresponding `file` + val testClassUpdated = (file as PsiClassOwner).classes.first() // only one class in the file + + // reformatting before creating reports due to + // SarifReport requires the final version of the generated tests code + runWriteCommandAction(testClassUpdated.project, "UtBot tests reformatting", null, { + reformat(model, file, testClassUpdated) + }) + unblockDocument(testClassUpdated.project, editor.document) + + // uploading formatted code + val testsCodeWithTestReportFormatted = + testsCodeWithTestReport.copy(generatedCode = file.text) + + // creating and saving reports + saveSarifAndTestReports( + testClassUpdated, + testCases, + model, + testsCodeWithTestReportFormatted, + reportsCountDown + ) - unblockDocument(testClass.project, editor.document) - // TODO: JIRA:1246 - display warnings if we rewrite the file - executeCommand(testClass.project, "Insert Generated Tests") { - editor.document.setText(generatedTestsCode) + unblockDocument(testClassUpdated.project, editor.document) + } + } } - unblockDocument(testClass.project, editor.document) - - // after committing the document the `testClass` is invalid in PsiTree, - // so we have to reload it from the corresponding `file` - val testClassUpdated = (file as PsiClassOwner).classes.first() // only one class in the file - - // reformatting before creating reports due to - // SarifReport requires the final version of the generated tests code - reformat(model, file, testClassUpdated) - unblockDocument(testClassUpdated.project, editor.document) - - // uploading formatted code - val testsCodeWithTestReportFormatted = testsCodeWithTestReport.copy(generatedCode = file.text) - - // creating and saving reports - saveSarifAndTestReports(testClassUpdated, testCases, model, testsCodeWithTestReportFormatted) - - unblockDocument(testClassUpdated.project, editor.document) } + //Note that reportsCountDown.countDown() has to be called in every generator implementation to complete whole process else -> TODO("Only model based code generator supported, but got: ${generator::class}") } } @@ -257,7 +312,8 @@ object TestGenerator { testClass: PsiClass, testCases: List, model: GenerateTestsModel, - testsCodeWithTestReport: TestsCodeWithTestReport + testsCodeWithTestReport: TestsCodeWithTestReport, + reportsCountDown: CountDownLatch ) { val project = model.project val generatedTestsCode = testsCodeWithTestReport.generatedCode @@ -274,6 +330,8 @@ object TestGenerator { message = "Cannot save Sarif report via generated tests: error occurred '${e.message}'", title = "Failed to save Sarif report" ) + } finally { + reportsCountDown.countDown() } try { From 006ae0810fa845bafb66536f1a9cb0b27f8e7a0a Mon Sep 17 00:00:00 2001 From: "Vassiliy.Kudryashov" Date: Mon, 4 Jul 2022 20:30:59 +0300 Subject: [PATCH 2/2] IDE fatal error during tests generation #68 Fix imports --- .../utbot/intellij/plugin/generator/TestGenerator.kt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/TestGenerator.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/TestGenerator.kt index 8f459638af..85f22eff0a 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/TestGenerator.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/TestGenerator.kt @@ -23,6 +23,9 @@ import com.intellij.codeInsight.FileModificationService import com.intellij.ide.fileTemplates.FileTemplateManager import com.intellij.ide.fileTemplates.FileTemplateUtil import com.intellij.ide.fileTemplates.JavaTemplateUtil +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.runReadAction +import com.intellij.openapi.application.runWriteAction import com.intellij.openapi.command.WriteCommandAction.runWriteCommandAction import com.intellij.openapi.command.executeCommand import com.intellij.openapi.components.service @@ -41,8 +44,10 @@ import com.intellij.util.IncorrectOperationException import com.intellij.util.concurrency.AppExecutorUtil import com.intellij.util.io.exists import com.siyeh.ig.psiutils.ImportUtils +import java.nio.file.Path import java.nio.file.Paths import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit import org.jetbrains.kotlin.asJava.classes.KtUltraLightClass import org.jetbrains.kotlin.idea.core.ShortenReferences import org.jetbrains.kotlin.idea.core.getPackage @@ -56,7 +61,10 @@ import org.jetbrains.kotlin.psi.KtPsiFactory import org.jetbrains.kotlin.psi.psiUtil.endOffset import org.jetbrains.kotlin.psi.psiUtil.startOffset import org.jetbrains.kotlin.scripting.resolve.classId +import org.utbot.framework.plugin.api.util.UtContext +import org.utbot.framework.plugin.api.util.withUtContext import org.utbot.intellij.plugin.error.showErrorDialogLater +import org.utbot.intellij.plugin.generator.TestGenerator.Target.* import org.utbot.intellij.plugin.ui.GenerateTestsModel import org.utbot.intellij.plugin.ui.SarifReportNotifier import org.utbot.intellij.plugin.ui.TestReportUrlOpeningListener @@ -106,9 +114,9 @@ object TestGenerator { showCreatingClassError(model.project, createTestClassName(srcClass)) } } - run(Target.READ_ACTION) { + run(READ_ACTION) { val sarifReportsPath = model.testModule.getOrCreateSarifReportsPath(model.testSourceRoot) - run(Target.THREAD_POOL) { + run(THREAD_POOL) { waitForCountDown(latch, model, sarifReportsPath) } }