Skip to content

Commit dec5883

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

File tree

29 files changed

+410
-158
lines changed

29 files changed

+410
-158
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: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
package org.utbot.framework.codegen
22

3+
import org.junit.experimental.runners.Enclosed
34
import org.utbot.framework.DEFAULT_CONCRETE_EXECUTION_TIMEOUT_IN_CHILD_PROCESS_MS
45
import org.utbot.framework.codegen.model.constructor.builtin.mockitoClassId
56
import org.utbot.framework.codegen.model.constructor.builtin.ongoingStubbingClassId
7+
import org.utbot.framework.codegen.model.constructor.util.CgStatementConstructor
8+
import org.utbot.framework.codegen.model.tree.CgAnnotation
69
import org.utbot.framework.codegen.model.tree.CgClassId
10+
import org.utbot.framework.codegen.model.tree.CgGetJavaClass
711
import org.utbot.framework.plugin.api.BuiltinClassId
812
import org.utbot.framework.plugin.api.ClassId
913
import org.utbot.framework.plugin.api.CodeGenerationSettingBox
@@ -183,6 +187,7 @@ sealed class TestFramework(
183187
abstract val methodSourceAnnotation: String
184188
abstract val methodSourceAnnotationId: ClassId
185189
abstract val methodSourceAnnotationFqn: String
190+
abstract val nestedClassesShouldBeStatic: Boolean
186191

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

@@ -221,6 +226,10 @@ sealed class TestFramework(
221226
private fun arrayAssertionId(name: String, vararg params: ClassId): MethodId =
222227
builtinStaticMethodId(arraysAssertionsClass, name, voidClassId, *params)
223228

229+
abstract fun getAnnotationForNestedClasses(constructor: CgStatementConstructor): CgAnnotation?
230+
231+
abstract fun getAnnotationForOuterClasses(constructor: CgStatementConstructor): CgAnnotation?
232+
224233
abstract fun getRunTestsCommand(
225234
executionInvoke: String,
226235
classPath: String,
@@ -301,6 +310,12 @@ object TestNg : TestFramework(displayName = "TestNG") {
301310
simpleName = "DataProvider"
302311
)
303312

313+
override val nestedClassesShouldBeStatic = true
314+
315+
override fun getAnnotationForNestedClasses(constructor: CgStatementConstructor) = null
316+
317+
override fun getAnnotationForOuterClasses(constructor: CgStatementConstructor) = null
318+
304319
@OptIn(ExperimentalStdlibApi::class)
305320
override fun getRunTestsCommand(
306321
executionInvoke: String,
@@ -385,6 +400,17 @@ object Junit4 : TestFramework("JUnit4") {
385400
)
386401
}
387402

403+
404+
override val nestedClassesShouldBeStatic = true
405+
406+
override fun getAnnotationForNestedClasses(constructor: CgStatementConstructor) = null
407+
408+
override fun getAnnotationForOuterClasses(constructor: CgStatementConstructor) =
409+
constructor.annotation(
410+
runWithAnnotationClassId,
411+
CgGetJavaClass(Enclosed::class.id)
412+
)
413+
388414
@OptIn(ExperimentalStdlibApi::class)
389415
override fun getRunTestsCommand(
390416
executionInvoke: String,
@@ -440,6 +466,12 @@ object Junit5 : TestFramework("JUnit5") {
440466
arguments = arrayOf(longClassId)
441467
)
442468

469+
val nestedTestClassAnnotationId = BuiltinClassId(
470+
name = "$JUNIT5_PACKAGE.Nested",
471+
canonicalName = "$JUNIT5_PACKAGE.Nested",
472+
simpleName = "Nested"
473+
)
474+
443475
override val testAnnotationId = BuiltinClassId(
444476
name = "$JUNIT5_PACKAGE.Test",
445477
canonicalName = "$JUNIT5_PACKAGE.Test",
@@ -501,9 +533,16 @@ object Junit5 : TestFramework("JUnit5") {
501533
)
502534
}
503535

536+
override val nestedClassesShouldBeStatic = false
537+
504538
private const val junitVersion = "1.7.1" // TODO read it from gradle.properties
505539
private const val platformJarName: String = "junit-platform-console-standalone-$junitVersion.jar"
506540

541+
override fun getAnnotationForNestedClasses(constructor: CgStatementConstructor) =
542+
constructor.annotation(nestedTestClassAnnotationId)
543+
544+
override fun getAnnotationForOuterClasses(constructor: CgStatementConstructor) = null
545+
507546
@OptIn(ExperimentalStdlibApi::class)
508547
override fun getRunTestsCommand(
509548
executionInvoke: String,

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: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package org.utbot.framework.codegen.model.constructor
2+
3+
import org.utbot.framework.codegen.model.tree.CgAnnotation
4+
import org.utbot.framework.codegen.model.tree.CgMethod
5+
import org.utbot.framework.plugin.api.ClassId
6+
7+
data class TestClassInfo(
8+
val collectedTestClassInterfaces: MutableSet<ClassId> = mutableSetOf(),
9+
val collectedTestClassAnnotations: MutableSet<CgAnnotation> = mutableSetOf(),
10+
val cgDataProviderMethods: MutableList<CgMethod> = mutableListOf(),
11+
) {
12+
var testClassSuperclass: ClassId? = null
13+
set(value) {
14+
// Assigning a value to the testClassSuperclass when it is already non-null
15+
// means that we need the test class to have more than one superclass
16+
// which is impossible in Java and Kotlin.
17+
require(field == null) { "It is impossible for the test class to have more than one superclass" }
18+
field = value
19+
}
20+
}
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.plugin.api.ClassId
4+
import org.utbot.framework.plugin.api.util.enclosingClass
5+
6+
// TODO: seems like this class needs to be renamed
7+
data class UtTestClass(
8+
val classUnderTest: ClassId,
9+
val methodTestSets: List<CgMethodTestSet>,
10+
val nestedClasses: List<UtTestClass> = listOf()
11+
) {
12+
companion object {
13+
fun fromTestSets(classUnderTest: ClassId, testSets: List<CgMethodTestSet>): UtTestClass {
14+
val class2methodTestSets = testSets.groupBy { it.executableId.classId }
15+
val class2nestedClasses = testSets
16+
.map { it.executableId.classId }
17+
.flatMap { generateSequence(it) { it.enclosingClass }.takeWhile { it != classUnderTest } }
18+
.groupBy { it.enclosingClass ?: error("All of the given to codegen methods should belong to classUnderTest") }
19+
.mapValues { (_, v) -> v.distinct() }
20+
return constructRecursively(classUnderTest, class2methodTestSets, class2nestedClasses)
21+
}
22+
23+
private fun constructRecursively(
24+
clazz: ClassId,
25+
class2methodTestSets: Map<ClassId, List<CgMethodTestSet>>,
26+
class2nestedClasses: Map<ClassId, List<ClassId>>
27+
): UtTestClass {
28+
val currentNestedClasses = class2nestedClasses.getOrDefault(clazz, listOf())
29+
val currentMethodTestSets = class2methodTestSets.getOrDefault(clazz, listOf())
30+
return UtTestClass(
31+
clazz,
32+
currentMethodTestSets,
33+
currentNestedClasses.map {
34+
constructRecursively(it, class2methodTestSets, class2nestedClasses)
35+
}
36+
)
37+
}
38+
}
39+
}

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

Lines changed: 39 additions & 39 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, [currentOuterMostTestClass] and [currentExecutable] can be reassigned
7071
* when we start generating another method or test class
7172
*
7273
* [existingVariableNames] is a 'var' property
@@ -79,19 +80,15 @@ internal interface CgContextOwner {
7980
val classUnderTest: ClassId
8081

8182
// test class currently being generated
82-
val currentTestClass: ClassId
83+
val currentOuterMostTestClass: ClassId
8384

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

87-
// test class superclass (if needed)
88-
var testClassSuperclass: ClassId?
88+
// TODO: add comments (especially about the case when outerMostTestClassInfo === currentTestClassInfo)
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+
var currentTestClassInfo: TestClassInfo
9592

9693
// exceptions that can be thrown inside of current method being built
9794
val collectedExceptions: MutableSet<ClassId>
@@ -262,7 +259,7 @@ internal interface CgContextOwner {
262259
}
263260
}
264261

265-
fun <R> withClassScope(block: () -> R): R {
262+
fun <R> withTestClassFileScope(block: () -> R): R {
266263
clearClassScope()
267264
return try {
268265
block()
@@ -271,6 +268,16 @@ internal interface CgContextOwner {
271268
}
272269
}
273270

271+
fun <R> withTestClassScope(block: () -> R): R {
272+
val savedCurrentTestClassInfo = currentTestClassInfo
273+
currentTestClassInfo = TestClassInfo()
274+
return try {
275+
block()
276+
} finally {
277+
currentTestClassInfo = savedCurrentTestClassInfo
278+
}
279+
}
280+
274281
/**
275282
* Set [mockFrameworkUsed] flag to true if the block is successfully executed
276283
*/
@@ -307,6 +314,8 @@ internal interface CgContextOwner {
307314
}
308315

309316
private fun clearClassScope() {
317+
outerMostTestClassInfo = TestClassInfo()
318+
currentTestClassInfo = outerMostTestClassInfo
310319
collectedImports.clear()
311320
importedStaticMethods.clear()
312321
importedClasses.clear()
@@ -321,7 +330,7 @@ internal interface CgContextOwner {
321330
* Check whether a method is an util method of the current class
322331
*/
323332
val MethodId.isUtil: Boolean
324-
get() = this in currentTestClass.possibleUtilMethodIds
333+
get() = this in currentOuterMostTestClass.possibleUtilMethodIds
325334

326335
/**
327336
* Checks is it our util reflection field getter method.
@@ -335,49 +344,49 @@ internal interface CgContextOwner {
335344
// util methods of current test class
336345

337346
val getUnsafeInstance: MethodId
338-
get() = currentTestClass.getUnsafeInstanceMethodId
347+
get() = currentOuterMostTestClass.getUnsafeInstanceMethodId
339348

340349
val createInstance: MethodId
341-
get() = currentTestClass.createInstanceMethodId
350+
get() = currentOuterMostTestClass.createInstanceMethodId
342351

343352
val createArray: MethodId
344-
get() = currentTestClass.createArrayMethodId
353+
get() = currentOuterMostTestClass.createArrayMethodId
345354

346355
val setField: MethodId
347-
get() = currentTestClass.setFieldMethodId
356+
get() = currentOuterMostTestClass.setFieldMethodId
348357

349358
val setStaticField: MethodId
350-
get() = currentTestClass.setStaticFieldMethodId
359+
get() = currentOuterMostTestClass.setStaticFieldMethodId
351360

352361
val getFieldValue: MethodId
353-
get() = currentTestClass.getFieldValueMethodId
362+
get() = currentOuterMostTestClass.getFieldValueMethodId
354363

355364
val getStaticFieldValue: MethodId
356-
get() = currentTestClass.getStaticFieldValueMethodId
365+
get() = currentOuterMostTestClass.getStaticFieldValueMethodId
357366

358367
val getEnumConstantByName: MethodId
359-
get() = currentTestClass.getEnumConstantByNameMethodId
368+
get() = currentOuterMostTestClass.getEnumConstantByNameMethodId
360369

361370
val deepEquals: MethodId
362-
get() = currentTestClass.deepEqualsMethodId
371+
get() = currentOuterMostTestClass.deepEqualsMethodId
363372

364373
val arraysDeepEquals: MethodId
365-
get() = currentTestClass.arraysDeepEqualsMethodId
374+
get() = currentOuterMostTestClass.arraysDeepEqualsMethodId
366375

367376
val iterablesDeepEquals: MethodId
368-
get() = currentTestClass.iterablesDeepEqualsMethodId
377+
get() = currentOuterMostTestClass.iterablesDeepEqualsMethodId
369378

370379
val streamsDeepEquals: MethodId
371-
get() = currentTestClass.streamsDeepEqualsMethodId
380+
get() = currentOuterMostTestClass.streamsDeepEqualsMethodId
372381

373382
val mapsDeepEquals: MethodId
374-
get() = currentTestClass.mapsDeepEqualsMethodId
383+
get() = currentOuterMostTestClass.mapsDeepEqualsMethodId
375384

376385
val hasCustomEquals: MethodId
377-
get() = currentTestClass.hasCustomEqualsMethodId
386+
get() = currentOuterMostTestClass.hasCustomEqualsMethodId
378387

379388
val getArrayLength: MethodId
380-
get() = currentTestClass.getArrayLengthMethodId
389+
get() = currentOuterMostTestClass.getArrayLengthMethodId
381390
}
382391

383392
/**
@@ -386,8 +395,8 @@ internal interface CgContextOwner {
386395
internal data class CgContext(
387396
override val classUnderTest: ClassId,
388397
override var currentExecutable: ExecutableId? = null,
389-
override val collectedTestClassInterfaces: MutableSet<ClassId> = mutableSetOf(),
390-
override val collectedTestClassAnnotations: MutableSet<CgAnnotation> = mutableSetOf(),
398+
override var outerMostTestClassInfo: TestClassInfo = TestClassInfo(),
399+
override var currentTestClassInfo: TestClassInfo = outerMostTestClassInfo,
391400
override val collectedExceptions: MutableSet<ClassId> = mutableSetOf(),
392401
override val collectedMethodAnnotations: MutableSet<CgAnnotation> = mutableSetOf(),
393402
override val collectedImports: MutableSet<Import> = mutableSetOf(),
@@ -425,7 +434,7 @@ internal data class CgContext(
425434
override lateinit var statesCache: EnvironmentFieldStateCache
426435
override lateinit var actual: CgVariable
427436

428-
override val currentTestClass: ClassId by lazy {
437+
override val currentOuterMostTestClass: ClassId by lazy {
429438
val packagePrefix = if (testClassPackageName.isNotEmpty()) "$testClassPackageName." else ""
430439
val simpleName = testClassCustomName ?: "${createTestClassName(classUnderTest.name)}Test"
431440
val name = "$packagePrefix$simpleName"
@@ -436,20 +445,11 @@ internal data class CgContext(
436445
)
437446
}
438447

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-
448448
override var valueByModel: IdentityHashMap<UtModel, CgValue> = IdentityHashMap()
449449

450450
override var valueByModelId: MutableMap<Int?, CgValue> = mutableMapOf()
451451

452452
override val currentMethodParameters: MutableMap<CgParameterKind, CgVariable> = mutableMapOf()
453453

454-
override val testClassThisInstance: CgThisInstance = CgThisInstance(currentTestClass)
454+
override val testClassThisInstance: CgThisInstance = CgThisInstance(currentOuterMostTestClass)
455455
}

0 commit comments

Comments
 (0)