Skip to content

Commit 94833f3

Browse files
committed
Fix codegen to generate tests for nested classes into nested testclasses
1 parent 1321946 commit 94833f3

File tree

28 files changed

+428
-156
lines changed

28 files changed

+428
-156
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/external/api/UtBotJavaApi.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import org.utbot.framework.plugin.api.MockFramework
1818
import org.utbot.framework.plugin.api.MockStrategyApi
1919
import org.utbot.framework.plugin.api.TestCaseGenerator
2020
import org.utbot.framework.plugin.api.UtExecution
21-
import org.utbot.framework.plugin.api.UtExecutionCreator
2221
import org.utbot.framework.plugin.api.UtMethod
2322
import org.utbot.framework.plugin.api.UtPrimitiveModel
2423
import org.utbot.framework.plugin.api.UtMethodTestSet

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ sealed class TestFramework(
183183
abstract val methodSourceAnnotation: String
184184
abstract val methodSourceAnnotationId: ClassId
185185
abstract val methodSourceAnnotationFqn: String
186+
abstract val nestedClassesShouldBeStatic: Boolean
186187

187188
val assertEquals by lazy { assertionId("assertEquals", objectClassId, objectClassId) }
188189

@@ -301,6 +302,8 @@ object TestNg : TestFramework(displayName = "TestNG") {
301302
simpleName = "DataProvider"
302303
)
303304

305+
override val nestedClassesShouldBeStatic = true
306+
304307
@OptIn(ExperimentalStdlibApi::class)
305308
override fun getRunTestsCommand(
306309
executionInvoke: String,
@@ -385,6 +388,9 @@ object Junit4 : TestFramework("JUnit4") {
385388
)
386389
}
387390

391+
392+
override val nestedClassesShouldBeStatic = true
393+
388394
@OptIn(ExperimentalStdlibApi::class)
389395
override fun getRunTestsCommand(
390396
executionInvoke: String,
@@ -440,6 +446,12 @@ object Junit5 : TestFramework("JUnit5") {
440446
arguments = arrayOf(longClassId)
441447
)
442448

449+
val nestedTestClassAnnotationId = BuiltinClassId(
450+
name = "$JUNIT5_PACKAGE.Nested",
451+
canonicalName = "$JUNIT5_PACKAGE.Nested",
452+
simpleName = "Nested"
453+
)
454+
443455
override val testAnnotationId = BuiltinClassId(
444456
name = "$JUNIT5_PACKAGE.Test",
445457
canonicalName = "$JUNIT5_PACKAGE.Test",
@@ -501,6 +513,8 @@ object Junit5 : TestFramework("JUnit5") {
501513
)
502514
}
503515

516+
override val nestedClassesShouldBeStatic = false
517+
504518
private const val junitVersion = "1.7.1" // TODO read it from gradle.properties
505519
private const val platformJarName: String = "junit-platform-console-standalone-$junitVersion.jar"
506520

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

Lines changed: 11 additions & 7 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.UtTestClass
2021

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

65-
fun generateAsStringWithTestReport(
66+
private fun generateAsStringWithTestReport(
6667
cgTestSets: List<CgMethodTestSet>,
6768
testClassCustomName: String? = null,
68-
): TestsCodeWithTestReport = withCustomContext(testClassCustomName) {
69-
context.withClassScope {
70-
val testClassFile = CgTestClassConstructor(context).construct(cgTestSets)
71-
TestsCodeWithTestReport(renderClassFile(testClassFile), testClassFile.testsGenerationReport)
72-
}
73-
}
69+
): TestsCodeWithTestReport =
70+
withCustomContext(testClassCustomName) {
71+
context.withTestClassFileScope {
72+
val testClassFile = CgTestClassConstructor(context).construct(
73+
UtTestClass.fromTestSets(classUnderTest, cgTestSets)
74+
)
75+
TestsCodeWithTestReport(renderClassFile(testClassFile), testClassFile.testsGenerationReport)
76+
}
77+
}
7478

7579
/**
7680
* Wrapper function that configures context as needed for utbot-online:
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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 be used only inside [CgContextOwner].
12+
*/
13+
internal data class TestClassInfo(
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+
24+
// test class superclass (if needed)
25+
var testClassSuperclass: ClassId? = null
26+
set(value) {
27+
// Assigning a value to the testClassSuperclass when it is already non-null
28+
// means that we need the test class to have more than one superclass
29+
// which is impossible in Java and Kotlin.
30+
require(field == null) { "It is impossible for the test class to have more than one superclass" }
31+
field = value
32+
}
33+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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 [UtTestClass] in one of [nestedClasses]
11+
*/
12+
data class UtTestClass(
13+
val classUnderTest: ClassId,
14+
val methodTestSets: List<CgMethodTestSet>,
15+
val nestedClasses: List<UtTestClass> = listOf()
16+
) {
17+
companion object {
18+
fun fromTestSets(classUnderTest: ClassId, testSets: List<CgMethodTestSet>): UtTestClass {
19+
val class2methodTestSets = testSets.groupBy { it.executableId.classId }
20+
val class2nestedClasses = testSets
21+
.map { it.executableId.classId }
22+
.flatMap { generateSequence(it) { it.enclosingClass }.takeWhile { it != classUnderTest } }
23+
.groupBy { it.enclosingClass ?: error("All of the given to codegen methods should belong to classUnderTest") }
24+
.mapValues { (_, v) -> v.distinct() }
25+
return constructRecursively(classUnderTest, class2methodTestSets, class2nestedClasses)
26+
}
27+
28+
private fun constructRecursively(
29+
clazz: ClassId,
30+
class2methodTestSets: Map<ClassId, List<CgMethodTestSet>>,
31+
class2nestedClasses: Map<ClassId, List<ClassId>>
32+
): UtTestClass {
33+
val currentNestedClasses = class2nestedClasses.getOrDefault(clazz, listOf())
34+
val currentMethodTestSets = class2methodTestSets.getOrDefault(clazz, listOf())
35+
return UtTestClass(
36+
clazz,
37+
currentMethodTestSets,
38+
currentNestedClasses.map {
39+
constructRecursively(it, class2methodTestSets, class2nestedClasses)
40+
}
41+
)
42+
}
43+
}
44+
}

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

Lines changed: 42 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import kotlinx.collections.immutable.persistentListOf
5252
import kotlinx.collections.immutable.persistentMapOf
5353
import kotlinx.collections.immutable.persistentSetOf
5454
import org.utbot.framework.codegen.model.constructor.CgMethodTestSet
55+
import org.utbot.framework.codegen.model.constructor.TestClassInfo
5556
import org.utbot.framework.codegen.model.constructor.builtin.streamsDeepEqualsMethodId
5657
import org.utbot.framework.codegen.model.tree.CgParameterKind
5758
import org.utbot.framework.plugin.api.util.id
@@ -66,7 +67,7 @@ import org.utbot.framework.plugin.api.util.jClass
6667
* Although, some of the properties are declared as 'var' so that
6768
* they can be reassigned as well as modified
6869
*
69-
* For example, [currentTestClass] and [currentExecutable] can be reassigned
70+
* For example, [outerMostTestClass] and [currentExecutable] can be reassigned
7071
* when we start generating another method or test class
7172
*
7273
* [existingVariableNames] is a 'var' property
@@ -78,20 +79,18 @@ internal interface CgContextOwner {
7879
// current class under test
7980
val classUnderTest: ClassId
8081

81-
// test class currently being generated
82-
val currentTestClass: ClassId
82+
// test class currently being generated (if series of nested classes is generated, it is the outermost one)
83+
val outerMostTestClass: ClassId
8384

8485
// current executable under test
8586
var currentExecutable: ExecutableId?
8687

87-
// test class superclass (if needed)
88-
var testClassSuperclass: ClassId?
88+
// ClassInfo for the outermost class currently being generated
89+
var outerMostTestClassInfo: TestClassInfo
8990

90-
// list of interfaces that the test class must inherit
91-
val collectedTestClassInterfaces: MutableSet<ClassId>
92-
93-
// list of annotations of the test class
94-
val collectedTestClassAnnotations: MutableSet<CgAnnotation>
91+
// If generating series of nested classes, it is ClassInfo for the innermost one,
92+
// otherwise it should be equal to outerMostTestClassInfo
93+
var currentTestClassInfo: TestClassInfo
9594

9695
// exceptions that can be thrown inside of current method being built
9796
val collectedExceptions: MutableSet<ClassId>
@@ -262,7 +261,7 @@ internal interface CgContextOwner {
262261
}
263262
}
264263

265-
fun <R> withClassScope(block: () -> R): R {
264+
fun <R> withTestClassFileScope(block: () -> R): R {
266265
clearClassScope()
267266
return try {
268267
block()
@@ -271,6 +270,16 @@ internal interface CgContextOwner {
271270
}
272271
}
273272

273+
fun <R> withTestClassScope(block: () -> R): R {
274+
val savedCurrentTestClassInfo = currentTestClassInfo
275+
currentTestClassInfo = TestClassInfo()
276+
return try {
277+
block()
278+
} finally {
279+
currentTestClassInfo = savedCurrentTestClassInfo
280+
}
281+
}
282+
274283
/**
275284
* Set [mockFrameworkUsed] flag to true if the block is successfully executed
276285
*/
@@ -307,6 +316,8 @@ internal interface CgContextOwner {
307316
}
308317

309318
private fun clearClassScope() {
319+
outerMostTestClassInfo = TestClassInfo()
320+
currentTestClassInfo = outerMostTestClassInfo
310321
collectedImports.clear()
311322
importedStaticMethods.clear()
312323
importedClasses.clear()
@@ -321,7 +332,7 @@ internal interface CgContextOwner {
321332
* Check whether a method is an util method of the current class
322333
*/
323334
val MethodId.isUtil: Boolean
324-
get() = this in currentTestClass.possibleUtilMethodIds
335+
get() = this in outerMostTestClass.possibleUtilMethodIds
325336

326337
/**
327338
* Checks is it our util reflection field getter method.
@@ -335,49 +346,49 @@ internal interface CgContextOwner {
335346
// util methods of current test class
336347

337348
val getUnsafeInstance: MethodId
338-
get() = currentTestClass.getUnsafeInstanceMethodId
349+
get() = outerMostTestClass.getUnsafeInstanceMethodId
339350

340351
val createInstance: MethodId
341-
get() = currentTestClass.createInstanceMethodId
352+
get() = outerMostTestClass.createInstanceMethodId
342353

343354
val createArray: MethodId
344-
get() = currentTestClass.createArrayMethodId
355+
get() = outerMostTestClass.createArrayMethodId
345356

346357
val setField: MethodId
347-
get() = currentTestClass.setFieldMethodId
358+
get() = outerMostTestClass.setFieldMethodId
348359

349360
val setStaticField: MethodId
350-
get() = currentTestClass.setStaticFieldMethodId
361+
get() = outerMostTestClass.setStaticFieldMethodId
351362

352363
val getFieldValue: MethodId
353-
get() = currentTestClass.getFieldValueMethodId
364+
get() = outerMostTestClass.getFieldValueMethodId
354365

355366
val getStaticFieldValue: MethodId
356-
get() = currentTestClass.getStaticFieldValueMethodId
367+
get() = outerMostTestClass.getStaticFieldValueMethodId
357368

358369
val getEnumConstantByName: MethodId
359-
get() = currentTestClass.getEnumConstantByNameMethodId
370+
get() = outerMostTestClass.getEnumConstantByNameMethodId
360371

361372
val deepEquals: MethodId
362-
get() = currentTestClass.deepEqualsMethodId
373+
get() = outerMostTestClass.deepEqualsMethodId
363374

364375
val arraysDeepEquals: MethodId
365-
get() = currentTestClass.arraysDeepEqualsMethodId
376+
get() = outerMostTestClass.arraysDeepEqualsMethodId
366377

367378
val iterablesDeepEquals: MethodId
368-
get() = currentTestClass.iterablesDeepEqualsMethodId
379+
get() = outerMostTestClass.iterablesDeepEqualsMethodId
369380

370381
val streamsDeepEquals: MethodId
371-
get() = currentTestClass.streamsDeepEqualsMethodId
382+
get() = outerMostTestClass.streamsDeepEqualsMethodId
372383

373384
val mapsDeepEquals: MethodId
374-
get() = currentTestClass.mapsDeepEqualsMethodId
385+
get() = outerMostTestClass.mapsDeepEqualsMethodId
375386

376387
val hasCustomEquals: MethodId
377-
get() = currentTestClass.hasCustomEqualsMethodId
388+
get() = outerMostTestClass.hasCustomEqualsMethodId
378389

379390
val getArrayLength: MethodId
380-
get() = currentTestClass.getArrayLengthMethodId
391+
get() = outerMostTestClass.getArrayLengthMethodId
381392
}
382393

383394
/**
@@ -386,8 +397,8 @@ internal interface CgContextOwner {
386397
internal data class CgContext(
387398
override val classUnderTest: ClassId,
388399
override var currentExecutable: ExecutableId? = null,
389-
override val collectedTestClassInterfaces: MutableSet<ClassId> = mutableSetOf(),
390-
override val collectedTestClassAnnotations: MutableSet<CgAnnotation> = mutableSetOf(),
400+
override var outerMostTestClassInfo: TestClassInfo = TestClassInfo(),
401+
override var currentTestClassInfo: TestClassInfo = outerMostTestClassInfo,
391402
override val collectedExceptions: MutableSet<ClassId> = mutableSetOf(),
392403
override val collectedMethodAnnotations: MutableSet<CgAnnotation> = mutableSetOf(),
393404
override val collectedImports: MutableSet<Import> = mutableSetOf(),
@@ -425,7 +436,7 @@ internal data class CgContext(
425436
override lateinit var statesCache: EnvironmentFieldStateCache
426437
override lateinit var actual: CgVariable
427438

428-
override val currentTestClass: ClassId by lazy {
439+
override val outerMostTestClass: ClassId by lazy {
429440
val packagePrefix = if (testClassPackageName.isNotEmpty()) "$testClassPackageName." else ""
430441
val simpleName = testClassCustomName ?: "${createTestClassName(classUnderTest.name)}Test"
431442
val name = "$packagePrefix$simpleName"
@@ -436,20 +447,11 @@ internal data class CgContext(
436447
)
437448
}
438449

439-
override var testClassSuperclass: ClassId? = null
440-
set(value) {
441-
// Assigning a value to the testClassSuperclass when it is already non-null
442-
// means that we need the test class to have more than one superclass
443-
// which is impossible in Java and Kotlin.
444-
require(field == null) { "It is impossible for the test class to have more than one superclass" }
445-
field = value
446-
}
447-
448450
override var valueByModel: IdentityHashMap<UtModel, CgValue> = IdentityHashMap()
449451

450452
override var valueByModelId: MutableMap<Int?, CgValue> = mutableMapOf()
451453

452454
override val currentMethodParameters: MutableMap<CgParameterKind, CgVariable> = mutableMapOf()
453455

454-
override val testClassThisInstance: CgThisInstance = CgThisInstance(currentTestClass)
456+
override val testClassThisInstance: CgThisInstance = CgThisInstance(outerMostTestClass)
455457
}

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgCallableAccessManager.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA
160160
private fun BuiltinMethodId.findExceptionTypes(): Set<ClassId> {
161161
if (!this.isUtil) return emptySet()
162162

163-
with(currentTestClass) {
163+
with(outerMostTestClass) {
164164
return when (this@findExceptionTypes) {
165165
getEnumConstantByNameMethodId -> setOf(IllegalAccessException::class.id)
166166
getStaticFieldValueMethodId,
@@ -185,7 +185,7 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA
185185
private infix fun CgExpression?.canBeReceiverOf(executable: MethodId): Boolean =
186186
when {
187187
// TODO: rewrite by using CgMethodId, etc.
188-
currentTestClass == executable.classId && this isThisInstanceOf currentTestClass -> true
188+
outerMostTestClass == executable.classId && this isThisInstanceOf outerMostTestClass -> true
189189
executable.isStatic -> true
190190
else -> this?.type?.isSubtypeOf(executable.classId) ?: false
191191
}

0 commit comments

Comments
 (0)