Skip to content

Commit 767e65c

Browse files
Vassiliy kudryashov/919 simultaneous test generation crashes one of them (#1164)
Simultaneous test generation crashes one of them #919
1 parent 8043e90 commit 767e65c

File tree

7 files changed

+250
-108
lines changed

7 files changed

+250
-108
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package org.utbot.framework.plugin.api.util
2+
3+
import java.io.OutputStream
4+
import java.nio.file.Paths
5+
import java.nio.file.StandardOpenOption
6+
import java.text.DateFormat
7+
import kotlin.io.path.deleteIfExists
8+
import kotlin.io.path.outputStream
9+
import mu.KotlinLogging
10+
import org.utbot.framework.utbotHomePath
11+
12+
private val lockFilePath = "$utbotHomePath/utbot.lock"
13+
private var currentLock : OutputStream? = null
14+
private val logger = KotlinLogging.logger {}
15+
16+
object Lock {
17+
@Synchronized
18+
fun isLocked() = currentLock != null
19+
20+
@Synchronized
21+
fun lock(): Boolean {
22+
if (currentLock != null) return false
23+
return try {
24+
currentLock = Paths.get(lockFilePath).outputStream(StandardOpenOption.CREATE_NEW, StandardOpenOption.DELETE_ON_CLOSE).also {
25+
it.write(DateFormat.getDateTimeInstance().format(System.currentTimeMillis()).toByteArray())
26+
}
27+
logger.debug("Locked")
28+
true
29+
} catch (e: Exception) {
30+
logger.error("Failed to lock")
31+
false
32+
}
33+
}
34+
35+
@Synchronized
36+
fun unlock(): Boolean {
37+
try {
38+
val tmp = currentLock
39+
if (tmp != null) {
40+
tmp.close()
41+
Paths.get(lockFilePath).deleteIfExists()
42+
logger.debug("Unlocked")
43+
currentLock = null
44+
return true
45+
}
46+
} catch (ignored: Exception) {
47+
logger.error("Failed to unlock")
48+
}
49+
return false
50+
}
51+
}

utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineMain.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ private fun EngineProcessModel.setup(
189189
val testPackageName: String? = params.testPackageName
190190
var hasWarnings = false
191191
val reports = testGenerationReports
192+
if (reports.isEmpty()) return@measureExecutionForTermination GenerateTestReportResult("No tests were generated", null, true)
192193
val isMultiPackage = params.isMultiPackage
193194
val (notifyMessage, statistics) = if (reports.size == 1) {
194195
val report = reports.first()

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

Lines changed: 126 additions & 76 deletions
Large diffs are not rendered by default.

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

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import com.intellij.psi.PsiClass
1919
import com.intellij.psi.PsiMethod
2020
import com.intellij.refactoring.util.classMembers.MemberInfo
2121
import com.intellij.task.ProjectTaskManager
22-
import com.intellij.util.concurrency.AppExecutorUtil
2322
import com.intellij.util.containers.nullize
2423
import com.intellij.util.io.exists
2524
import com.jetbrains.rd.util.lifetime.LifetimeDefinition
@@ -49,11 +48,26 @@ import java.nio.file.Path
4948
import java.nio.file.Paths
5049
import java.util.concurrent.TimeUnit
5150
import kotlin.io.path.pathString
51+
import org.utbot.framework.plugin.api.util.Lock
5252

5353
object UtTestsDialogProcessor {
54-
5554
private val logger = KotlinLogging.logger {}
5655

56+
enum class ProgressRange(val from : Double, val to: Double) {
57+
SOLVING(from = 0.0, to = 0.7),
58+
CODEGEN(from = 0.7, to = 0.9),
59+
SARIF(from = 0.9, to = 1.0)
60+
}
61+
62+
fun updateIndicator(indicator: ProgressIndicator, range : ProgressRange, text: String? = null, fraction: Double? = null) {
63+
invokeLater {
64+
text?.let { indicator.text = it }
65+
fraction?.let { indicator.fraction = indicator.fraction.coerceAtLeast(range.from + (range.to - range.from) * fraction.coerceIn(0.0, 1.0)) }
66+
logger.debug("Phase ${indicator.text} with progress ${String.format("%.2f",indicator.fraction)}")
67+
}
68+
}
69+
70+
5771
fun createDialogAndGenerateTests(
5872
project: Project,
5973
srcClasses: Set<PsiClass>,
@@ -108,24 +122,20 @@ object UtTestsDialogProcessor {
108122
promise.onSuccess {
109123
if (it.hasErrors() || it.isAborted)
110124
return@onSuccess
125+
if (!Lock.lock()) {
126+
return@onSuccess
127+
}
111128

112129
(object : Task.Backgroundable(project, "Generate tests") {
113130

114131
override fun run(indicator: ProgressIndicator) {
115132
val ldef = LifetimeDefinition()
133+
ldef.onTermination { Lock.unlock() }
116134
ldef.terminateOnException { lifetime ->
117-
val startTime = System.currentTimeMillis()
118135
val secondsTimeout = TimeUnit.MILLISECONDS.toSeconds(model.timeout)
119-
val totalTimeout = model.timeout * model.srcClasses.size
120136

121137
indicator.isIndeterminate = false
122-
indicator.text = "Generate tests: read classes"
123-
124-
val timerHandler =
125-
AppExecutorUtil.getAppScheduledExecutorService().scheduleWithFixedDelay({
126-
indicator.fraction =
127-
(System.currentTimeMillis() - startTime).toDouble() / totalTimeout
128-
}, 0, 500, TimeUnit.MILLISECONDS)
138+
updateIndicator(indicator, ProgressRange.SOLVING, "Generate tests: read classes", 0.0)
129139

130140
val buildPaths = ReadAction
131141
.nonBlocking<BuildPaths?> { findPaths(model.srcClasses) }
@@ -178,10 +188,13 @@ object UtTestsDialogProcessor {
178188
continue
179189
}
180190

181-
indicator.text = "Generate test cases for class $className"
182191
if (totalClasses > 1) {
183-
indicator.fraction =
184-
indicator.fraction.coerceAtLeast(0.9 * processedClasses / totalClasses)
192+
updateIndicator(
193+
indicator,
194+
ProgressRange.SOLVING,
195+
"Generate test cases for class $className",
196+
processedClasses.toDouble() / totalClasses
197+
)
185198
}
186199

187200
// set timeout for concrete execution and for generated tests
@@ -231,8 +244,6 @@ object UtTestsDialogProcessor {
231244
} else {
232245
testSetsByClass[srcClass] = rdGenerateResult
233246
}
234-
235-
timerHandler.cancel(true)
236247
}
237248
processedClasses++
238249
}
@@ -247,14 +258,13 @@ object UtTestsDialogProcessor {
247258
}
248259
return
249260
}
250-
251-
indicator.fraction = indicator.fraction.coerceAtLeast(0.9)
252-
indicator.text = "Generate code for tests"
261+
updateIndicator(indicator, ProgressRange.CODEGEN, "Generate code for tests", 0.0)
253262
// Commented out to generate tests for collected executions even if action was canceled.
254263
// indicator.checkCanceled()
255264

256265
invokeLater {
257-
generateTests(model, testSetsByClass, psi2KClass, proc)
266+
generateTests(model, testSetsByClass, psi2KClass, proc, indicator)
267+
logger.info { "Generation complete" }
258268
}
259269
}
260270
}

utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/sarif/SarifReportIdea.kt

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,21 @@ package org.utbot.intellij.plugin.sarif
22

33
import com.intellij.openapi.application.WriteAction
44
import com.intellij.psi.PsiClass
5+
import com.intellij.openapi.progress.ProgressIndicator
56
import org.utbot.common.PathUtil.classFqnToPath
7+
import org.utbot.intellij.plugin.ui.utils.getOrCreateSarifReportsPath
8+
import com.intellij.openapi.vfs.VfsUtil
9+
import java.util.concurrent.CountDownLatch
10+
import mu.KotlinLogging
611
import org.utbot.framework.plugin.api.ClassId
12+
import org.utbot.intellij.plugin.generator.UtTestsDialogProcessor
713
import org.utbot.intellij.plugin.models.GenerateTestsModel
814
import org.utbot.intellij.plugin.process.EngineProcess
9-
import org.utbot.intellij.plugin.ui.utils.getOrCreateSarifReportsPath
15+
import org.utbot.intellij.plugin.util.IntelliJApiHelper
1016
import java.nio.file.Path
1117

1218
object SarifReportIdea {
13-
19+
private val logger = KotlinLogging.logger {}
1420
/**
1521
* Creates the SARIF report by calling the SarifReport.createReport(),
1622
* saves it to test resources directory and notifies the user about the creation.
@@ -21,14 +27,25 @@ object SarifReportIdea {
2127
classId: ClassId,
2228
model: GenerateTestsModel,
2329
generatedTestsCode: String,
24-
psiClass: PsiClass
30+
psiClass: PsiClass,
31+
reportsCountDown: CountDownLatch,
32+
indicator: ProgressIndicator
2533
) {
34+
UtTestsDialogProcessor.updateIndicator(indicator, UtTestsDialogProcessor.ProgressRange.SARIF, "Generate SARIF report for ${classId.name}", .5)
2635
// building the path to the report file
2736
val classFqn = classId.name
2837
val (sarifReportsPath, sourceFinding) = WriteAction.computeAndWait<Pair<Path, SourceFindingStrategyIdea>, Exception> { model.testModule.getOrCreateSarifReportsPath(model.testSourceRoot) to SourceFindingStrategyIdea(psiClass) }
2938
val reportFilePath = sarifReportsPath.resolve("${classFqnToPath(classFqn)}Report.sarif")
3039

31-
proc.writeSarif(reportFilePath, testSetsId, generatedTestsCode, sourceFinding)
40+
IntelliJApiHelper.run(IntelliJApiHelper.Target.THREAD_POOL, indicator) {
41+
try {
42+
proc.writeSarif(reportFilePath, testSetsId, generatedTestsCode, sourceFinding)
43+
} catch (e: Exception) {
44+
logger.error { e }
45+
} finally {
46+
reportsCountDown.countDown()
47+
}
48+
}
3249
}
3350
}
3451

utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/actions/GenerateTestsAction.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import org.utbot.intellij.plugin.util.isVisible
2626
import java.util.*
2727
import org.jetbrains.kotlin.j2k.getContainingClass
2828
import org.jetbrains.kotlin.utils.addIfNotNull
29+
import org.utbot.framework.plugin.api.util.Lock
2930
import org.utbot.intellij.plugin.models.packageName
3031
import org.utbot.intellij.plugin.ui.InvalidClassNotifier
3132
import org.utbot.intellij.plugin.util.isAbstract
@@ -41,6 +42,10 @@ class GenerateTestsAction : AnAction(), UpdateInBackground {
4142
}
4243

4344
override fun update(e: AnActionEvent) {
45+
if (Lock.isLocked()) {
46+
e.presentation.isEnabled = false
47+
return
48+
}
4449
if (e.place == ActionPlaces.POPUP) {
4550
e.presentation.text = "Tests with UnitTestBot..."
4651
}

utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/IntelliJApiHelper.kt

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,36 @@ import com.intellij.ide.plugins.PluginManagerCore
44
import com.intellij.openapi.application.runReadAction
55
import com.intellij.openapi.application.runWriteAction
66
import com.intellij.openapi.extensions.PluginId
7+
import com.intellij.openapi.progress.ProgressIndicator
78
import com.intellij.openapi.project.Project
89
import com.intellij.util.PlatformUtils
910
import com.intellij.util.ReflectionUtil
1011
import com.intellij.util.concurrency.AppExecutorUtil
12+
import mu.KotlinLogging
1113
import org.jetbrains.kotlin.idea.util.application.invokeLater
1214

1315
/**
1416
* This object is required to encapsulate Android API usage and grant safe access to it.
1517
*/
1618
object IntelliJApiHelper {
17-
19+
private val logger = KotlinLogging.logger {}
1820
enum class Target { THREAD_POOL, READ_ACTION, WRITE_ACTION, EDT_LATER }
1921

20-
fun run(target: Target, runnable: Runnable) {
21-
when (target) {
22-
Target.THREAD_POOL -> AppExecutorUtil.getAppExecutorService().submit {
22+
fun run(target: Target, indicator: ProgressIndicator? = null, runnable: Runnable) {
23+
if (indicator != null && indicator.isCanceled) return
24+
val wrapper = Runnable {
25+
try {
2326
runnable.run()
27+
} catch (e: Exception) {
28+
logger.error(e) { target.toString() }
29+
throw e
2430
}
25-
26-
Target.READ_ACTION -> runReadAction { runnable.run() }
27-
Target.WRITE_ACTION -> runWriteAction { runnable.run() }
28-
Target.EDT_LATER -> invokeLater { runnable.run() }
31+
}
32+
when (target) {
33+
Target.THREAD_POOL -> AppExecutorUtil.getAppExecutorService().submit { wrapper.run() }
34+
Target.READ_ACTION -> runReadAction { wrapper.run() }
35+
Target.WRITE_ACTION -> runWriteAction { wrapper.run() }
36+
Target.EDT_LATER -> invokeLater { wrapper.run() }
2937
}
3038
}
3139

0 commit comments

Comments
 (0)