Skip to content

Commit 2e2d62a

Browse files
authored
Fix codegen to generate tests for nested classes into nested testclasses #560 (#665)
1 parent c38666c commit 2e2d62a

29 files changed

+801
-377
lines changed

utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import org.utbot.framework.plugin.api.util.isPrimitive
2828
import org.utbot.framework.plugin.api.util.jClass
2929
import org.utbot.framework.plugin.api.util.longClassId
3030
import org.utbot.framework.plugin.api.util.method
31-
import org.utbot.framework.plugin.api.util.objectClassId
3231
import org.utbot.framework.plugin.api.util.primitiveTypeJvmNameOrNull
3332
import org.utbot.framework.plugin.api.util.safeJField
3433
import org.utbot.framework.plugin.api.util.shortClassId

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/Domain.kt

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ package org.utbot.framework.codegen
33
import org.utbot.framework.DEFAULT_CONCRETE_EXECUTION_TIMEOUT_IN_CHILD_PROCESS_MS
44
import org.utbot.framework.codegen.model.constructor.builtin.mockitoClassId
55
import org.utbot.framework.codegen.model.constructor.builtin.ongoingStubbingClassId
6+
import org.utbot.framework.codegen.model.constructor.util.argumentsClassId
67
import org.utbot.framework.codegen.model.tree.CgClassId
78
import org.utbot.framework.plugin.api.BuiltinClassId
89
import org.utbot.framework.plugin.api.ClassId
910
import org.utbot.framework.plugin.api.CodeGenerationSettingBox
1011
import org.utbot.framework.plugin.api.CodeGenerationSettingItem
1112
import org.utbot.framework.plugin.api.MethodId
13+
import org.utbot.framework.plugin.api.TypeParameters
1214
import org.utbot.framework.plugin.api.isolateCommandLineArgumentsToArgumentFile
1315
import org.utbot.framework.plugin.api.util.booleanArrayClassId
1416
import org.utbot.framework.plugin.api.util.booleanClassId
@@ -28,6 +30,7 @@ import org.utbot.framework.plugin.api.util.shortArrayClassId
2830
import org.utbot.framework.plugin.api.util.voidClassId
2931
import java.io.File
3032
import org.utbot.framework.plugin.api.util.longClassId
33+
import org.utbot.framework.plugin.api.util.objectArrayClassId
3134
import org.utbot.framework.plugin.api.util.voidWrapperClassId
3235

3336
data class TestClassFile(val packageName: String, val imports: List<Import>, val testClass: String)
@@ -183,6 +186,8 @@ sealed class TestFramework(
183186
abstract val methodSourceAnnotation: String
184187
abstract val methodSourceAnnotationId: ClassId
185188
abstract val methodSourceAnnotationFqn: String
189+
abstract val nestedClassesShouldBeStatic: Boolean
190+
abstract val argListClassId: ClassId
186191

187192
val assertEquals by lazy { assertionId("assertEquals", objectClassId, objectClassId) }
188193

@@ -301,6 +306,30 @@ object TestNg : TestFramework(displayName = "TestNG") {
301306
simpleName = "DataProvider"
302307
)
303308

309+
override val nestedClassesShouldBeStatic = true
310+
311+
override val argListClassId: ClassId
312+
get() {
313+
val outerArrayId = Array<Array<Any?>?>::class.id
314+
val innerArrayId = BuiltinClassId(
315+
name = objectArrayClassId.name,
316+
simpleName = objectArrayClassId.simpleName,
317+
canonicalName = objectArrayClassId.canonicalName,
318+
packageName = objectArrayClassId.packageName,
319+
elementClassId = objectClassId,
320+
typeParameters = TypeParameters(listOf(objectClassId))
321+
)
322+
323+
return BuiltinClassId(
324+
name = outerArrayId.name,
325+
simpleName = outerArrayId.simpleName,
326+
canonicalName = outerArrayId.canonicalName,
327+
packageName = outerArrayId.packageName,
328+
elementClassId = innerArrayId,
329+
typeParameters = TypeParameters(listOf(innerArrayId))
330+
)
331+
}
332+
304333
@OptIn(ExperimentalStdlibApi::class)
305334
override fun getRunTestsCommand(
306335
executionInvoke: String,
@@ -346,14 +375,21 @@ object TestNg : TestFramework(displayName = "TestNG") {
346375
}
347376

348377
object Junit4 : TestFramework("JUnit4") {
378+
private val parametrizedTestsNotSupportedError: Nothing
379+
get() = error("Parametrized tests are not supported for JUnit4")
380+
349381
override val mainPackage: String = JUNIT4_PACKAGE
350382
override val testAnnotation = "@$mainPackage.Test"
351383
override val testAnnotationFqn: String = "$mainPackage.Test"
352384

353-
override val parameterizedTestAnnotation = "Parameterized tests are not supported for JUnit4"
354-
override val parameterizedTestAnnotationFqn = "Parameterized tests are not supported for JUnit4"
355-
override val methodSourceAnnotation = "Parameterized tests are not supported for JUnit4"
356-
override val methodSourceAnnotationFqn = "Parameterized tests are not supported for JUnit4"
385+
override val parameterizedTestAnnotation
386+
get() = parametrizedTestsNotSupportedError
387+
override val parameterizedTestAnnotationFqn
388+
get() = parametrizedTestsNotSupportedError
389+
override val methodSourceAnnotation
390+
get() = parametrizedTestsNotSupportedError
391+
override val methodSourceAnnotationFqn
392+
get() = parametrizedTestsNotSupportedError
357393

358394
override val testAnnotationId = BuiltinClassId(
359395
name = "$JUNIT4_PACKAGE.Test",
@@ -385,6 +421,17 @@ object Junit4 : TestFramework("JUnit4") {
385421
)
386422
}
387423

424+
val enclosedClassId = BuiltinClassId(
425+
name = "org.junit.experimental.runners.Enclosed",
426+
canonicalName = "org.junit.experimental.runners.Enclosed",
427+
simpleName = "Enclosed"
428+
)
429+
430+
override val nestedClassesShouldBeStatic = true
431+
432+
override val argListClassId: ClassId
433+
get() = parametrizedTestsNotSupportedError
434+
388435
@OptIn(ExperimentalStdlibApi::class)
389436
override fun getRunTestsCommand(
390437
executionInvoke: String,
@@ -440,6 +487,12 @@ object Junit5 : TestFramework("JUnit5") {
440487
arguments = arrayOf(longClassId)
441488
)
442489

490+
val nestedTestClassAnnotationId = BuiltinClassId(
491+
name = "$JUNIT5_PACKAGE.Nested",
492+
canonicalName = "$JUNIT5_PACKAGE.Nested",
493+
simpleName = "Nested"
494+
)
495+
443496
override val testAnnotationId = BuiltinClassId(
444497
name = "$JUNIT5_PACKAGE.Test",
445498
canonicalName = "$JUNIT5_PACKAGE.Test",
@@ -501,6 +554,20 @@ object Junit5 : TestFramework("JUnit5") {
501554
)
502555
}
503556

557+
override val nestedClassesShouldBeStatic = false
558+
559+
override val argListClassId: ClassId
560+
get() {
561+
val arrayListId = java.util.ArrayList::class.id
562+
return BuiltinClassId(
563+
name = arrayListId.name,
564+
simpleName = arrayListId.simpleName,
565+
canonicalName = arrayListId.canonicalName,
566+
packageName = arrayListId.packageName,
567+
typeParameters = TypeParameters(listOf(argumentsClassId))
568+
)
569+
}
570+
504571
private const val junitVersion = "1.7.1" // TODO read it from gradle.properties
505572
private const val platformJarName: String = "junit-platform-console-standalone-$junitVersion.jar"
506573

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import org.utbot.framework.plugin.api.CodegenLanguage
1717
import org.utbot.framework.plugin.api.ExecutableId
1818
import org.utbot.framework.plugin.api.MockFramework
1919
import org.utbot.framework.plugin.api.UtMethodTestSet
20+
import org.utbot.framework.codegen.model.constructor.TestClassModel
2021

2122
class CodeGenerator(
2223
private val classUnderTest: ClassId,
@@ -62,12 +63,13 @@ class CodeGenerator(
6263
return generateAsStringWithTestReport(cgTestSets, testClassCustomName)
6364
}
6465

65-
fun generateAsStringWithTestReport(
66+
private fun generateAsStringWithTestReport(
6667
cgTestSets: List<CgMethodTestSet>,
6768
testClassCustomName: String? = null,
6869
): TestsCodeWithTestReport = withCustomContext(testClassCustomName) {
69-
context.withClassScope {
70-
val testClassFile = CgTestClassConstructor(context).construct(cgTestSets)
70+
context.withTestClassFileScope {
71+
val testClassModel = TestClassModel.fromTestSets(classUnderTest, cgTestSets)
72+
val testClassFile = CgTestClassConstructor(context).construct(testClassModel)
7173
TestsCodeWithTestReport(renderClassFile(testClassFile), testClassFile.testsGenerationReport)
7274
}
7375
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package org.utbot.framework.codegen.model.constructor
2+
3+
import org.utbot.framework.codegen.model.constructor.context.CgContextOwner
4+
import org.utbot.framework.codegen.model.tree.CgAnnotation
5+
import org.utbot.framework.codegen.model.tree.CgMethod
6+
import org.utbot.framework.plugin.api.ClassId
7+
import org.utbot.framework.codegen.model.tree.CgTestClass
8+
9+
/**
10+
* This class stores context information needed to build [CgTestClass].
11+
* Should only be used in [CgContextOwner].
12+
*/
13+
internal data class TestClassContext(
14+
// set of interfaces that the test class must inherit
15+
val collectedTestClassInterfaces: MutableSet<ClassId> = mutableSetOf(),
16+
17+
// set of annotations of the test class
18+
val collectedTestClassAnnotations: MutableSet<CgAnnotation> = mutableSetOf(),
19+
20+
// list of data provider methods that test class must implement
21+
val cgDataProviderMethods: MutableList<CgMethod> = mutableListOf(),
22+
) {
23+
// test class superclass (if needed)
24+
var testClassSuperclass: ClassId? = null
25+
set(value) {
26+
// Assigning a value to the testClassSuperclass when it is already non-null
27+
// means that we need the test class to have more than one superclass
28+
// which is impossible in Java and Kotlin.
29+
require(value == null || field == null) { "It is impossible for the test class to have more than one superclass" }
30+
field = value
31+
}
32+
33+
fun clear() {
34+
collectedTestClassAnnotations.clear()
35+
collectedTestClassInterfaces.clear()
36+
cgDataProviderMethods.clear()
37+
testClassSuperclass = null
38+
}
39+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package org.utbot.framework.codegen.model.constructor
2+
3+
import org.utbot.framework.plugin.api.ClassId
4+
import org.utbot.framework.plugin.api.util.enclosingClass
5+
6+
// TODO: seems like this class needs to be renamed
7+
/**
8+
* Stores method testsets in a structure that replicates structure of their methods in [classUnderTest].
9+
* I.e., if some method is declared in nested class of [classUnderTest], its testset will be put
10+
* in [TestClassModel] in one of [nestedClasses]
11+
*/
12+
data class TestClassModel(
13+
val classUnderTest: ClassId,
14+
val methodTestSets: List<CgMethodTestSet>,
15+
val nestedClasses: List<TestClassModel> = listOf()
16+
) {
17+
companion object {
18+
fun fromTestSets(classUnderTest: ClassId, testSets: List<CgMethodTestSet>): TestClassModel {
19+
// For each class stores list of methods declared in this class (methods from nested classes are excluded)
20+
val class2methodTestSets = testSets.groupBy { it.executableId.classId }
21+
22+
val classesWithMethodsUnderTest = testSets
23+
.distinctBy { it.executableId.classId }
24+
.map { it.executableId.classId }
25+
26+
// For each class stores list of its "direct" nested classes
27+
val class2nestedClasses = mutableMapOf<ClassId, MutableList<ClassId>>()
28+
29+
for (classId in classesWithMethodsUnderTest) {
30+
var currentClass = classId
31+
var enclosingClass = currentClass.enclosingClass
32+
// while we haven't reached the top of nested class hierarchy or the main class under test
33+
while (enclosingClass != null && currentClass != classUnderTest) {
34+
class2nestedClasses.getOrPut(enclosingClass) { mutableListOf() } += currentClass
35+
currentClass = enclosingClass
36+
enclosingClass = enclosingClass.enclosingClass
37+
}
38+
}
39+
return constructRecursively(classUnderTest, class2methodTestSets, class2nestedClasses)
40+
}
41+
42+
private fun constructRecursively(
43+
clazz: ClassId,
44+
class2methodTestSets: Map<ClassId, List<CgMethodTestSet>>,
45+
class2nestedClasses: Map<ClassId, List<ClassId>>
46+
): TestClassModel {
47+
val currentNestedClasses = class2nestedClasses.getOrDefault(clazz, listOf())
48+
val currentMethodTestSets = class2methodTestSets.getOrDefault(clazz, listOf())
49+
return TestClassModel(
50+
clazz,
51+
currentMethodTestSets,
52+
currentNestedClasses.map {
53+
constructRecursively(it, class2methodTestSets, class2nestedClasses)
54+
}
55+
)
56+
}
57+
}
58+
}

0 commit comments

Comments
 (0)