Skip to content

Android Studio fixes (#634, #635) #639

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
merged 1 commit into from
Aug 9, 2022
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
13 changes: 12 additions & 1 deletion docs/AndroidStudioSupport.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,26 @@

> 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

> For now, running Utbot is supported only for Kotlin libraries. You can
> create one like this:
>
> <https://proandroiddev.com/create-an-android-library-aar-79d2338678ba>
>
> To run generated tests, you must create separate JUnit configuration.\
> ("Green arrows" will not work, since they launch Android Emulator.)
>
## Debug Intellij code

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ abstract class JdkPathProvider {
}

abstract val jdkPath: Path

abstract val jdkVersion: String
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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 = """
<html>No test source roots found in the project.<br>
Please, <a href="https://www.jetbrains.com/help/idea/testing.html#add-test-root">create or configure</a> at least one test source root.
Expand All @@ -103,8 +95,7 @@ object UtTestsDialogProcessor {
GenerateTestsModel(
project,
srcModule,
testModule,
jdkVersion,
testModules,
srcClasses,
if (focusedMethod != null) setOf(focusedMethod) else null,
UtSettings.utBotGenerationTimeoutInMillis,
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,38 @@ 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
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<Module>,
var srcClasses: Set<PsiClass>,
var selectedMethods: Set<MemberInfo>?,
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
Expand All @@ -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 ?: ""
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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))
Expand All @@ -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())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,35 +83,37 @@ 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<Module> {
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<Module> {
val modules = mutableListOf<Module>()
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<Module>()
ModuleUtilCore.collectModulesDependsOn(srcModule, modules)
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)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}