diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/Lock.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/Lock.kt new file mode 100644 index 0000000000..b61f943355 --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/Lock.kt @@ -0,0 +1,51 @@ +package org.utbot.framework.plugin.api.util + +import java.io.OutputStream +import java.nio.file.Paths +import java.nio.file.StandardOpenOption +import java.text.DateFormat +import kotlin.io.path.deleteIfExists +import kotlin.io.path.outputStream +import mu.KotlinLogging +import org.utbot.framework.utbotHomePath + +private val lockFilePath = "$utbotHomePath/utbot.lock" +private var currentLock : OutputStream? = null +private val logger = KotlinLogging.logger {} + +object Lock { + @Synchronized + fun isLocked() = currentLock != null + + @Synchronized + fun lock(): Boolean { + if (currentLock != null) return false + return try { + currentLock = Paths.get(lockFilePath).outputStream(StandardOpenOption.CREATE_NEW, StandardOpenOption.DELETE_ON_CLOSE).also { + it.write(DateFormat.getDateTimeInstance().format(System.currentTimeMillis()).toByteArray()) + } + logger.debug("Locked") + true + } catch (e: Exception) { + logger.error("Failed to lock") + false + } + } + + @Synchronized + fun unlock(): Boolean { + try { + val tmp = currentLock + if (tmp != null) { + tmp.close() + Paths.get(lockFilePath).deleteIfExists() + logger.debug("Unlocked") + currentLock = null + return true + } + } catch (ignored: Exception) { + logger.error("Failed to unlock") + } + return false + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineMain.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineMain.kt index 2d6532d827..9b1948544c 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineMain.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineMain.kt @@ -189,6 +189,7 @@ private fun EngineProcessModel.setup( val testPackageName: String? = params.testPackageName var hasWarnings = false val reports = testGenerationReports + if (reports.isEmpty()) return@measureExecutionForTermination GenerateTestReportResult("No tests were generated", null, true) val isMultiPackage = params.isMultiPackage val (notifyMessage, statistics) = if (reports.size == 1) { val report = reports.first() diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt index db404c574f..f651d568e1 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt @@ -16,6 +16,7 @@ import com.intellij.openapi.editor.Editor import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.fileTypes.FileType import com.intellij.openapi.module.Module +import com.intellij.openapi.progress.ProgressIndicator import com.intellij.openapi.project.DumbService import com.intellij.openapi.project.Project import com.intellij.openapi.util.Computable @@ -25,7 +26,6 @@ import com.intellij.psi.codeStyle.CodeStyleManager import com.intellij.psi.codeStyle.JavaCodeStyleManager import com.intellij.psi.search.GlobalSearchScopesCore import com.intellij.testIntegration.TestIntegrationUtils -import com.intellij.util.IncorrectOperationException import com.siyeh.ig.psiutils.ImportUtils import mu.KotlinLogging import org.jetbrains.kotlin.asJava.classes.KtUltraLightClass @@ -67,6 +67,7 @@ import org.utbot.intellij.plugin.util.RunConfigurationHelper import org.utbot.intellij.plugin.util.extractClassMethodsIncludingNested import org.utbot.sarif.SarifReport import java.nio.file.Path +import java.util.concurrent.CancellationException import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit @@ -85,7 +86,8 @@ object CodeGenerationController { model: GenerateTestsModel, classesWithTests: Map, psi2KClass: Map, - proc: EngineProcess + proc: EngineProcess, + indicator: ProgressIndicator ) { val baseTestDirectory = model.testSourceRoot?.toPsiDirectory(model.project) ?: return @@ -93,45 +95,64 @@ object CodeGenerationController { val latch = CountDownLatch(classesWithTests.size) val testFilesPointers = mutableListOf>() val utilClassListener = UtilClassListener() + var index = 0 for ((srcClass, generateResult) in classesWithTests) { + if (indicator.isCanceled) return val (count, testSetsId) = generateResult - if (count <= 0) continue + if (count <= 0) { + latch.countDown() + continue + } try { + UtTestsDialogProcessor.updateIndicator(indicator, UtTestsDialogProcessor.ProgressRange.CODEGEN, "Write test cases for class ${srcClass.name}", index.toDouble() / classesWithTests.size) val classPackageName = model.getTestClassPackageNameFor(srcClass) val testDirectory = allTestPackages[classPackageName] ?: baseTestDirectory val testClass = createTestClass(srcClass, testDirectory, model) ?: continue - val testFilePointer = SmartPointerManager.getInstance(model.project).createSmartPsiElementPointer(testClass.containingFile) + val testFilePointer = SmartPointerManager.getInstance(model.project) + .createSmartPsiElementPointer(testClass.containingFile) val cut = psi2KClass[srcClass] ?: error("Didn't find KClass instance for class ${srcClass.name}") runWriteCommandAction(model.project, "Generate tests with UtBot", null, { - try { - generateCodeAndReport(proc, testSetsId, srcClass, cut, testClass, testFilePointer, model, latch, utilClassListener) - testFilesPointers.add(testFilePointer) - } catch (e: IncorrectOperationException) { - logger.error { e } - showCreatingClassError(model.project, createTestClassName(srcClass)) - } + generateCodeAndReport( + proc, + testSetsId, + srcClass, + cut, + testClass, + testFilePointer, + model, + latch, + utilClassListener, + indicator + ) + testFilesPointers.add(testFilePointer) }) - } catch (e: IncorrectOperationException) { - logger.error { e } + } catch (e : CancellationException) { + throw e + } catch (e: Exception) { showCreatingClassError(model.project, createTestClassName(srcClass)) + } finally { + index++ } } - run(THREAD_POOL) { - waitForCountDown(latch) { - run(EDT_LATER) { - run(WRITE_ACTION) { - createUtilityClassIfNeed(utilClassListener, model, baseTestDirectory) - run(EDT_LATER) { + run(THREAD_POOL, indicator) { + waitForCountDown(latch, indicator = indicator) { + run(EDT_LATER, indicator) { + run(WRITE_ACTION, indicator) { + createUtilityClassIfNeed(utilClassListener, model, baseTestDirectory, indicator) + run(EDT_LATER, indicator) { proceedTestReport(proc, model) - run(THREAD_POOL) { + run(THREAD_POOL, indicator) { val sarifReportsPath = model.testModule.getOrCreateSarifReportsPath(model.testSourceRoot) + UtTestsDialogProcessor.updateIndicator(indicator, UtTestsDialogProcessor.ProgressRange.SARIF, "Merge Sarif reports", 0.75) mergeSarifReports(model, sarifReportsPath) if (model.runGeneratedTestsWithCoverage) { + UtTestsDialogProcessor.updateIndicator(indicator, UtTestsDialogProcessor.ProgressRange.SARIF, "Start tests with coverage", 0.95) RunConfigurationHelper.runTestsWithCoverage(model, testFilesPointers) } proc.forceTermination() + UtTestsDialogProcessor.updateIndicator(indicator, UtTestsDialogProcessor.ProgressRange.SARIF, "Start tests with coverage", 1.0) } } } @@ -148,6 +169,7 @@ object CodeGenerationController { showTestsReport(proc, model) } } catch (e: Exception) { + logger.error(e) { "Failed to save tests report" } showErrorDialogLater( model.project, message = "Cannot save tests generation report: error occurred '${e.message}'", @@ -158,7 +180,8 @@ object CodeGenerationController { private fun createUtilityClassIfNeed( utilClassListener: UtilClassListener, model: GenerateTestsModel, - baseTestDirectory: PsiDirectory + baseTestDirectory: PsiDirectory, + indicator: ProgressIndicator ) { val requiredUtilClassKind = utilClassListener.requiredUtilClassKind ?: return // no util class needed @@ -170,7 +193,8 @@ object CodeGenerationController { testDirectory = baseTestDirectory, utilClassKind = utilClassKind, existingUtilClass = existingUtilClass, - model = model + model = model, + indicator = indicator ) } } @@ -237,7 +261,8 @@ object CodeGenerationController { testDirectory: PsiDirectory, utilClassKind: UtilClassKind, existingUtilClass: PsiFile?, - model: GenerateTestsModel + model: GenerateTestsModel, + indicator: ProgressIndicator ) { val language = model.codegenLanguage @@ -247,7 +272,7 @@ object CodeGenerationController { // create util class file and put it into utils directory createNewUtilClass(utilClassDirectory, language, utilClassKind, model) } else { - overwriteUtilClass(existingUtilClass, utilClassKind, model) + overwriteUtilClass(existingUtilClass, utilClassKind, model, indicator) } val utUtilsClass = runReadAction { @@ -271,7 +296,8 @@ object CodeGenerationController { private fun overwriteUtilClass( existingUtilClass: PsiFile, utilClassKind: UtilClassKind, - model: GenerateTestsModel + model: GenerateTestsModel, + indicator: ProgressIndicator ): PsiFile { val utilsClassDocument = runReadAction { PsiDocumentManager @@ -282,8 +308,8 @@ object CodeGenerationController { val utUtilsText = utilClassKind.getUtilClassText(model.codegenLanguage) - run(EDT_LATER) { - run(WRITE_ACTION) { + run(EDT_LATER, indicator) { + run(WRITE_ACTION, indicator) { unblockDocument(model.project, utilsClassDocument) executeCommand { utilsClassDocument.setText(utUtilsText.replace("jdk.internal.misc", "sun.misc")) @@ -450,10 +476,10 @@ object CodeGenerationController { CodegenLanguage.KOTLIN -> KotlinFileType.INSTANCE } - private fun waitForCountDown(latch: CountDownLatch, timeout: Long = 5, timeUnit: TimeUnit = TimeUnit.SECONDS, action: Runnable) { + private fun waitForCountDown(latch: CountDownLatch, timeout: Long = 5, timeUnit: TimeUnit = TimeUnit.SECONDS, indicator : ProgressIndicator, action: Runnable) { try { if (!latch.await(timeout, timeUnit)) { - run(THREAD_POOL) { waitForCountDown(latch, timeout, timeUnit, action) } + run(THREAD_POOL, indicator) { waitForCountDown(latch, timeout, timeUnit, indicator, action) } } else { action.run() } @@ -559,42 +585,61 @@ object CodeGenerationController { filePointer: SmartPsiElementPointer, model: GenerateTestsModel, reportsCountDown: CountDownLatch, - utilClassListener: UtilClassListener + utilClassListener: UtilClassListener, + indicator: ProgressIndicator ) { val classMethods = srcClass.extractClassMethodsIncludingNested(false) - val paramNames = DumbService.getInstance(model.project) - .runReadActionInSmartMode(Computable { proc.findMethodParamNames(classUnderTest, classMethods) }) + val paramNames = try { + DumbService.getInstance(model.project) + .runReadActionInSmartMode(Computable { proc.findMethodParamNames(classUnderTest, classMethods) }) + } catch (e: Exception) { + logger.warn(e) { "Cannot find method param names for ${classUnderTest.name}" } + reportsCountDown.countDown() + return + } val testPackageName = testClass.packageName val editor = CodeInsightUtil.positionCursorAtLBrace(testClass.project, filePointer.containingFile, testClass) //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 (generatedTestsCode, utilClassKind) = proc.render( - testSetsId, - classUnderTest, - paramNames.toMutableMap(), - generateUtilClassFile = true, - model.testFramework, - model.mockFramework, - model.staticsMocking, - model.forceStaticMocking, - model.generateWarningsForStaticMocking, - model.codegenLanguage, - model.parametrizedTestSource, - model.runtimeExceptionTestsBehaviour, - model.hangingTestsTimeout, - enableTestsTimeout = true, - testPackageName - ) + run(THREAD_POOL, indicator) { + val (generatedTestsCode, utilClassKind) = try { + proc.render( + testSetsId, + classUnderTest, + paramNames.toMutableMap(), + generateUtilClassFile = true, + model.testFramework, + model.mockFramework, + model.staticsMocking, + model.forceStaticMocking, + model.generateWarningsForStaticMocking, + model.codegenLanguage, + model.parametrizedTestSource, + model.runtimeExceptionTestsBehaviour, + model.hangingTestsTimeout, + enableTestsTimeout = true, + testPackageName + ) + } catch (e: Exception) { + logger.warn(e) { "Cannot render test class ${testClass.name}" } + reportsCountDown.countDown() + return@run + } utilClassListener.onTestClassGenerated(utilClassKind) - 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.replace("jdk.internal.misc.Unsafe", "sun.misc.Unsafe")) + run(EDT_LATER, indicator) { + run(WRITE_ACTION, indicator) { + try { + 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.replace("jdk.internal.misc.Unsafe", "sun.misc.Unsafe")) + } + unblockDocument(testClass.project, editor.document) + } catch (e: Exception) { + logger.error(e) { "Cannot save document for ${testClass.name}" } + reportsCountDown.countDown() + return@run } - 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` @@ -602,28 +647,31 @@ object CodeGenerationController { // reformatting before creating reports due to // SarifReport requires the final version of the generated tests code - run(THREAD_POOL) { + run(THREAD_POOL, indicator) { // IntentionHelper(model.project, editor, filePointer).applyIntentions() - run(EDT_LATER) { - runWriteCommandAction(filePointer.project, "UtBot tests reformatting", null, { - reformat(model, filePointer, testClassUpdated) - }) - unblockDocument(testClassUpdated.project, editor.document) - + run(EDT_LATER, indicator) { + try { + runWriteCommandAction(filePointer.project, "UtBot tests reformatting", null, { + reformat(model, filePointer, testClassUpdated) + }) + unblockDocument(testClassUpdated.project, editor.document) + } catch (e : Exception) { + logger.error(e) { "Cannot save Sarif report for ${testClassUpdated.name}" } + } // uploading formatted code val file = filePointer.containingFile - run(THREAD_POOL) { - saveSarifReport( - proc, - testSetsId, - testClassUpdated, - classUnderTest, - model, - file?.text ?: generatedTestsCode - ) - reportsCountDown.countDown() - } + saveSarifReport( + proc, + testSetsId, + testClassUpdated, + classUnderTest, + model, + reportsCountDown, + file?.text ?: generatedTestsCode, + indicator + ) + unblockDocument(testClassUpdated.project, editor.document) } } } @@ -656,13 +704,15 @@ object CodeGenerationController { testClass: PsiClass, testClassId: ClassId, model: GenerateTestsModel, + reportsCountDown: CountDownLatch, generatedTestsCode: String, + indicator: ProgressIndicator ) { val project = model.project try { // saving sarif report - SarifReportIdea.createAndSave(proc, testSetsId, testClassId, model, generatedTestsCode, testClass) + SarifReportIdea.createAndSave(proc, testSetsId, testClassId, model, generatedTestsCode, testClass, reportsCountDown, indicator) } catch (e: Exception) { logger.error(e) { "error in saving sarif report"} showErrorDialogLater( diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/UtTestsDialogProcessor.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/UtTestsDialogProcessor.kt index 3a7f291e1f..29a0ce18d0 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/UtTestsDialogProcessor.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/UtTestsDialogProcessor.kt @@ -19,7 +19,6 @@ import com.intellij.psi.PsiClass import com.intellij.psi.PsiMethod import com.intellij.refactoring.util.classMembers.MemberInfo import com.intellij.task.ProjectTaskManager -import com.intellij.util.concurrency.AppExecutorUtil import com.intellij.util.containers.nullize import com.intellij.util.io.exists import com.jetbrains.rd.util.lifetime.LifetimeDefinition @@ -49,11 +48,26 @@ import java.nio.file.Path import java.nio.file.Paths import java.util.concurrent.TimeUnit import kotlin.io.path.pathString +import org.utbot.framework.plugin.api.util.Lock object UtTestsDialogProcessor { - private val logger = KotlinLogging.logger {} + enum class ProgressRange(val from : Double, val to: Double) { + SOLVING(from = 0.0, to = 0.7), + CODEGEN(from = 0.7, to = 0.9), + SARIF(from = 0.9, to = 1.0) + } + + fun updateIndicator(indicator: ProgressIndicator, range : ProgressRange, text: String? = null, fraction: Double? = null) { + invokeLater { + text?.let { indicator.text = it } + fraction?.let { indicator.fraction = indicator.fraction.coerceAtLeast(range.from + (range.to - range.from) * fraction.coerceIn(0.0, 1.0)) } + logger.debug("Phase ${indicator.text} with progress ${String.format("%.2f",indicator.fraction)}") + } + } + + fun createDialogAndGenerateTests( project: Project, srcClasses: Set, @@ -108,24 +122,20 @@ object UtTestsDialogProcessor { promise.onSuccess { if (it.hasErrors() || it.isAborted) return@onSuccess + if (!Lock.lock()) { + return@onSuccess + } (object : Task.Backgroundable(project, "Generate tests") { override fun run(indicator: ProgressIndicator) { val ldef = LifetimeDefinition() + ldef.onTermination { Lock.unlock() } ldef.terminateOnException { lifetime -> - val startTime = System.currentTimeMillis() val secondsTimeout = TimeUnit.MILLISECONDS.toSeconds(model.timeout) - val totalTimeout = model.timeout * model.srcClasses.size indicator.isIndeterminate = false - indicator.text = "Generate tests: read classes" - - val timerHandler = - AppExecutorUtil.getAppScheduledExecutorService().scheduleWithFixedDelay({ - indicator.fraction = - (System.currentTimeMillis() - startTime).toDouble() / totalTimeout - }, 0, 500, TimeUnit.MILLISECONDS) + updateIndicator(indicator, ProgressRange.SOLVING, "Generate tests: read classes", 0.0) val buildPaths = ReadAction .nonBlocking { findPaths(model.srcClasses) } @@ -178,10 +188,13 @@ object UtTestsDialogProcessor { continue } - indicator.text = "Generate test cases for class $className" if (totalClasses > 1) { - indicator.fraction = - indicator.fraction.coerceAtLeast(0.9 * processedClasses / totalClasses) + updateIndicator( + indicator, + ProgressRange.SOLVING, + "Generate test cases for class $className", + processedClasses.toDouble() / totalClasses + ) } // set timeout for concrete execution and for generated tests @@ -231,8 +244,6 @@ object UtTestsDialogProcessor { } else { testSetsByClass[srcClass] = rdGenerateResult } - - timerHandler.cancel(true) } processedClasses++ } @@ -247,14 +258,13 @@ object UtTestsDialogProcessor { } return } - - indicator.fraction = indicator.fraction.coerceAtLeast(0.9) - indicator.text = "Generate code for tests" + updateIndicator(indicator, ProgressRange.CODEGEN, "Generate code for tests", 0.0) // Commented out to generate tests for collected executions even if action was canceled. // indicator.checkCanceled() invokeLater { - generateTests(model, testSetsByClass, psi2KClass, proc) + generateTests(model, testSetsByClass, psi2KClass, proc, indicator) + logger.info { "Generation complete" } } } } diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/sarif/SarifReportIdea.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/sarif/SarifReportIdea.kt index b9c21a5d97..cd46beff64 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/sarif/SarifReportIdea.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/sarif/SarifReportIdea.kt @@ -2,15 +2,21 @@ package org.utbot.intellij.plugin.sarif import com.intellij.openapi.application.WriteAction import com.intellij.psi.PsiClass +import com.intellij.openapi.progress.ProgressIndicator import org.utbot.common.PathUtil.classFqnToPath +import org.utbot.intellij.plugin.ui.utils.getOrCreateSarifReportsPath +import com.intellij.openapi.vfs.VfsUtil +import java.util.concurrent.CountDownLatch +import mu.KotlinLogging import org.utbot.framework.plugin.api.ClassId +import org.utbot.intellij.plugin.generator.UtTestsDialogProcessor import org.utbot.intellij.plugin.models.GenerateTestsModel import org.utbot.intellij.plugin.process.EngineProcess -import org.utbot.intellij.plugin.ui.utils.getOrCreateSarifReportsPath +import org.utbot.intellij.plugin.util.IntelliJApiHelper import java.nio.file.Path object SarifReportIdea { - + private val logger = KotlinLogging.logger {} /** * Creates the SARIF report by calling the SarifReport.createReport(), * saves it to test resources directory and notifies the user about the creation. @@ -21,14 +27,25 @@ object SarifReportIdea { classId: ClassId, model: GenerateTestsModel, generatedTestsCode: String, - psiClass: PsiClass + psiClass: PsiClass, + reportsCountDown: CountDownLatch, + indicator: ProgressIndicator ) { + UtTestsDialogProcessor.updateIndicator(indicator, UtTestsDialogProcessor.ProgressRange.SARIF, "Generate SARIF report for ${classId.name}", .5) // building the path to the report file val classFqn = classId.name val (sarifReportsPath, sourceFinding) = WriteAction.computeAndWait, Exception> { model.testModule.getOrCreateSarifReportsPath(model.testSourceRoot) to SourceFindingStrategyIdea(psiClass) } val reportFilePath = sarifReportsPath.resolve("${classFqnToPath(classFqn)}Report.sarif") - proc.writeSarif(reportFilePath, testSetsId, generatedTestsCode, sourceFinding) + IntelliJApiHelper.run(IntelliJApiHelper.Target.THREAD_POOL, indicator) { + try { + proc.writeSarif(reportFilePath, testSetsId, generatedTestsCode, sourceFinding) + } catch (e: Exception) { + logger.error { e } + } finally { + reportsCountDown.countDown() + } + } } } diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/actions/GenerateTestsAction.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/actions/GenerateTestsAction.kt index d6e05ee4db..ed846f544f 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/actions/GenerateTestsAction.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/actions/GenerateTestsAction.kt @@ -26,6 +26,7 @@ import org.utbot.intellij.plugin.util.isVisible import java.util.* import org.jetbrains.kotlin.j2k.getContainingClass import org.jetbrains.kotlin.utils.addIfNotNull +import org.utbot.framework.plugin.api.util.Lock import org.utbot.intellij.plugin.models.packageName import org.utbot.intellij.plugin.ui.InvalidClassNotifier import org.utbot.intellij.plugin.util.isAbstract @@ -41,6 +42,10 @@ class GenerateTestsAction : AnAction(), UpdateInBackground { } override fun update(e: AnActionEvent) { + if (Lock.isLocked()) { + e.presentation.isEnabled = false + return + } if (e.place == ActionPlaces.POPUP) { e.presentation.text = "Tests with UnitTestBot..." } diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/IntelliJApiHelper.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/IntelliJApiHelper.kt index 68d50c3079..1b43fe54f8 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/IntelliJApiHelper.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/IntelliJApiHelper.kt @@ -4,28 +4,36 @@ import com.intellij.ide.plugins.PluginManagerCore import com.intellij.openapi.application.runReadAction import com.intellij.openapi.application.runWriteAction import com.intellij.openapi.extensions.PluginId +import com.intellij.openapi.progress.ProgressIndicator import com.intellij.openapi.project.Project import com.intellij.util.PlatformUtils import com.intellij.util.ReflectionUtil import com.intellij.util.concurrency.AppExecutorUtil +import mu.KotlinLogging import org.jetbrains.kotlin.idea.util.application.invokeLater /** * This object is required to encapsulate Android API usage and grant safe access to it. */ object IntelliJApiHelper { - + private val logger = KotlinLogging.logger {} enum class Target { THREAD_POOL, READ_ACTION, WRITE_ACTION, EDT_LATER } - fun run(target: Target, runnable: Runnable) { - when (target) { - Target.THREAD_POOL -> AppExecutorUtil.getAppExecutorService().submit { + fun run(target: Target, indicator: ProgressIndicator? = null, runnable: Runnable) { + if (indicator != null && indicator.isCanceled) return + val wrapper = Runnable { + try { runnable.run() + } catch (e: Exception) { + logger.error(e) { target.toString() } + throw e } - - Target.READ_ACTION -> runReadAction { runnable.run() } - Target.WRITE_ACTION -> runWriteAction { runnable.run() } - Target.EDT_LATER -> invokeLater { runnable.run() } + } + when (target) { + Target.THREAD_POOL -> AppExecutorUtil.getAppExecutorService().submit { wrapper.run() } + Target.READ_ACTION -> runReadAction { wrapper.run() } + Target.WRITE_ACTION -> runWriteAction { wrapper.run() } + Target.EDT_LATER -> invokeLater { wrapper.run() } } }