Skip to content

Commit 8b9bcc2

Browse files
"Generate and Run" action doesn't initiate test run #709 (#887)
Fix various exceptions around threads/locks/project model modifications Switch from PsiFiles to SmartPsiElementPointers Add logging for exceptions Resolving conflicts
1 parent 40d1215 commit 8b9bcc2

File tree

6 files changed

+123
-83
lines changed

6 files changed

+123
-83
lines changed

utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/UtContext.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import org.utbot.common.currentThreadInfo
55
import org.utbot.framework.plugin.api.util.UtContext.Companion.setUtContext
66
import kotlin.coroutines.CoroutineContext
77
import kotlinx.coroutines.ThreadContextElement
8+
import mu.KotlinLogging
89

910
val utContext: UtContext
1011
get() = UtContext.currentContext()
@@ -70,4 +71,11 @@ class UtContext(val classLoader: ClassLoader) : ThreadContextElement<UtContext?>
7071
}
7172
}
7273

73-
inline fun <T> withUtContext(context: UtContext, block: () -> T): T = setUtContext(context).use { block() }
74+
inline fun <T> withUtContext(context: UtContext, block: () -> T): T = setUtContext(context).use {
75+
try {
76+
return@use block.invoke()
77+
} catch (e: Exception) {
78+
KotlinLogging.logger("withUtContext").error { e }
79+
throw e
80+
}
81+
}

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

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ import com.intellij.psi.PsiFile
3030
import com.intellij.psi.PsiFileFactory
3131
import com.intellij.psi.PsiManager
3232
import com.intellij.psi.PsiMethod
33+
import com.intellij.psi.SmartPointerManager
34+
import com.intellij.psi.SmartPsiElementPointer
3335
import com.intellij.psi.codeStyle.CodeStyleManager
3436
import com.intellij.psi.codeStyle.JavaCodeStyleManager
3537
import com.intellij.psi.search.GlobalSearchScopesCore
@@ -91,10 +93,12 @@ import java.util.concurrent.CountDownLatch
9193
import java.util.concurrent.TimeUnit
9294
import kotlin.reflect.KClass
9395
import kotlin.reflect.full.functions
96+
import mu.KotlinLogging
9497
import org.utbot.intellij.plugin.util.IntelliJApiHelper.Target.*
9598
import org.utbot.intellij.plugin.util.IntelliJApiHelper.run
9699

97100
object CodeGenerationController {
101+
private val logger = KotlinLogging.logger {}
98102

99103
private class UtilClassListener {
100104
var requiredUtilClassKind: UtilClassKind? = null
@@ -115,35 +119,27 @@ object CodeGenerationController {
115119
val latch = CountDownLatch(testSetsByClass.size)
116120

117121
val reports = mutableListOf<TestsGenerationReport>()
118-
val testFiles = mutableListOf<PsiFile>()
122+
val testFilesPointers = mutableListOf<SmartPsiElementPointer<PsiFile>>()
119123
val utilClassListener = UtilClassListener()
120124
for (srcClass in testSetsByClass.keys) {
121125
val testSets = testSetsByClass[srcClass] ?: continue
122126
try {
123127
val classPackageName = model.getTestClassPackageNameFor(srcClass)
124128
val testDirectory = allTestPackages[classPackageName] ?: baseTestDirectory
125129
val testClass = createTestClass(srcClass, testDirectory, model) ?: continue
126-
val testClassFile = testClass.containingFile
130+
val testFilePointer = SmartPointerManager.getInstance(model.project).createSmartPsiElementPointer(testClass.containingFile)
127131
val cut = psi2KClass[srcClass] ?: error("Didn't find KClass instance for class ${srcClass.name}")
128132
runWriteCommandAction(model.project, "Generate tests with UtBot", null, {
129133
try {
130-
generateCodeAndReport(
131-
srcClass,
132-
cut,
133-
testClass,
134-
testClassFile,
135-
testSets,
136-
model,
137-
latch,
138-
reports,
139-
utilClassListener
140-
)
141-
testFiles.add(testClassFile)
134+
generateCodeAndReport(srcClass, cut, testClass, testFilePointer, testSets, model, latch, reports, utilClassListener)
135+
testFilesPointers.add(testFilePointer)
142136
} catch (e: IncorrectOperationException) {
137+
logger.error { e }
143138
showCreatingClassError(model.project, createTestClassName(srcClass))
144139
}
145140
})
146141
} catch (e: IncorrectOperationException) {
142+
logger.error { e }
147143
showCreatingClassError(model.project, createTestClassName(srcClass))
148144
}
149145
}
@@ -187,7 +183,7 @@ object CodeGenerationController {
187183

188184
mergeSarifReports(model, sarifReportsPath)
189185
if (model.runGeneratedTestsWithCoverage) {
190-
RunConfigurationHelper.runTestsWithCoverage(model, testFiles)
186+
RunConfigurationHelper.runTestsWithCoverage(model, testFilesPointers)
191187
}
192188
}
193189
}
@@ -275,7 +271,7 @@ object CodeGenerationController {
275271
}
276272

277273
runWriteCommandAction(model.project, "UtBot util class reformatting", null, {
278-
reformat(model, utUtilsFile, utUtilsClass)
274+
reformat(model, SmartPointerManager.getInstance(model.project).createSmartPsiElementPointer(utUtilsFile), utUtilsClass)
279275
})
280276

281277
val utUtilsDocument = PsiDocumentManager
@@ -301,7 +297,7 @@ object CodeGenerationController {
301297
run(WRITE_ACTION) {
302298
unblockDocument(model.project, utilsClassDocument)
303299
executeCommand {
304-
utilsClassDocument.setText(utUtilsText)
300+
utilsClassDocument.setText(utUtilsText.replace("jdk.internal.misc", "sun.misc"))
305301
}
306302
unblockDocument(model.project, utilsClassDocument)
307303
}
@@ -569,7 +565,7 @@ object CodeGenerationController {
569565
srcClass: PsiClass,
570566
classUnderTest: KClass<*>,
571567
testClass: PsiClass,
572-
file: PsiFile,
568+
filePointer: SmartPsiElementPointer<PsiFile>,
573569
testSets: List<UtMethodTestSet>,
574570
model: GenerateTestsModel,
575571
reportsCountDown: CountDownLatch,
@@ -597,7 +593,7 @@ object CodeGenerationController {
597593
testClassPackageName = testClass.packageName
598594
)
599595

600-
val editor = CodeInsightUtil.positionCursorAtLBrace(testClass.project, file, testClass)
596+
val editor = CodeInsightUtil.positionCursorAtLBrace(testClass.project, filePointer.containingFile, testClass)
601597
//TODO: Use PsiDocumentManager.getInstance(model.project).getDocument(file)
602598
// if we don't want to open _all_ new files with tests in editor one-by-one
603599
run(THREAD_POOL) {
@@ -609,27 +605,28 @@ object CodeGenerationController {
609605
unblockDocument(testClass.project, editor.document)
610606
// TODO: JIRA:1246 - display warnings if we rewrite the file
611607
executeCommand(testClass.project, "Insert Generated Tests") {
612-
editor.document.setText(generatedTestsCode)
608+
editor.document.setText(generatedTestsCode.replace("jdk.internal.misc.Unsafe", "sun.misc.Unsafe"))
613609
}
614610
unblockDocument(testClass.project, editor.document)
615611

616612
// after committing the document the `testClass` is invalid in PsiTree,
617613
// so we have to reload it from the corresponding `file`
618-
val testClassUpdated = (file as PsiClassOwner).classes.first() // only one class in the file
614+
val testClassUpdated = (filePointer.containingFile as PsiClassOwner).classes.first() // only one class in the file
619615

620616
// reformatting before creating reports due to
621617
// SarifReport requires the final version of the generated tests code
622618
run(THREAD_POOL) {
623-
IntentionHelper(model.project, editor, file).applyIntentions()
619+
IntentionHelper(model.project, editor, filePointer).applyIntentions()
624620
run(EDT_LATER) {
625621
runWriteCommandAction(testClassUpdated.project, "UtBot tests reformatting", null, {
626-
reformat(model, file, testClassUpdated)
622+
reformat(model, filePointer, testClassUpdated)
627623
})
628624
unblockDocument(testClassUpdated.project, editor.document)
629625

630626
// uploading formatted code
627+
val file = filePointer.containingFile
631628
val codeGenerationResultFormatted =
632-
codeGenerationResult.copy(generatedCode = file.text)
629+
codeGenerationResult.copy(generatedCode = file?.text?: generatedTestsCode)
633630

634631
// creating and saving reports
635632
reports += codeGenerationResultFormatted.testsGenerationReport
@@ -652,9 +649,10 @@ object CodeGenerationController {
652649
}
653650
}
654651

655-
private fun reformat(model: GenerateTestsModel, file: PsiFile, testClass: PsiClass) {
652+
private fun reformat(model: GenerateTestsModel, smartPointer: SmartPsiElementPointer<PsiFile>, testClass: PsiClass) {
656653
val project = model.project
657654
val codeStyleManager = CodeStyleManager.getInstance(project)
655+
val file = smartPointer.containingFile?: return
658656
codeStyleManager.reformat(file)
659657
when (model.codegenLanguage) {
660658
CodegenLanguage.JAVA -> {
@@ -694,6 +692,7 @@ object CodeGenerationController {
694692
SarifReportIdea.createAndSave(model, testSets, generatedTestsCode, sourceFinding)
695693
}
696694
} catch (e: Exception) {
695+
logger.error { e }
697696
showErrorDialogLater(
698697
project,
699698
message = "Cannot save Sarif report via generated tests: error occurred '${e.message}'",

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

Lines changed: 50 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -13,57 +13,66 @@ import com.intellij.openapi.util.Computable
1313
import com.intellij.openapi.util.Disposer
1414
import com.intellij.openapi.util.TextRange
1515
import com.intellij.psi.PsiFile
16+
import com.intellij.psi.SmartPsiElementPointer
17+
import mu.KotlinLogging
18+
import org.utbot.intellij.plugin.util.IntelliJApiHelper
19+
import org.utbot.intellij.plugin.util.IntelliJApiHelper.run
1620

17-
class IntentionHelper(val project: Project, private val editor: Editor, private val testFile: PsiFile) {
21+
private val logger = KotlinLogging.logger {}
22+
23+
class IntentionHelper(val project: Project, private val editor: Editor, private val testFile: SmartPsiElementPointer<PsiFile>) {
1824
fun applyIntentions() {
19-
while (true) {
20-
val actions =
21-
DumbService.getInstance(project).runReadActionInSmartMode(Computable<Map<IntentionAction, String>> {
22-
val daemonProgressIndicator = DaemonProgressIndicator()
23-
Disposer.register(project, daemonProgressIndicator)//check it
24-
val list = ProgressManager.getInstance().runProcess(Computable<List<HighlightInfo>> {
25+
val actions =
26+
DumbService.getInstance(project).runReadActionInSmartMode(Computable<Map<IntentionAction, String>> {
27+
val daemonProgressIndicator = DaemonProgressIndicator()
28+
Disposer.register(project, daemonProgressIndicator)//check it
29+
val list = ProgressManager.getInstance().runProcess(Computable<List<HighlightInfo>> {
30+
try {
31+
val containingFile = testFile.containingFile ?: return@Computable emptyList()
32+
DaemonCodeAnalyzerEx.getInstanceEx(project).runMainPasses(
33+
containingFile,
34+
editor.document,
35+
daemonProgressIndicator
36+
)
37+
} catch (e: Exception) {
38+
logger.info { e }
39+
emptyList()// 'Cannot obtain read-action' rare case
40+
}
41+
}, daemonProgressIndicator)
42+
val actions = mutableMapOf<IntentionAction, String>()
43+
list.forEach { info ->
44+
val quickFixActionRanges = info.quickFixActionRanges
45+
if (!quickFixActionRanges.isNullOrEmpty()) {
46+
val toList =
47+
quickFixActionRanges.map { pair: com.intellij.openapi.util.Pair<HighlightInfo.IntentionActionDescriptor, TextRange> -> pair.first.action }
48+
.toList()
49+
toList.forEach { intentionAction -> actions[intentionAction] = intentionAction.familyName }
50+
}
51+
}
52+
actions
53+
})
54+
actions.forEach {
55+
if (it.value.isApplicable()) {
56+
if (it.key.startInWriteAction()) {
57+
WriteCommandAction.runWriteCommandAction(project) {
2558
try {
26-
DaemonCodeAnalyzerEx.getInstanceEx(project).runMainPasses(
27-
testFile,
28-
editor.document,
29-
daemonProgressIndicator
30-
)
59+
it.key.invoke(project, editor, testFile.containingFile)
3160
} catch (e: Exception) {
32-
emptyList()// 'Cannot obtain read-action' rare case
33-
}
34-
}, daemonProgressIndicator)
35-
val actions = mutableMapOf<IntentionAction, String>()
36-
list.forEach { info ->
37-
val quickFixActionRanges = info.quickFixActionRanges
38-
if (!quickFixActionRanges.isNullOrEmpty()) {
39-
val toList =
40-
quickFixActionRanges.map { pair: com.intellij.openapi.util.Pair<HighlightInfo.IntentionActionDescriptor, TextRange> -> pair.first.action }
41-
.toList()
42-
toList.forEach { intentionAction -> actions[intentionAction] = intentionAction.familyName }
61+
logger.error { e }
4362
}
4463
}
45-
actions
46-
})
47-
if (actions.isEmpty()) break
48-
49-
var someWereApplied = false
50-
actions.forEach {
51-
if (it.value.isApplicable()) {
52-
someWereApplied = true
53-
if (it.key.startInWriteAction()) {
54-
WriteCommandAction.runWriteCommandAction(project) {
55-
editor.document.isInBulkUpdate = true
56-
it.key.invoke(project, editor, testFile)
57-
editor.document.isInBulkUpdate = false
64+
} else {
65+
run(IntelliJApiHelper.Target.EDT_LATER) {
66+
run(IntelliJApiHelper.Target.READ_ACTION) {
67+
try {
68+
it.key.invoke(project, editor, testFile.containingFile)
69+
} catch (e: Exception) {
70+
logger.error { e }
71+
}
5872
}
59-
} else {
60-
editor.document.isInBulkUpdate = true
61-
it.key.invoke(project, editor, testFile)
62-
editor.document.isInBulkUpdate = false
6373
}
6474
}
6575
}
66-
if (!someWereApplied) break
6776
}
6877
}
6978

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import org.utbot.intellij.plugin.ui.utils.PsiElementHandler
55
import com.intellij.openapi.actionSystem.AnAction
66
import com.intellij.openapi.actionSystem.AnActionEvent
77
import com.intellij.openapi.actionSystem.CommonDataKeys
8+
import com.intellij.openapi.actionSystem.UpdateInBackground
89
import com.intellij.openapi.actionSystem.PlatformDataKeys
10+
import com.intellij.openapi.application.runReadAction
911
import com.intellij.openapi.editor.Editor
1012
import com.intellij.openapi.module.ModuleUtil
1113
import com.intellij.openapi.project.Project
@@ -23,7 +25,7 @@ import java.util.*
2325
import org.jetbrains.kotlin.j2k.getContainingClass
2426
import org.jetbrains.kotlin.utils.addIfNotNull
2527

26-
class GenerateTestsAction : AnAction() {
28+
class GenerateTestsAction : AnAction(), UpdateInBackground {
2729
override fun actionPerformed(e: AnActionEvent) {
2830
val project = e.project ?: return
2931
val (srcClasses, focusedMethods, extractMembersFromSrcClasses) = getPsiTargets(e) ?: return
@@ -79,7 +81,10 @@ class GenerateTestsAction : AnAction() {
7981
psiElementHandler.containingClass(element)?.let {
8082
srcClasses += setOf(it)
8183
extractMembersFromSrcClasses = true
82-
if (it.extractFirstLevelMembers(false).isEmpty())
84+
val memberInfoList = runReadAction<List<MemberInfo>> {
85+
it.extractFirstLevelMembers(false)
86+
}
87+
if (memberInfoList.isNullOrEmpty())
8388
return null
8489
}
8590

utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/ModuleUtils.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ private fun getOrCreateTestResourcesUrl(module: Module, testSourceRoot: VirtualF
193193
}
194194

195195
val testFolder = sourceFolders.firstOrNull { it.rootType in testSourceRootTypes }
196-
val contentEntry = testFolder?.contentEntry ?: rootModel.contentEntries.first()
196+
val contentEntry = testFolder?.getModifiableContentEntry() ?: rootModel.contentEntries.first()
197197

198198
val parentFolderUrl = testFolder?.let { getParentPath(testFolder.url) }
199199
val testResourcesUrl =
@@ -217,6 +217,9 @@ private fun getOrCreateTestResourcesUrl(module: Module, testSourceRoot: VirtualF
217217
if (!rootModel.isDisposed && rootModel.isWritable) rootModel.dispose()
218218
}
219219
}
220+
fun SourceFolder.getModifiableContentEntry() : ContentEntry? {
221+
return ModuleRootManager.getInstance(contentEntry.rootModel.module).modifiableModel.contentEntries.find { entry -> entry.url == url }
222+
}
220223

221224
fun ContentEntry.addSourceRootIfAbsent(
222225
model: ModifiableRootModel,

0 commit comments

Comments
 (0)