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/LockFile.kt similarity index 95% rename from utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/Lock.kt rename to utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/LockFile.kt index b61f943355..e9ac6a3aa1 100644 --- 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/LockFile.kt @@ -13,7 +13,7 @@ private val lockFilePath = "$utbotHomePath/utbot.lock" private var currentLock : OutputStream? = null private val logger = KotlinLogging.logger {} -object Lock { +object LockFile { @Synchronized fun isLocked() = currentLock != null @@ -21,6 +21,7 @@ object Lock { fun lock(): Boolean { if (currentLock != null) return false return try { + Paths.get(utbotHomePath).toFile().mkdirs() currentLock = Paths.get(lockFilePath).outputStream(StandardOpenOption.CREATE_NEW, StandardOpenOption.DELETE_ON_CLOSE).also { it.write(DateFormat.getDateTimeInstance().format(System.currentTimeMillis()).toByteArray()) } 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 29a0ce18d0..a756253336 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 @@ -48,7 +48,7 @@ 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 +import org.utbot.framework.plugin.api.util.LockFile object UtTestsDialogProcessor { private val logger = KotlinLogging.logger {} @@ -122,150 +122,153 @@ 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 secondsTimeout = TimeUnit.MILLISECONDS.toSeconds(model.timeout) - - indicator.isIndeterminate = false - updateIndicator(indicator, ProgressRange.SOLVING, "Generate tests: read classes", 0.0) - - val buildPaths = ReadAction - .nonBlocking { findPaths(model.srcClasses) } - .executeSynchronously() - ?: return - - val (buildDirs, classpath, classpathList, pluginJarsPath) = buildPaths - - val testSetsByClass = mutableMapOf() - val psi2KClass = mutableMapOf() - var processedClasses = 0 - val totalClasses = model.srcClasses.size - - val proc = EngineProcess(lifetime, project) - - proc.setupUtContext(buildDirs + classpathList) - proc.createTestGenerator( - buildDirs, - classpath, - pluginJarsPath.joinToString(separator = File.pathSeparator), - JdkInfoService.provide() - ) { - ApplicationManager.getApplication().runReadAction(Computable { - indicator.isCanceled - }) - } + if (!LockFile.lock()) { + return + } + try { + val ldef = LifetimeDefinition() + ldef.terminateOnException { lifetime -> + val secondsTimeout = TimeUnit.MILLISECONDS.toSeconds(model.timeout) - for (srcClass in model.srcClasses) { - val (methods, className) = DumbService.getInstance(project) - .runReadActionInSmartMode(Computable { - val canonicalName = srcClass.canonicalName - val classId = proc.obtainClassId(canonicalName) - psi2KClass[srcClass] = classId - - val srcMethods = if (model.extractMembersFromSrcClasses) { - val chosenMethods = model.selectedMembers.filter { it.member is PsiMethod } - val chosenNestedClasses = - model.selectedMembers.mapNotNull { it.member as? PsiClass } - chosenMethods + chosenNestedClasses.flatMap { - it.extractClassMethodsIncludingNested(false) - } - } else { - srcClass.extractClassMethodsIncludingNested(false) - } - proc.findMethodsInClassMatchingSelected(classId, srcMethods) to srcClass.name - }) + indicator.isIndeterminate = false + updateIndicator(indicator, ProgressRange.SOLVING, "Generate tests: read classes", 0.0) - if (methods.isEmpty()) { - logger.error { "No methods matching selected found in class $className." } - continue + val buildPaths = ReadAction + .nonBlocking { findPaths(model.srcClasses) } + .executeSynchronously() + ?: return + + val (buildDirs, classpath, classpathList, pluginJarsPath) = buildPaths + + val testSetsByClass = mutableMapOf() + val psi2KClass = mutableMapOf() + var processedClasses = 0 + val totalClasses = model.srcClasses.size + + val proc = EngineProcess(lifetime, project) + + proc.setupUtContext(buildDirs + classpathList) + proc.createTestGenerator( + buildDirs, + classpath, + pluginJarsPath.joinToString(separator = File.pathSeparator), + JdkInfoService.provide() + ) { + ApplicationManager.getApplication().runReadAction(Computable { + indicator.isCanceled + }) } - if (totalClasses > 1) { - updateIndicator( - indicator, - ProgressRange.SOLVING, - "Generate test cases for class $className", - processedClasses.toDouble() / totalClasses - ) - } + for (srcClass in model.srcClasses) { + val (methods, className) = DumbService.getInstance(project) + .runReadActionInSmartMode(Computable { + val canonicalName = srcClass.canonicalName + val classId = proc.obtainClassId(canonicalName) + psi2KClass[srcClass] = classId + + val srcMethods = if (model.extractMembersFromSrcClasses) { + val chosenMethods = model.selectedMembers.filter { it.member is PsiMethod } + val chosenNestedClasses = + model.selectedMembers.mapNotNull { it.member as? PsiClass } + chosenMethods + chosenNestedClasses.flatMap { + it.extractClassMethodsIncludingNested(false) + } + } else { + srcClass.extractClassMethodsIncludingNested(false) + } + proc.findMethodsInClassMatchingSelected(classId, srcMethods) to srcClass.name + }) - // set timeout for concrete execution and for generated tests - UtSettings.concreteExecutionTimeoutInChildProcess = - model.hangingTestsTimeout.timeoutMs + if (methods.isEmpty()) { + logger.error { "No methods matching selected found in class $className." } + continue + } - UtSettings.useCustomJavaDocTags = - model.commentStyle == JavaDocCommentStyle.CUSTOM_JAVADOC_TAGS + if (totalClasses > 1) { + updateIndicator( + indicator, + ProgressRange.SOLVING, + "Generate test cases for class $className", + processedClasses.toDouble() / totalClasses + ) + } - UtSettings.enableSummariesGeneration = model.enableSummariesGeneration + // set timeout for concrete execution and for generated tests + UtSettings.concreteExecutionTimeoutInChildProcess = + model.hangingTestsTimeout.timeoutMs - val searchDirectory = ReadAction - .nonBlocking { - project.basePath?.let { Paths.get(it) } - ?: Paths.get(srcClass.containingFile.virtualFile.parent.path) - } - .executeSynchronously() + UtSettings.useCustomJavaDocTags = + model.commentStyle == JavaDocCommentStyle.CUSTOM_JAVADOC_TAGS - withStaticsSubstitutionRequired(true) { - val mockFrameworkInstalled = model.mockFramework?.isInstalled ?: true - - val rdGenerateResult = proc.generate( - mockFrameworkInstalled, - model.staticsMocking.isConfigured, - model.conflictTriggers, - methods, - model.mockStrategy, - model.chosenClassesToMockAlways, - model.timeout, - model.timeout, - true, - UtSettings.useFuzzing, - project.service().fuzzingValue, - searchDirectory.pathString - ) - - if (rdGenerateResult.notEmptyCases == 0) { - if (model.srcClasses.size > 1) { - logger.error { "Failed to generate any tests cases for class $className" } + UtSettings.enableSummariesGeneration = model.enableSummariesGeneration + + val searchDirectory = ReadAction + .nonBlocking { + project.basePath?.let { Paths.get(it) } + ?: Paths.get(srcClass.containingFile.virtualFile.parent.path) + } + .executeSynchronously() + + withStaticsSubstitutionRequired(true) { + val mockFrameworkInstalled = model.mockFramework?.isInstalled ?: true + + val rdGenerateResult = proc.generate( + mockFrameworkInstalled, + model.staticsMocking.isConfigured, + model.conflictTriggers, + methods, + model.mockStrategy, + model.chosenClassesToMockAlways, + model.timeout, + model.timeout, + true, + UtSettings.useFuzzing, + project.service().fuzzingValue, + searchDirectory.pathString + ) + + if (rdGenerateResult.notEmptyCases == 0) { + if (model.srcClasses.size > 1) { + logger.error { "Failed to generate any tests cases for class $className" } + } else { + showErrorDialogLater( + model.project, + errorMessage(className, secondsTimeout), + title = "Failed to generate unit tests for class $className" + ) + } } else { - showErrorDialogLater( - model.project, - errorMessage(className, secondsTimeout), - title = "Failed to generate unit tests for class $className" - ) + testSetsByClass[srcClass] = rdGenerateResult } - } else { - testSetsByClass[srcClass] = rdGenerateResult } + processedClasses++ } - processedClasses++ - } - if (processedClasses == 0) { - invokeLater { - Messages.showInfoMessage( - model.project, - "No methods for test generation were found among selected items", - "No methods found" - ) + if (processedClasses == 0) { + invokeLater { + Messages.showInfoMessage( + model.project, + "No methods for test generation were found among selected items", + "No methods found" + ) + } + return } - return - } - 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() + 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, indicator) - logger.info { "Generation complete" } + invokeLater { + generateTests(model, testSetsByClass, psi2KClass, proc, indicator) + logger.info { "Generation complete" } + } } + } finally { + LockFile.unlock() } } }).queue() 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 6fa5d9cd67..75950f489e 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,7 +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.framework.plugin.api.util.LockFile import org.utbot.intellij.plugin.models.packageName import org.utbot.intellij.plugin.ui.InvalidClassNotifier import org.utbot.intellij.plugin.util.isAbstract @@ -42,7 +42,7 @@ class GenerateTestsAction : AnAction(), UpdateInBackground { } override fun update(e: AnActionEvent) { - if (Lock.isLocked()) { + if (LockFile.isLocked()) { e.presentation.isEnabled = false return }