From d53a4e9f0c3d916af64ce88aa26804649e2415ba Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Wed, 5 Oct 2022 18:19:16 +0300 Subject: [PATCH 1/2] Notify and reason to the user that UtBot can't be run on the edited class --- .../utbot/intellij/plugin/ui/Notifications.kt | 6 ++++ .../plugin/ui/actions/GenerateTestsAction.kt | 34 ++++++++++++++++--- .../intellij/plugin/util/PsiClassHelper.kt | 3 +- 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/Notifications.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/Notifications.kt index 7b9a3d6705..a043bc21b8 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/Notifications.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/Notifications.kt @@ -64,6 +64,12 @@ object UnsupportedJdkNotifier : ErrorNotifier() { "JDK versions older than 8 are not supported. This project's JDK version is $info" } +object InvalidClassNotifier : WarningNotifier() { + override val displayId: String = "Invalid class" + override fun content(project: Project?, module: Module?, info: String): String = + "Generate tests with UtBot for the $info is not supported" +} + object MissingLibrariesNotifier : WarningNotifier() { override val displayId: String = "Missing libraries" override fun content(project: Project?, module: Module?, info: String): String = 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 cd14a1c363..415d9c4b71 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,11 +26,14 @@ 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.intellij.plugin.models.packageName +import org.utbot.intellij.plugin.ui.InvalidClassNotifier +import org.utbot.intellij.plugin.util.isAbstract class GenerateTestsAction : AnAction(), UpdateInBackground { override fun actionPerformed(e: AnActionEvent) { val project = e.project ?: return - val (srcClasses, focusedMethods, extractMembersFromSrcClasses) = getPsiTargets(e) ?: return + val (srcClasses, focusedMethods, extractMembersFromSrcClasses) = getPsiTargets(e, true) ?: return UtTestsDialogProcessor.createDialogAndGenerateTests(project, srcClasses, extractMembersFromSrcClasses, focusedMethods) } @@ -41,7 +44,7 @@ class GenerateTestsAction : AnAction(), UpdateInBackground { e.presentation.isEnabled = getPsiTargets(e) != null } - private fun getPsiTargets(e: AnActionEvent): Triple, Set, Boolean>? { + private fun getPsiTargets(e: AnActionEvent, withWarnings: Boolean = false): Triple, Set, Boolean>? { val project = e.project ?: return null val editor = e.getData(CommonDataKeys.EDITOR) if (editor != null) { @@ -53,7 +56,7 @@ class GenerateTestsAction : AnAction(), UpdateInBackground { if (psiElementHandler.isCreateTestActionAvailable(element)) { val srcClass = psiElementHandler.containingClass(element) ?: return null - if (srcClass.isInterface || !srcClass.isVisible) return null + if (srcClass.isInvalid(withWarnings)) return null val srcSourceRoot = srcClass.getSourceRoot() ?: return null val srcMembers = srcClass.extractFirstLevelMembers(false) val focusedMethod = focusedMethodOrNull(element, srcMembers, psiElementHandler) @@ -113,7 +116,8 @@ class GenerateTestsAction : AnAction(), UpdateInBackground { } } } - srcClasses.removeIf { it.isInterface } + + srcClasses.removeIf { it.isInvalid(withWarnings) } if (srcClasses.size > 1) { extractMembersFromSrcClasses = false } @@ -136,6 +140,28 @@ class GenerateTestsAction : AnAction(), UpdateInBackground { return null } + private fun PsiClass.isInvalid(withWarnings: Boolean): Boolean { + val isAbstractOrInterface = this.isInterface || this.isAbstract + if (isAbstractOrInterface) { + if (withWarnings) InvalidClassNotifier.notify("abstract class or interface") + return true + } + + val isInvisible = !this.isVisible + if (isInvisible) { + if (withWarnings) InvalidClassNotifier.notify("private or protected class") + return true + } + + val packageIsIncorrect = this.packageName.startsWith("java") + if (packageIsIncorrect) { + if (withWarnings) InvalidClassNotifier.notify("class located in java.* package") + return true + } + + return false + } + private fun PsiElement?.getSourceRoot() : VirtualFile? { val project = this?.project?: return null val virtualFile = this.containingFile?.originalFile?.virtualFile?: return null diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/PsiClassHelper.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/PsiClassHelper.kt index b74130e9a0..5b1f30e098 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/PsiClassHelper.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/PsiClassHelper.kt @@ -12,10 +12,9 @@ import org.jetbrains.kotlin.asJava.elements.isSetter import org.utbot.common.filterWhen import org.utbot.framework.UtSettings -private val PsiMember.isAbstract: Boolean +val PsiMember.isAbstract: Boolean get() = modifierList?.hasModifierProperty(PsiModifier.ABSTRACT)?: false - private val PsiMember.isKotlinGetterOrSetter: Boolean get() { if (this !is KtLightMethod) From 22dd189b25cac6c5353a37a5e2e5b6a206fe5aa3 Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Wed, 5 Oct 2022 19:25:07 +0300 Subject: [PATCH 2/2] Improve previous implementation --- .../utbot/intellij/plugin/ui/Notifications.kt | 2 +- .../plugin/ui/actions/GenerateTestsAction.kt | 34 ++++++++++++++----- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/Notifications.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/Notifications.kt index a043bc21b8..87a9f36317 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/Notifications.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/Notifications.kt @@ -67,7 +67,7 @@ object UnsupportedJdkNotifier : ErrorNotifier() { object InvalidClassNotifier : WarningNotifier() { override val displayId: String = "Invalid class" override fun content(project: Project?, module: Module?, info: String): String = - "Generate tests with UtBot for the $info is not supported" + "Generate tests with UtBot for the $info is not supported." } object MissingLibrariesNotifier : WarningNotifier() { 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 415d9c4b71..d6e05ee4db 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 @@ -33,8 +33,11 @@ import org.utbot.intellij.plugin.util.isAbstract class GenerateTestsAction : AnAction(), UpdateInBackground { override fun actionPerformed(e: AnActionEvent) { val project = e.project ?: return - val (srcClasses, focusedMethods, extractMembersFromSrcClasses) = getPsiTargets(e, true) ?: return - UtTestsDialogProcessor.createDialogAndGenerateTests(project, srcClasses, extractMembersFromSrcClasses, focusedMethods) + + val (srcClasses, focusedMethods, extractMembersFromSrcClasses) = getPsiTargets(e) ?: return + val validatedSrcClasses = validateSrcClasses(srcClasses) ?: return + + UtTestsDialogProcessor.createDialogAndGenerateTests(project, validatedSrcClasses, extractMembersFromSrcClasses, focusedMethods) } override fun update(e: AnActionEvent) { @@ -44,7 +47,7 @@ class GenerateTestsAction : AnAction(), UpdateInBackground { e.presentation.isEnabled = getPsiTargets(e) != null } - private fun getPsiTargets(e: AnActionEvent, withWarnings: Boolean = false): Triple, Set, Boolean>? { + private fun getPsiTargets(e: AnActionEvent): Triple, Set, Boolean>? { val project = e.project ?: return null val editor = e.getData(CommonDataKeys.EDITOR) if (editor != null) { @@ -56,7 +59,6 @@ class GenerateTestsAction : AnAction(), UpdateInBackground { if (psiElementHandler.isCreateTestActionAvailable(element)) { val srcClass = psiElementHandler.containingClass(element) ?: return null - if (srcClass.isInvalid(withWarnings)) return null val srcSourceRoot = srcClass.getSourceRoot() ?: return null val srcMembers = srcClass.extractFirstLevelMembers(false) val focusedMethod = focusedMethodOrNull(element, srcMembers, psiElementHandler) @@ -117,7 +119,6 @@ class GenerateTestsAction : AnAction(), UpdateInBackground { } } - srcClasses.removeIf { it.isInvalid(withWarnings) } if (srcClasses.size > 1) { extractMembersFromSrcClasses = false } @@ -140,22 +141,39 @@ class GenerateTestsAction : AnAction(), UpdateInBackground { return null } + /** + * Validates that a set of source classes matches some requirements from [isInvalid]. + * If no one of them matches, shows a warning about the first mismatch reason. + */ + private fun validateSrcClasses(srcClasses: Set): Set? { + val filteredClasses = srcClasses + .filterNot { it.isInvalid(withWarnings = false) } + .toSet() + + if (filteredClasses.isEmpty()) { + srcClasses.first().isInvalid(withWarnings = true) + return null + } + + return filteredClasses + } + private fun PsiClass.isInvalid(withWarnings: Boolean): Boolean { val isAbstractOrInterface = this.isInterface || this.isAbstract if (isAbstractOrInterface) { - if (withWarnings) InvalidClassNotifier.notify("abstract class or interface") + if (withWarnings) InvalidClassNotifier.notify("abstract class or interface ${this.name}") return true } val isInvisible = !this.isVisible if (isInvisible) { - if (withWarnings) InvalidClassNotifier.notify("private or protected class") + if (withWarnings) InvalidClassNotifier.notify("private or protected class ${this.name}") return true } val packageIsIncorrect = this.packageName.startsWith("java") if (packageIsIncorrect) { - if (withWarnings) InvalidClassNotifier.notify("class located in java.* package") + if (withWarnings) InvalidClassNotifier.notify("class ${this.name} located in java.* package") return true }