Skip to content

Commit 540010c

Browse files
committed
Android Studio fixes (#634, #635)
Multiple test modules supported. Removed unused jdkVersion from model, added for convenience to JdkPathService. Removed unnecessary classpath modification parts for Android.
1 parent f5132f7 commit 540010c

File tree

11 files changed

+91
-55
lines changed

11 files changed

+91
-55
lines changed

docs/AndroidStudioSupport.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,26 @@
1414

1515
> Install and setup gradle version 7.2+ (version 7.4 tested)
1616
>
17-
> Use JDK 8 for Gradle in\
17+
> Use JDK 11 for Gradle in\
1818
> `File -> Settings -> Build, Execution, Deployment -> Build Tools -> Gradle -> Gradle JVM`
19+
>
20+
> \
21+
> If you want to use JDK 8, you can:
22+
> 1. Generate tests with JDK 8
23+
> 2. Switch to JDK 11 and compile tests
24+
> 3. Switch back to JDK 8 and run tests
25+
>
26+
> The reason for it is the Android Gradle Plugin, which requires Java 11 to build anything.
1927
2028
## Running in AS
2129

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

utbot-framework-api/src/main/kotlin/org/utbot/framework/JdkPathDefaultProvider.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,7 @@ import java.nio.file.Paths
66
open class JdkPathDefaultProvider: JdkPathProvider() {
77
override val jdkPath: Path
88
get() = Paths.get(System.getProperty("java.home"))
9+
10+
override val jdkVersion: String
11+
get() = System.getProperty("java.version")
912
}

utbot-framework-api/src/main/kotlin/org/utbot/framework/JdkPathProvider.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,6 @@ abstract class JdkPathProvider {
99
}
1010

1111
abstract val jdkPath: Path
12+
13+
abstract val jdkVersion: String
1214
}

utbot-framework-api/src/main/kotlin/org/utbot/framework/JdkPathService.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,7 @@ object JdkPathService {
1616
// Kotlin delegates do not support changing in runtime, so use simple getter
1717
val jdkPath: Path
1818
get() = jdkPathProvider.jdkPath
19+
20+
val jdkVersion: String
21+
get() = jdkPathProvider.jdkVersion
1922
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -402,13 +402,14 @@ object CodeGenerationController {
402402

403403
destinationWarningMessage(model.testPackageName, classUnderTestPackageName)
404404
?.let {
405+
hasWarnings = true
405406
appendHtmlLine(it)
406407
appendHtmlLine()
407408
}
408409

409410
appendHtmlLine(eventLogMessage())
410411
}
411-
hasWarnings = report.hasWarnings
412+
hasWarnings = hasWarnings || report.hasWarnings
412413
Pair(message, report.detailedStatistics)
413414
} else {
414415
val accumulatedReport = reports.first()

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

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import org.utbot.framework.plugin.api.util.withUtContext
3737
import org.utbot.intellij.plugin.generator.CodeGenerationController.generateTests
3838
import org.utbot.intellij.plugin.models.GenerateTestsModel
3939
import org.utbot.intellij.plugin.ui.GenerateTestsDialogWindow
40+
import org.utbot.intellij.plugin.ui.utils.showErrorDialogLater
4041
import org.utbot.intellij.plugin.util.IntelliJApiHelper
4142
import org.utbot.intellij.plugin.util.PluginJdkPathProvider
4243
import org.utbot.intellij.plugin.util.signature
@@ -51,11 +52,9 @@ import org.utbot.engine.util.mockListeners.ForceStaticMockListener
5152
import org.utbot.framework.plugin.api.testFlow
5253
import org.utbot.intellij.plugin.settings.Settings
5354
import org.utbot.intellij.plugin.ui.utils.isGradle
54-
import org.utbot.intellij.plugin.ui.utils.jdkVersion
55-
import org.utbot.intellij.plugin.ui.utils.showErrorDialogLater
5655
import org.utbot.intellij.plugin.ui.utils.suitableTestSourceRoots
57-
import org.utbot.intellij.plugin.ui.utils.testModule
5856
import org.utbot.intellij.plugin.util.isAbstract
57+
import org.utbot.intellij.plugin.ui.utils.testModules
5958
import kotlin.reflect.KClass
6059
import kotlin.reflect.full.functions
6160

@@ -79,18 +78,11 @@ object UtTestsDialogProcessor {
7978
focusedMethod: MemberInfo?,
8079
): GenerateTestsDialogWindow? {
8180
val srcModule = findSrcModule(srcClasses)
82-
val testModule = srcModule.testModule(project)
83-
84-
JdkPathService.jdkPathProvider = PluginJdkPathProvider(project, testModule)
85-
val jdkVersion = try {
86-
testModule.jdkVersion()
87-
} catch (e: IllegalStateException) {
88-
// Just ignore it here, notification will be shown in
89-
// org.utbot.intellij.plugin.ui.utils.ModuleUtilsKt.jdkVersionBy
90-
return null
91-
}
81+
val testModules = srcModule.testModules(project)
9282

93-
if (project.isGradle() && testModule.suitableTestSourceRoots().isEmpty()) {
83+
JdkPathService.jdkPathProvider = PluginJdkPathProvider(project)
84+
85+
if (project.isGradle() && testModules.flatMap { it.suitableTestSourceRoots() }.isEmpty()) {
9486
val errorMessage = """
9587
<html>No test source roots found in the project.<br>
9688
Please, <a href="https://www.jetbrains.com/help/idea/testing.html#add-test-root">create or configure</a> at least one test source root.
@@ -103,8 +95,7 @@ object UtTestsDialogProcessor {
10395
GenerateTestsModel(
10496
project,
10597
srcModule,
106-
testModule,
107-
jdkVersion,
98+
testModules,
10899
srcClasses,
109100
if (focusedMethod != null) setOf(focusedMethod) else null,
110101
UtSettings.utBotGenerationTimeoutInMillis,
@@ -296,12 +287,6 @@ object UtTestsDialogProcessor {
296287
val pathsList = OrderEnumerator.orderEntries(srcModule).recursively().pathsList
297288

298289
val (classpath, classpathList) = if (IntelliJApiHelper.isAndroidStudio()) {
299-
// Add $JAVA_HOME/jre/lib/rt.jar to path.
300-
// This allows Soot to analyze real java instead of stub version in Android SDK on local machine.
301-
pathsList.add(
302-
System.getenv("JAVA_HOME") + File.separator + Paths.get("jre", "lib", "rt.jar")
303-
)
304-
305290
// Filter out manifests from classpath.
306291
val filterPredicate = { it: String ->
307292
!it.contains("manifest", ignoreCase = true)

utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/models/GenerateTestsModel.kt

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,38 @@ import org.utbot.framework.plugin.api.CodegenLanguage
1111
import org.utbot.framework.plugin.api.MockFramework
1212
import org.utbot.framework.plugin.api.MockStrategyApi
1313
import com.intellij.openapi.module.Module
14+
import com.intellij.openapi.module.ModuleUtil
1415
import com.intellij.openapi.project.Project
1516
import com.intellij.openapi.projectRoots.JavaSdkVersion
1617
import com.intellij.openapi.vfs.VirtualFile
1718
import com.intellij.psi.PsiClass
1819
import com.intellij.refactoring.util.classMembers.MemberInfo
1920
import org.jetbrains.kotlin.idea.core.getPackage
2021
import org.utbot.framework.util.ConflictTriggers
22+
import org.utbot.intellij.plugin.ui.utils.jdkVersion
2123

2224
data class GenerateTestsModel(
2325
val project: Project,
2426
val srcModule: Module,
25-
val testModule: Module,
26-
val jdkVersion: JavaSdkVersion,
27+
val potentialTestModules: List<Module>,
2728
var srcClasses: Set<PsiClass>,
2829
var selectedMethods: Set<MemberInfo>?,
2930
var timeout:Long,
3031
var generateWarningsForStaticMocking: Boolean = false,
3132
var fuzzingValue: Double = 0.05
3233
) {
34+
// GenerateTestsModel is supposed to be created with non-empty list of potentialTestModules.
35+
// Otherwise, the error window is supposed to be shown earlier.
36+
var testModule: Module = potentialTestModules.firstOrNull() ?: error("Empty list of test modules in model")
37+
3338
var testSourceRoot: VirtualFile? = null
39+
fun setSourceRootAndFindTestModule(newTestSourceRoot: VirtualFile?) {
40+
requireNotNull(newTestSourceRoot)
41+
testSourceRoot = newTestSourceRoot
42+
testModule = ModuleUtil.findModuleForFile(newTestSourceRoot, project)
43+
?: error("Could not find module for $newTestSourceRoot")
44+
}
45+
3446
var testPackageName: String? = null
3547
lateinit var testFramework: TestFramework
3648
lateinit var mockStrategy: MockStrategyApi
@@ -49,6 +61,14 @@ data class GenerateTestsModel(
4961
srcClasses.map { it.packageName }.distinct().size != 1
5062
}
5163
var runGeneratedTestsWithCoverage : Boolean = false
64+
65+
val jdkVersion: JavaSdkVersion?
66+
get() = try {
67+
testModule.jdkVersion()
68+
} catch (e: IllegalStateException) {
69+
// Just ignore it here, notification will be shown in org.utbot.intellij.plugin.ui.utils.ModuleUtilsKt.jdkVersionBy
70+
null
71+
}
5272
}
5373

5474
val PsiClass.packageName: String get() = this.containingFile.containingDirectory.getPackage()?.qualifiedName ?: ""

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -561,8 +561,9 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m
561561
* Creates test source root if absent and target packages for tests.
562562
*/
563563
private fun createTestRootAndPackages(): Boolean {
564-
model.testSourceRoot = createDirectoryIfMissing(model.testSourceRoot)
564+
model.setSourceRootAndFindTestModule(createDirectoryIfMissing(model.testSourceRoot))
565565
val testSourceRoot = model.testSourceRoot ?: return false
566+
566567
if (model.testSourceRoot?.isDirectory != true) return false
567568
if (getOrCreateTestRoot(testSourceRoot)) {
568569
if (cbSpecifyTestPackage.isSelected) {
@@ -860,10 +861,10 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m
860861
testSourceFolderField.childComponent.addActionListener { event ->
861862
with((event.source as JComboBox<*>).selectedItem) {
862863
if (this is VirtualFile) {
863-
model.testSourceRoot = this@with
864+
model.setSourceRootAndFindTestModule(this@with)
864865
}
865866
else {
866-
model.testSourceRoot = null
867+
model.setSourceRootAndFindTestModule(null)
867868
}
868869
}
869870
}

utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/components/TestFolderComboWithBrowseButton.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,11 @@ class TestFolderComboWithBrowseButton(private val model: GenerateTestsModel) : C
5050
}
5151
}
5252

53-
val testRoots = model.testModule.suitableTestSourceRoots().toMutableList()
53+
val testRoots = model.potentialTestModules.flatMap { it.suitableTestSourceRoots().toMutableList() }.toMutableList()
54+
55+
// this method is blocked for Gradle, where multiple test modules can exist
5456
model.testModule.addDedicatedTestRoot(testRoots)
57+
5558
if (testRoots.isNotEmpty()) {
5659
configureRootsCombo(testRoots)
5760
} else {
@@ -61,7 +64,7 @@ class TestFolderComboWithBrowseButton(private val model: GenerateTestsModel) : C
6164
addActionListener {
6265
val testSourceRoot = createNewTestSourceRoot(model)
6366
testSourceRoot?.let {
64-
model.testSourceRoot = it
67+
model.setSourceRootAndFindTestModule(it)
6568

6669
if (childComponent.itemCount == 1 && childComponent.selectedItem == SET_TEST_FOLDER) {
6770
newItemList(setOf(it))
@@ -88,6 +91,7 @@ class TestFolderComboWithBrowseButton(private val model: GenerateTestsModel) : C
8891
// unfortunately, Gradle creates Kotlin test source root with Java source root type, so type is misleading
8992
val selectedRoot = testRoots.first()
9093

94+
// do not update model.testModule here, because fake test source root could have been chosen
9195
model.testSourceRoot = selectedRoot
9296
newItemList(testRoots.toSet())
9397
}

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

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -83,35 +83,37 @@ fun Module.getOrCreateSarifReportsPath(testSourceRoot: VirtualFile?): Path {
8383
}
8484

8585
/**
86-
* Find test module by current source module.
86+
* Find test modules by current source module.
8787
*/
88-
fun Module.testModule(project: Project): Module {
89-
var testModule = findPotentialModuleForTests(project, this)
90-
val testRootUrls = testModule.suitableTestSourceRoots()
88+
fun Module.testModules(project: Project): List<Module> {
89+
var testModules = findPotentialModulesForTests(project, this)
90+
val testRootUrls = testModules.flatMap { it.suitableTestSourceRoots() }
9191

9292
//if no suitable module for tests is found, create tests in the same root
93-
if (testRootUrls.isEmpty() && testModule.suitableTestSourceFolders().isEmpty()) {
94-
testModule = this
93+
if (testRootUrls.isEmpty() && testModules.flatMap { it.suitableTestSourceFolders() }.isEmpty()) {
94+
testModules = listOf(this)
9595
}
96-
return testModule
96+
return testModules
9797
}
9898

99-
private fun findPotentialModuleForTests(project: Project, srcModule: Module): Module {
99+
private fun findPotentialModulesForTests(project: Project, srcModule: Module): List<Module> {
100+
val modules = mutableListOf<Module>()
100101
for (module in ModuleManager.getInstance(project).modules) {
101102
if (srcModule == TestModuleProperties.getInstance(module).productionModule) {
102-
return module
103+
modules += module
103104
}
104105
}
106+
if (modules.isNotEmpty()) return modules
105107

106108
if (srcModule.suitableTestSourceFolders().isEmpty()) {
107109
val modules = mutableSetOf<Module>()
108110
ModuleUtilCore.collectModulesDependsOn(srcModule, modules)
109111
modules.remove(srcModule)
110112

111113
val modulesWithTestRoot = modules.filter { it.suitableTestSourceFolders().isNotEmpty() }
112-
if (modulesWithTestRoot.size == 1) return modulesWithTestRoot[0]
114+
if (modulesWithTestRoot.size == 1) return modulesWithTestRoot
113115
}
114-
return srcModule
116+
return listOf(srcModule)
115117
}
116118

117119
/**

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

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,35 @@ package org.utbot.intellij.plugin.util
22

33
import org.utbot.common.PathUtil.toPath
44
import org.utbot.framework.JdkPathDefaultProvider
5-
import com.intellij.openapi.module.Module
65
import com.intellij.openapi.project.Project
76
import com.intellij.openapi.projectRoots.ProjectJdkTable
8-
import com.intellij.openapi.roots.ModuleRootManager
7+
import com.intellij.openapi.projectRoots.Sdk
98
import com.intellij.openapi.roots.ProjectRootManager
109
import java.nio.file.Path
1110

1211
class PluginJdkPathProvider(
13-
private val project: Project,
14-
private val testModule: Module,
12+
private val project: Project
1513
) : JdkPathDefaultProvider() {
1614

17-
override val jdkPath: Path
18-
get() =
15+
private val sdk: Sdk?
16+
get() {
1917
if (IntelliJApiHelper.isAndroidStudio()) {
2018
// Get Gradle JDK for Android
2119
IntelliJApiHelper.androidGradleSDK(project)
2220
?.let { sdkName ->
23-
ProjectJdkTable.getInstance().findJdk(sdkName)?.homePath?.toPath()
21+
ProjectJdkTable.getInstance().findJdk(sdkName) ?.let {
22+
return it
23+
}
2424
}
25-
} else {
26-
// Use testModule JDK (or Project SDK) as analyzed JDK
27-
(ModuleRootManager.getInstance(testModule).sdk
28-
?.homePath ?: ProjectRootManager.getInstance(project).projectSdk?.homePath)
29-
?.toPath()
30-
} ?: super.jdkPath // Return default JDK in case of failure
25+
}
26+
27+
// Use Project SDK as analyzed JDK
28+
return ProjectRootManager.getInstance(project).projectSdk
29+
}
30+
31+
override val jdkPath: Path
32+
get() = sdk?.let { it.homePath?.toPath() } ?: super.jdkPath // Return default JDK in case of failure
3133

34+
override val jdkVersion: String
35+
get() = sdk?.versionString ?: super.jdkVersion // Return default JDK in case of failure
3236
}

0 commit comments

Comments
 (0)