diff --git a/docs/AndroidStudioSupport.md b/docs/AndroidStudioSupport.md index 7049d52571..c051d53a62 100644 --- a/docs/AndroidStudioSupport.md +++ b/docs/AndroidStudioSupport.md @@ -14,8 +14,16 @@ > Install and setup gradle version 7.2+ (version 7.4 tested) > -> Use JDK 8 for Gradle in\ +> Use JDK 11 for Gradle in\ > `File -> Settings -> Build, Execution, Deployment -> Build Tools -> Gradle -> Gradle JVM` +> +> \ +> If you want to use JDK 8, you can: +> 1. Generate tests with JDK 8 +> 2. Switch to JDK 11 and compile tests +> 3. Switch back to JDK 8 and run tests +> +> The reason for it is the Android Gradle Plugin, which requires Java 11 to build anything. ## Running in AS @@ -23,6 +31,9 @@ > create one like this: > > +> +> To run generated tests, you must create separate JUnit configuration.\ +> ("Green arrows" will not work, since they launch Android Emulator.) > ## Debug Intellij code diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/JdkPathDefaultProvider.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/JdkPathDefaultProvider.kt index 9764f00961..4262202b34 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/JdkPathDefaultProvider.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/JdkPathDefaultProvider.kt @@ -6,4 +6,7 @@ import java.nio.file.Paths open class JdkPathDefaultProvider: JdkPathProvider() { override val jdkPath: Path get() = Paths.get(System.getProperty("java.home")) + + override val jdkVersion: String + get() = System.getProperty("java.version") } diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/JdkPathProvider.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/JdkPathProvider.kt index b46903c571..96c9e36184 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/JdkPathProvider.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/JdkPathProvider.kt @@ -9,4 +9,6 @@ abstract class JdkPathProvider { } abstract val jdkPath: Path + + abstract val jdkVersion: String } diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/JdkPathService.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/JdkPathService.kt index d9e321a718..60c7b071f2 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/JdkPathService.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/JdkPathService.kt @@ -16,4 +16,7 @@ object JdkPathService { // Kotlin delegates do not support changing in runtime, so use simple getter val jdkPath: Path get() = jdkPathProvider.jdkPath + + val jdkVersion: String + get() = jdkPathProvider.jdkVersion } diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt index b93c5b8f8e..86e9a6c912 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt @@ -402,13 +402,14 @@ object CodeGenerationController { destinationWarningMessage(model.testPackageName, classUnderTestPackageName) ?.let { + hasWarnings = true appendHtmlLine(it) appendHtmlLine() } appendHtmlLine(eventLogMessage()) } - hasWarnings = report.hasWarnings + hasWarnings = hasWarnings || report.hasWarnings Pair(message, report.detailedStatistics) } else { val accumulatedReport = reports.first() 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 ba4c3e4f58..4e7bc3407a 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 @@ -37,6 +37,7 @@ import org.utbot.framework.plugin.api.util.withUtContext import org.utbot.intellij.plugin.generator.CodeGenerationController.generateTests import org.utbot.intellij.plugin.models.GenerateTestsModel import org.utbot.intellij.plugin.ui.GenerateTestsDialogWindow +import org.utbot.intellij.plugin.ui.utils.showErrorDialogLater import org.utbot.intellij.plugin.util.IntelliJApiHelper import org.utbot.intellij.plugin.util.PluginJdkPathProvider import org.utbot.intellij.plugin.util.signature @@ -51,11 +52,9 @@ import org.utbot.engine.util.mockListeners.ForceStaticMockListener import org.utbot.framework.plugin.api.testFlow import org.utbot.intellij.plugin.settings.Settings import org.utbot.intellij.plugin.ui.utils.isGradle -import org.utbot.intellij.plugin.ui.utils.jdkVersion -import org.utbot.intellij.plugin.ui.utils.showErrorDialogLater import org.utbot.intellij.plugin.ui.utils.suitableTestSourceRoots -import org.utbot.intellij.plugin.ui.utils.testModule import org.utbot.intellij.plugin.util.isAbstract +import org.utbot.intellij.plugin.ui.utils.testModules import kotlin.reflect.KClass import kotlin.reflect.full.functions @@ -79,18 +78,11 @@ object UtTestsDialogProcessor { focusedMethod: MemberInfo?, ): GenerateTestsDialogWindow? { val srcModule = findSrcModule(srcClasses) - val testModule = srcModule.testModule(project) - - JdkPathService.jdkPathProvider = PluginJdkPathProvider(project, testModule) - val jdkVersion = try { - testModule.jdkVersion() - } catch (e: IllegalStateException) { - // Just ignore it here, notification will be shown in - // org.utbot.intellij.plugin.ui.utils.ModuleUtilsKt.jdkVersionBy - return null - } + val testModules = srcModule.testModules(project) - if (project.isGradle() && testModule.suitableTestSourceRoots().isEmpty()) { + JdkPathService.jdkPathProvider = PluginJdkPathProvider(project) + + if (project.isGradle() && testModules.flatMap { it.suitableTestSourceRoots() }.isEmpty()) { val errorMessage = """ No test source roots found in the project.
Please, create or configure at least one test source root. @@ -103,8 +95,7 @@ object UtTestsDialogProcessor { GenerateTestsModel( project, srcModule, - testModule, - jdkVersion, + testModules, srcClasses, if (focusedMethod != null) setOf(focusedMethod) else null, UtSettings.utBotGenerationTimeoutInMillis, @@ -296,12 +287,6 @@ object UtTestsDialogProcessor { val pathsList = OrderEnumerator.orderEntries(srcModule).recursively().pathsList val (classpath, classpathList) = if (IntelliJApiHelper.isAndroidStudio()) { - // Add $JAVA_HOME/jre/lib/rt.jar to path. - // This allows Soot to analyze real java instead of stub version in Android SDK on local machine. - pathsList.add( - System.getenv("JAVA_HOME") + File.separator + Paths.get("jre", "lib", "rt.jar") - ) - // Filter out manifests from classpath. val filterPredicate = { it: String -> !it.contains("manifest", ignoreCase = true) diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/models/GenerateTestsModel.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/models/GenerateTestsModel.kt index 95c88e3575..02842ad139 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/models/GenerateTestsModel.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/models/GenerateTestsModel.kt @@ -11,6 +11,7 @@ import org.utbot.framework.plugin.api.CodegenLanguage import org.utbot.framework.plugin.api.MockFramework import org.utbot.framework.plugin.api.MockStrategyApi import com.intellij.openapi.module.Module +import com.intellij.openapi.module.ModuleUtil import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.JavaSdkVersion import com.intellij.openapi.vfs.VirtualFile @@ -18,19 +19,30 @@ import com.intellij.psi.PsiClass import com.intellij.refactoring.util.classMembers.MemberInfo import org.jetbrains.kotlin.idea.core.getPackage import org.utbot.framework.util.ConflictTriggers +import org.utbot.intellij.plugin.ui.utils.jdkVersion data class GenerateTestsModel( val project: Project, val srcModule: Module, - val testModule: Module, - val jdkVersion: JavaSdkVersion, + val potentialTestModules: List, var srcClasses: Set, var selectedMethods: Set?, var timeout:Long, var generateWarningsForStaticMocking: Boolean = false, var fuzzingValue: Double = 0.05 ) { + // GenerateTestsModel is supposed to be created with non-empty list of potentialTestModules. + // Otherwise, the error window is supposed to be shown earlier. + var testModule: Module = potentialTestModules.firstOrNull() ?: error("Empty list of test modules in model") + var testSourceRoot: VirtualFile? = null + fun setSourceRootAndFindTestModule(newTestSourceRoot: VirtualFile?) { + requireNotNull(newTestSourceRoot) + testSourceRoot = newTestSourceRoot + testModule = ModuleUtil.findModuleForFile(newTestSourceRoot, project) + ?: error("Could not find module for $newTestSourceRoot") + } + var testPackageName: String? = null lateinit var testFramework: TestFramework lateinit var mockStrategy: MockStrategyApi @@ -49,6 +61,14 @@ data class GenerateTestsModel( srcClasses.map { it.packageName }.distinct().size != 1 } var runGeneratedTestsWithCoverage : Boolean = false + + val jdkVersion: JavaSdkVersion? + get() = try { + testModule.jdkVersion() + } catch (e: IllegalStateException) { + // Just ignore it here, notification will be shown in org.utbot.intellij.plugin.ui.utils.ModuleUtilsKt.jdkVersionBy + null + } } val PsiClass.packageName: String get() = this.containingFile.containingDirectory.getPackage()?.qualifiedName ?: "" \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt index 8aae196b0e..4e36c0d5fa 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt @@ -561,8 +561,9 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m * Creates test source root if absent and target packages for tests. */ private fun createTestRootAndPackages(): Boolean { - model.testSourceRoot = createDirectoryIfMissing(model.testSourceRoot) + model.setSourceRootAndFindTestModule(createDirectoryIfMissing(model.testSourceRoot)) val testSourceRoot = model.testSourceRoot ?: return false + if (model.testSourceRoot?.isDirectory != true) return false if (getOrCreateTestRoot(testSourceRoot)) { if (cbSpecifyTestPackage.isSelected) { @@ -860,10 +861,10 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m testSourceFolderField.childComponent.addActionListener { event -> with((event.source as JComboBox<*>).selectedItem) { if (this is VirtualFile) { - model.testSourceRoot = this@with + model.setSourceRootAndFindTestModule(this@with) } else { - model.testSourceRoot = null + model.setSourceRootAndFindTestModule(null) } } } diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/components/TestFolderComboWithBrowseButton.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/components/TestFolderComboWithBrowseButton.kt index cd9389b573..7071657f2a 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/components/TestFolderComboWithBrowseButton.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/components/TestFolderComboWithBrowseButton.kt @@ -50,8 +50,11 @@ class TestFolderComboWithBrowseButton(private val model: GenerateTestsModel) : C } } - val testRoots = model.testModule.suitableTestSourceRoots().toMutableList() + val testRoots = model.potentialTestModules.flatMap { it.suitableTestSourceRoots().toMutableList() }.toMutableList() + + // this method is blocked for Gradle, where multiple test modules can exist model.testModule.addDedicatedTestRoot(testRoots) + if (testRoots.isNotEmpty()) { configureRootsCombo(testRoots) } else { @@ -61,7 +64,7 @@ class TestFolderComboWithBrowseButton(private val model: GenerateTestsModel) : C addActionListener { val testSourceRoot = createNewTestSourceRoot(model) testSourceRoot?.let { - model.testSourceRoot = it + model.setSourceRootAndFindTestModule(it) if (childComponent.itemCount == 1 && childComponent.selectedItem == SET_TEST_FOLDER) { newItemList(setOf(it)) @@ -88,6 +91,7 @@ class TestFolderComboWithBrowseButton(private val model: GenerateTestsModel) : C // unfortunately, Gradle creates Kotlin test source root with Java source root type, so type is misleading val selectedRoot = testRoots.first() + // do not update model.testModule here, because fake test source root could have been chosen model.testSourceRoot = selectedRoot newItemList(testRoots.toSet()) } diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/ModuleUtils.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/ModuleUtils.kt index 59e03e3a3d..4867cd72f6 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/ModuleUtils.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/ModuleUtils.kt @@ -83,25 +83,27 @@ fun Module.getOrCreateSarifReportsPath(testSourceRoot: VirtualFile?): Path { } /** - * Find test module by current source module. + * Find test modules by current source module. */ -fun Module.testModule(project: Project): Module { - var testModule = findPotentialModuleForTests(project, this) - val testRootUrls = testModule.suitableTestSourceRoots() +fun Module.testModules(project: Project): List { + var testModules = findPotentialModulesForTests(project, this) + val testRootUrls = testModules.flatMap { it.suitableTestSourceRoots() } //if no suitable module for tests is found, create tests in the same root - if (testRootUrls.isEmpty() && testModule.suitableTestSourceFolders().isEmpty()) { - testModule = this + if (testRootUrls.isEmpty() && testModules.flatMap { it.suitableTestSourceFolders() }.isEmpty()) { + testModules = listOf(this) } - return testModule + return testModules } -private fun findPotentialModuleForTests(project: Project, srcModule: Module): Module { +private fun findPotentialModulesForTests(project: Project, srcModule: Module): List { + val modules = mutableListOf() for (module in ModuleManager.getInstance(project).modules) { if (srcModule == TestModuleProperties.getInstance(module).productionModule) { - return module + modules += module } } + if (modules.isNotEmpty()) return modules if (srcModule.suitableTestSourceFolders().isEmpty()) { val modules = mutableSetOf() @@ -109,9 +111,9 @@ private fun findPotentialModuleForTests(project: Project, srcModule: Module): Mo modules.remove(srcModule) val modulesWithTestRoot = modules.filter { it.suitableTestSourceFolders().isNotEmpty() } - if (modulesWithTestRoot.size == 1) return modulesWithTestRoot[0] + if (modulesWithTestRoot.size == 1) return modulesWithTestRoot } - return srcModule + return listOf(srcModule) } /** diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/PluginJdkPathProvider.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/PluginJdkPathProvider.kt index ba78340018..ba3d7ee80c 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/PluginJdkPathProvider.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/PluginJdkPathProvider.kt @@ -2,31 +2,35 @@ package org.utbot.intellij.plugin.util import org.utbot.common.PathUtil.toPath import org.utbot.framework.JdkPathDefaultProvider -import com.intellij.openapi.module.Module import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.ProjectJdkTable -import com.intellij.openapi.roots.ModuleRootManager +import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.roots.ProjectRootManager import java.nio.file.Path class PluginJdkPathProvider( - private val project: Project, - private val testModule: Module, + private val project: Project ) : JdkPathDefaultProvider() { - override val jdkPath: Path - get() = + private val sdk: Sdk? + get() { if (IntelliJApiHelper.isAndroidStudio()) { // Get Gradle JDK for Android IntelliJApiHelper.androidGradleSDK(project) ?.let { sdkName -> - ProjectJdkTable.getInstance().findJdk(sdkName)?.homePath?.toPath() + ProjectJdkTable.getInstance().findJdk(sdkName) ?.let { + return it + } } - } else { - // Use testModule JDK (or Project SDK) as analyzed JDK - (ModuleRootManager.getInstance(testModule).sdk - ?.homePath ?: ProjectRootManager.getInstance(project).projectSdk?.homePath) - ?.toPath() - } ?: super.jdkPath // Return default JDK in case of failure + } + + // Use Project SDK as analyzed JDK + return ProjectRootManager.getInstance(project).projectSdk + } + + override val jdkPath: Path + get() = sdk?.let { it.homePath?.toPath() } ?: super.jdkPath // Return default JDK in case of failure + override val jdkVersion: String + get() = sdk?.versionString ?: super.jdkVersion // Return default JDK in case of failure } \ No newline at end of file