Skip to content

Commit 58cccea

Browse files
committed
Add nested classes to ui
1 parent c8018a5 commit 58cccea

File tree

10 files changed

+148
-99
lines changed

10 files changed

+148
-99
lines changed

utbot-core/src/main/kotlin/org/utbot/common/KClassUtil.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package org.utbot.common
22

33
import java.lang.reflect.InvocationTargetException
44
import java.lang.reflect.Method
5+
import kotlin.reflect.KClass
56

67

78
val Class<*>.packageName: String get() = `package`?.name?:""
@@ -11,3 +12,6 @@ fun Method.invokeCatching(obj: Any?, args: List<Any?>) = try {
1112
} catch (e: InvocationTargetException) {
1213
Result.failure<Nothing>(e.targetException)
1314
}
15+
16+
val KClass<*>.allNestedClasses: List<KClass<*>>
17+
get() = listOf(this) + nestedClasses.flatMap { it.allNestedClasses }

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import org.jetbrains.kotlin.psi.psiUtil.startOffset
4444
import org.jetbrains.kotlin.scripting.resolve.classId
4545
import org.utbot.common.HTML_LINE_SEPARATOR
4646
import org.utbot.common.PathUtil.toHtmlLinkTag
47+
import org.utbot.common.allNestedClasses
4748
import org.utbot.common.appendHtmlLine
4849
import org.utbot.framework.codegen.Import
4950
import org.utbot.framework.codegen.ParametrizedTestSource
@@ -70,6 +71,7 @@ import org.utbot.intellij.plugin.ui.WarningTestsReportNotifier
7071
import org.utbot.intellij.plugin.ui.utils.getOrCreateSarifReportsPath
7172
import org.utbot.intellij.plugin.ui.utils.showErrorDialogLater
7273
import org.utbot.intellij.plugin.util.RunConfigurationHelper
74+
import org.utbot.intellij.plugin.util.extractClassMethodsIncludingNested
7375
import org.utbot.intellij.plugin.util.signature
7476
import org.utbot.sarif.SarifReport
7577
import java.nio.file.Path
@@ -82,7 +84,11 @@ import org.utbot.intellij.plugin.util.IntelliJApiHelper.run
8284

8385
object CodeGenerationController {
8486

85-
fun generateTests(model: GenerateTestsModel, testSetsByClass: Map<PsiClass, List<UtMethodTestSet>>) {
87+
fun generateTests(
88+
model: GenerateTestsModel,
89+
testSetsByClass: Map<PsiClass, List<UtMethodTestSet>>,
90+
psi2KClass: Map<PsiClass, KClass<*>>
91+
) {
8692
val baseTestDirectory = model.testSourceRoot?.toPsiDirectory(model.project)
8793
?: return
8894
val allTestPackages = getPackageDirectories(baseTestDirectory)
@@ -98,9 +104,10 @@ object CodeGenerationController {
98104
val testDirectory = allTestPackages[classPackageName] ?: baseTestDirectory
99105
val testClass = createTestClass(srcClass, testDirectory, model) ?: continue
100106
val file = testClass.containingFile
107+
val cut = psi2KClass[srcClass] ?: error("Didn't find KClass instance for class ${srcClass.name}")
101108
runWriteCommandAction(model.project, "Generate tests with UtBot", null, {
102109
try {
103-
generateCodeAndReport(srcClass, testClass, file, testSets, model, latch, reports)
110+
generateCodeAndReport(srcClass, cut, testClass, file, testSets, model, latch, reports)
104111
testFiles.add(file)
105112
} catch (e: IncorrectOperationException) {
106113
showCreatingClassError(model.project, createTestClassName(srcClass))
@@ -240,15 +247,15 @@ object CodeGenerationController {
240247

241248
private fun generateCodeAndReport(
242249
srcClass: PsiClass,
250+
classUnderTest: KClass<*>,
243251
testClass: PsiClass,
244252
file: PsiFile,
245253
testSets: List<UtMethodTestSet>,
246254
model: GenerateTestsModel,
247255
reportsCountDown: CountDownLatch,
248256
reports: MutableList<TestsGenerationReport>,
249257
) {
250-
val classUnderTest = testSets.first().method.clazz
251-
val classMethods = TestIntegrationUtils.extractClassMethods(srcClass, false)
258+
val classMethods = srcClass.extractClassMethodsIncludingNested(false)
252259
val paramNames = DumbService.getInstance(model.project)
253260
.runReadActionInSmartMode(Computable { findMethodParamNames(classUnderTest, classMethods) })
254261

@@ -339,7 +346,7 @@ object CodeGenerationController {
339346

340347
private fun findMethodParamNames(clazz: KClass<*>, methods: List<MemberInfo>): Map<ExecutableId, List<String>> {
341348
val bySignature = methods.associate { it.signature() to it.paramNames() }
342-
return clazz.functions
349+
return clazz.allNestedClasses.flatMap { it.functions }
343350
.mapNotNull { method -> bySignature[method.signature()]?.let { params -> method.executableId to params } }
344351
.toMap()
345352
}

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

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ import com.intellij.openapi.ui.Messages
1919
import com.intellij.openapi.util.Computable
2020
import com.intellij.openapi.util.text.StringUtil
2121
import com.intellij.psi.PsiClass
22-
import com.intellij.psi.SyntheticElement
22+
import com.intellij.psi.PsiMethod
2323
import com.intellij.refactoring.util.classMembers.MemberInfo
24-
import com.intellij.testIntegration.TestIntegrationUtils
2524
import com.intellij.util.concurrency.AppExecutorUtil
2625
import mu.KotlinLogging
2726
import org.jetbrains.kotlin.idea.util.module
27+
import org.utbot.common.allNestedClasses
2828
import org.utbot.engine.util.mockListeners.ForceMockListener
2929
import org.utbot.framework.plugin.services.JdkInfoService
3030
import org.utbot.framework.UtSettings
@@ -47,15 +47,15 @@ import java.net.URLClassLoader
4747
import java.nio.file.Path
4848
import java.nio.file.Paths
4949
import java.util.concurrent.TimeUnit
50-
import org.utbot.common.filterWhen
5150
import org.utbot.engine.util.mockListeners.ForceStaticMockListener
5251
import org.utbot.framework.plugin.api.testFlow
5352
import org.utbot.framework.plugin.services.WorkingDirService
53+
import org.utbot.intellij.plugin.models.packageName
5454
import org.utbot.intellij.plugin.settings.Settings
55+
import org.utbot.intellij.plugin.util.extractClassMethodsIncludingNested
5556
import org.utbot.intellij.plugin.ui.utils.isBuildWithGradle
5657
import org.utbot.intellij.plugin.ui.utils.suitableTestSourceRoots
5758
import org.utbot.intellij.plugin.util.PluginWorkingDirProvider
58-
import org.utbot.intellij.plugin.util.isAbstract
5959
import org.utbot.intellij.plugin.ui.utils.testModules
6060
import kotlin.reflect.KClass
6161
import kotlin.reflect.full.functions
@@ -67,16 +67,18 @@ object UtTestsDialogProcessor {
6767
fun createDialogAndGenerateTests(
6868
project: Project,
6969
srcClasses: Set<PsiClass>,
70+
extractMembersFromSrcClasses: Boolean,
7071
focusedMethod: MemberInfo?,
7172
) {
72-
createDialog(project, srcClasses, focusedMethod)?.let {
73+
createDialog(project, srcClasses, extractMembersFromSrcClasses, focusedMethod)?.let {
7374
if (it.showAndGet()) createTests(project, it.model)
7475
}
7576
}
7677

7778
private fun createDialog(
7879
project: Project,
7980
srcClasses: Set<PsiClass>,
81+
extractMembersFromSrcClasses: Boolean,
8082
focusedMethod: MemberInfo?,
8183
): GenerateTestsDialogWindow? {
8284
val srcModule = findSrcModule(srcClasses)
@@ -101,6 +103,7 @@ object UtTestsDialogProcessor {
101103
srcModule,
102104
testModules,
103105
srcClasses,
106+
extractMembersFromSrcClasses,
104107
if (focusedMethod != null) setOf(focusedMethod) else null,
105108
UtSettings.utBotGenerationTimeoutInMillis,
106109
)
@@ -140,6 +143,7 @@ object UtTestsDialogProcessor {
140143
val context = UtContext(classLoader)
141144

142145
val testSetsByClass = mutableMapOf<PsiClass, List<UtMethodTestSet>>()
146+
val psi2KClass = mutableMapOf<PsiClass, KClass<*>>()
143147
var processedClasses = 0
144148
val totalClasses = model.srcClasses.size
145149

@@ -152,15 +156,24 @@ object UtTestsDialogProcessor {
152156

153157
for (srcClass in model.srcClasses) {
154158
val methods = ReadAction.nonBlocking<List<UtMethod<*>>> {
155-
val clazz = classLoader.loadClass(srcClass.qualifiedName).kotlin
156-
val srcMethods = model.selectedMethods?.toList() ?:
157-
TestIntegrationUtils.extractClassMethods(srcClass, false)
158-
.filterWhen(UtSettings.skipTestGenerationForSyntheticMethods) {
159-
it.member !is SyntheticElement
160-
}
161-
.filterNot { it.isAbstract }
159+
val canonicalName = srcClass.canonicalName
160+
val clazz = classLoader.loadClass(canonicalName).kotlin
161+
psi2KClass[srcClass] = clazz
162+
163+
val srcMethods = if (model.extractMembersFromSrcClasses) {
164+
val chosenMethods = model.selectedMembers?.filter { it.member is PsiMethod } ?: listOf()
165+
val chosenNestedClasses = model.selectedMembers?.mapNotNull { it.member as? PsiClass } ?: listOf()
166+
chosenMethods + chosenNestedClasses.flatMap {
167+
it.extractClassMethodsIncludingNested(false)
168+
}
169+
} else {
170+
srcClass.extractClassMethodsIncludingNested(false)
171+
}
172+
162173
DumbService.getInstance(project).runReadActionInSmartMode(Computable {
163-
findMethodsInClassMatchingSelected(clazz, srcMethods)
174+
clazz.allNestedClasses.flatMap {
175+
findMethodsInClassMatchingSelected(it, srcMethods)
176+
}
164177
})
165178
}.executeSynchronously()
166179

@@ -249,7 +262,7 @@ object UtTestsDialogProcessor {
249262

250263
invokeLater {
251264
withUtContext(context) {
252-
generateTests(model, testSetsByClass)
265+
generateTests(model, testSetsByClass, psi2KClass)
253266
}
254267
}
255268
}
@@ -258,6 +271,19 @@ object UtTestsDialogProcessor {
258271
}
259272
}
260273

274+
private val PsiClass.canonicalName: String
275+
get() {
276+
return if (packageName.isEmpty()) {
277+
qualifiedName?.replace(".", "$") ?: ""
278+
} else {
279+
val name = qualifiedName
280+
?.substringAfter("$packageName.")
281+
?.replace(".", "$")
282+
?: ""
283+
"$packageName.$name"
284+
}
285+
}
286+
261287
private fun errorMessage(className: String?, timeout: Long) = buildString {
262288
appendLine("UtBot failed to generate any test cases for class $className.")
263289
appendLine()

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ data class GenerateTestsModel(
2626
val srcModule: Module,
2727
val potentialTestModules: List<Module>,
2828
var srcClasses: Set<PsiClass>,
29-
var selectedMethods: Set<MemberInfo>?,
30-
var timeout:Long,
29+
val extractMembersFromSrcClasses: Boolean,
30+
var selectedMembers: Set<MemberInfo>?, // TODO: maybe we should make it not nullable?
31+
var timeout: Long,
3132
var generateWarningsForStaticMocking: Boolean = false,
3233
var fuzzingValue: Double = 0.05
3334
) {
@@ -36,6 +37,7 @@ data class GenerateTestsModel(
3637
var testModule: Module = potentialTestModules.firstOrNull() ?: error("Empty list of test modules in model")
3738

3839
var testSourceRoot: VirtualFile? = null
40+
3941
fun setSourceRootAndFindTestModule(newTestSourceRoot: VirtualFile?) {
4042
requireNotNull(newTestSourceRoot)
4143
testSourceRoot = newTestSourceRoot

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

Lines changed: 17 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,11 @@ import com.intellij.openapi.vfs.newvfs.impl.FakeVirtualFile
3939
import com.intellij.openapi.wm.ToolWindowManager
4040
import com.intellij.psi.PsiClass
4141
import com.intellij.psi.PsiManager
42-
import com.intellij.psi.PsiMethod
43-
import com.intellij.psi.SyntheticElement
4442
import com.intellij.refactoring.PackageWrapper
4543
import com.intellij.refactoring.ui.MemberSelectionTable
4644
import com.intellij.refactoring.ui.PackageNameReferenceEditorCombo
4745
import com.intellij.refactoring.util.RefactoringUtil
4846
import com.intellij.refactoring.util.classMembers.MemberInfo
49-
import com.intellij.testIntegration.TestIntegrationUtils
5047
import com.intellij.ui.ColoredListCellRenderer
5148
import com.intellij.ui.ContextHelpLabel
5249
import com.intellij.ui.HyperlinkLabel
@@ -97,7 +94,6 @@ import org.jetbrains.concurrency.Promise
9794
import org.jetbrains.concurrency.thenRun
9895
import org.jetbrains.kotlin.asJava.classes.KtUltraLightClass
9996
import org.utbot.common.PathUtil.toPath
100-
import org.utbot.common.filterWhen
10197
import org.utbot.framework.UtSettings
10298
import org.utbot.framework.codegen.ForceStaticMocking
10399
import org.utbot.framework.codegen.Junit4
@@ -136,7 +132,7 @@ import org.utbot.intellij.plugin.ui.utils.parseVersion
136132
import org.utbot.intellij.plugin.ui.utils.testResourceRootTypes
137133
import org.utbot.intellij.plugin.ui.utils.testRootType
138134
import org.utbot.intellij.plugin.util.IntelliJApiHelper
139-
import org.utbot.intellij.plugin.util.isAbstract
135+
import org.utbot.intellij.plugin.util.extractFirstLevelMembers
140136

141137
private const val RECENTS_KEY = "org.utbot.recents"
142138

@@ -384,17 +380,14 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m
384380
private fun updateMembersTable() {
385381
val srcClasses = model.srcClasses
386382

387-
val items: List<MemberInfo>
388-
if (srcClasses.size == 1) {
389-
items = TestIntegrationUtils.extractClassMethods(srcClasses.single(), false)
390-
.filterWhen(UtSettings.skipTestGenerationForSyntheticMethods) { it.member !is SyntheticElement }
391-
.filterNot { it.isAbstract }
392-
updateMethodsTable(items)
383+
val items = if (model.extractMembersFromSrcClasses) {
384+
srcClasses.flatMap { it.extractFirstLevelMembers(false) }
393385
} else {
394-
items = srcClasses.map { MemberInfo(it) }
395-
updateClassesTable(items)
386+
srcClasses.map { MemberInfo(it) }
396387
}
397388

389+
checkMembers(items)
390+
membersTable.setMemberInfos(items)
398391
if (items.isEmpty()) isOKActionEnabled = false
399392

400393
// fix issue with MemberSelectionTable height, set it directly.
@@ -403,28 +396,14 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m
403396
membersTable.preferredScrollableViewportSize = size(-1, height)
404397
}
405398

406-
private fun updateMethodsTable(allMethods: List<MemberInfo>) {
407-
val selectedDisplayNames = model.selectedMethods?.map { it.displayName } ?: emptyList()
408-
val selectedMethods = if (selectedDisplayNames.isEmpty())
409-
allMethods
410-
else allMethods.filter { it.displayName in selectedDisplayNames }
399+
private fun checkMembers(allMembers: List<MemberInfo>) {
400+
val selectedDisplayNames = model.selectedMembers?.map { it.displayName } ?: emptyList()
401+
val selectedMembers = allMembers.filter { it.displayName in selectedDisplayNames }
411402

412-
if (selectedMethods.isEmpty()) {
413-
checkMembers(allMethods)
414-
} else {
415-
checkMembers(selectedMethods)
416-
}
417-
418-
membersTable.setMemberInfos(allMethods)
419-
}
420-
421-
private fun updateClassesTable(srcClasses: List<MemberInfo>) {
422-
checkMembers(srcClasses)
423-
membersTable.setMemberInfos(srcClasses)
403+
val methodsToCheck = selectedMembers.ifEmpty { allMembers }
404+
methodsToCheck.forEach { it.isChecked = true }
424405
}
425406

426-
private fun checkMembers(members: List<MemberInfo>) = members.forEach { it.isChecked = true }
427-
428407
private fun getTestRoot() : VirtualFile? {
429408
model.testSourceRoot?.let {
430409
if (it.isDirectory || it is FakeVirtualFile) return it
@@ -497,12 +476,12 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m
497476
if (testPackageField.text != SAME_PACKAGE_LABEL) testPackageField.text else ""
498477

499478
val selectedMembers = membersTable.selectedMemberInfos
500-
model.srcClasses = selectedMembers
501-
.mapNotNull { it.member as? PsiClass ?: it.member.containingClass }
502-
.toSet()
503-
504-
val selectedMethods = selectedMembers.filter { it.member is PsiMethod }.toSet()
505-
model.selectedMethods = if (selectedMethods.any()) selectedMethods else null
479+
if (!model.extractMembersFromSrcClasses) {
480+
model.srcClasses = selectedMembers
481+
.mapNotNull { it.member as? PsiClass }
482+
.toSet()
483+
}
484+
model.selectedMembers = selectedMembers.toSet()
506485

507486
model.testFramework = testFrameworks.item
508487
model.mockStrategy = mockStrategies.item

0 commit comments

Comments
 (0)