Skip to content

Plugin Action stays locked even when previous generation is finished … #1175

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ 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

@Synchronized
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())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
Expand Down Expand Up @@ -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<BuildPaths?> { findPaths(model.srcClasses) }
.executeSynchronously()
?: return

val (buildDirs, classpath, classpathList, pluginJarsPath) = buildPaths

val testSetsByClass = mutableMapOf<PsiClass, RdTestGenerationResult>()
val psi2KClass = mutableMapOf<PsiClass, ClassId>()
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<BuildPaths?> { findPaths(model.srcClasses) }
.executeSynchronously()
?: return

val (buildDirs, classpath, classpathList, pluginJarsPath) = buildPaths

val testSetsByClass = mutableMapOf<PsiClass, RdTestGenerationResult>()
val psi2KClass = mutableMapOf<PsiClass, ClassId>()
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<Path> {
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<Settings>().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<Path> {
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<Settings>().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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -42,7 +42,7 @@ class GenerateTestsAction : AnAction(), UpdateInBackground {
}

override fun update(e: AnActionEvent) {
if (Lock.isLocked()) {
if (LockFile.isLocked()) {
e.presentation.isEnabled = false
return
}
Expand Down