Skip to content

Commit 7f195ef

Browse files
committed
Add nested classes to ui/cli
1 parent 2e2d62a commit 7f195ef

File tree

13 files changed

+155
-104
lines changed

13 files changed

+155
-104
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-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/TestClassModel.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@ data class TestClassModel(
2424
.map { it.executableId.classId }
2525

2626
// For each class stores list of its "direct" nested classes
27-
val class2nestedClasses = mutableMapOf<ClassId, MutableList<ClassId>>()
27+
val class2nestedClasses = mutableMapOf<ClassId, MutableSet<ClassId>>()
2828

2929
for (classId in classesWithMethodsUnderTest) {
3030
var currentClass = classId
3131
var enclosingClass = currentClass.enclosingClass
3232
// while we haven't reached the top of nested class hierarchy or the main class under test
3333
while (enclosingClass != null && currentClass != classUnderTest) {
34-
class2nestedClasses.getOrPut(enclosingClass) { mutableListOf() } += currentClass
34+
class2nestedClasses.getOrPut(enclosingClass) { mutableSetOf() } += currentClass
3535
currentClass = enclosingClass
3636
enclosingClass = enclosingClass.enclosingClass
3737
}
@@ -42,7 +42,7 @@ data class TestClassModel(
4242
private fun constructRecursively(
4343
clazz: ClassId,
4444
class2methodTestSets: Map<ClassId, List<CgMethodTestSet>>,
45-
class2nestedClasses: Map<ClassId, List<ClassId>>
45+
class2nestedClasses: Map<ClassId, Set<ClassId>>
4646
): TestClassModel {
4747
val currentNestedClasses = class2nestedClasses.getOrDefault(clazz, listOf())
4848
val currentMethodTestSets = class2methodTestSets.getOrDefault(clazz, listOf())

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,7 @@ internal data class CgContext(
449449

450450
override val outerMostTestClass: ClassId by lazy {
451451
val packagePrefix = if (testClassPackageName.isNotEmpty()) "$testClassPackageName." else ""
452-
val simpleName = testClassCustomName ?: "${createTestClassName(classUnderTest.name)}Test"
452+
val simpleName = testClassCustomName ?: "${createTestClassName(classUnderTest.simpleName)}Test"
453453
val name = "$packagePrefix$simpleName"
454454
BuiltinClassId(
455455
name = name,

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+
psi2Class: 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 = psi2Class[srcClass] ?: error("AAAA")
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.isGradle
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 psi2Class = 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+
psi2Class[srcClass] = clazz
162+
163+
val srcMethods = if (model.extractMembersFromSrcClasses) {
164+
val chosenMethods = model.selectedMembers?.filter { it.member is PsiMethod } ?: listOf()
165+
val nestedClasses = model.selectedMembers?.mapNotNull { it.member as? PsiClass } ?: listOf()
166+
chosenMethods + nestedClasses.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, psi2Class)
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
@@ -135,7 +131,7 @@ import org.utbot.intellij.plugin.ui.utils.parseVersion
135131
import org.utbot.intellij.plugin.ui.utils.testResourceRootTypes
136132
import org.utbot.intellij.plugin.ui.utils.testRootType
137133
import org.utbot.intellij.plugin.util.IntelliJApiHelper
138-
import org.utbot.intellij.plugin.util.isAbstract
134+
import org.utbot.intellij.plugin.util.extractRelevantMembers
139135

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

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

386-
val items: List<MemberInfo>
387-
if (srcClasses.size == 1) {
388-
items = TestIntegrationUtils.extractClassMethods(srcClasses.single(), false)
389-
.filterWhen(UtSettings.skipTestGenerationForSyntheticMethods) { it.member !is SyntheticElement }
390-
.filterNot { it.isAbstract }
391-
updateMethodsTable(items)
382+
val items = if (model.extractMembersFromSrcClasses) {
383+
extractRelevantMembers(srcClasses.single(), false)
392384
} else {
393-
items = srcClasses.map { MemberInfo(it) }
394-
updateClassesTable(items)
385+
srcClasses.map { MemberInfo(it) }
395386
}
396387

388+
checkMembers(items)
389+
membersTable.setMemberInfos(items)
397390
if (items.isEmpty()) isOKActionEnabled = false
398391

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

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

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

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

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

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

0 commit comments

Comments
 (0)