Skip to content

Fix codegen to generate tests for nested classes into nested testclasses #560 #665

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import org.utbot.framework.plugin.api.util.isPrimitive
import org.utbot.framework.plugin.api.util.jClass
import org.utbot.framework.plugin.api.util.longClassId
import org.utbot.framework.plugin.api.util.method
import org.utbot.framework.plugin.api.util.objectClassId
import org.utbot.framework.plugin.api.util.primitiveTypeJvmNameOrNull
import org.utbot.framework.plugin.api.util.safeJField
import org.utbot.framework.plugin.api.util.shortClassId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ package org.utbot.framework.codegen
import org.utbot.framework.DEFAULT_CONCRETE_EXECUTION_TIMEOUT_IN_CHILD_PROCESS_MS
import org.utbot.framework.codegen.model.constructor.builtin.mockitoClassId
import org.utbot.framework.codegen.model.constructor.builtin.ongoingStubbingClassId
import org.utbot.framework.codegen.model.constructor.util.argumentsClassId
import org.utbot.framework.codegen.model.tree.CgClassId
import org.utbot.framework.plugin.api.BuiltinClassId
import org.utbot.framework.plugin.api.ClassId
import org.utbot.framework.plugin.api.CodeGenerationSettingBox
import org.utbot.framework.plugin.api.CodeGenerationSettingItem
import org.utbot.framework.plugin.api.MethodId
import org.utbot.framework.plugin.api.TypeParameters
import org.utbot.framework.plugin.api.isolateCommandLineArgumentsToArgumentFile
import org.utbot.framework.plugin.api.util.booleanArrayClassId
import org.utbot.framework.plugin.api.util.booleanClassId
Expand All @@ -28,6 +30,7 @@ import org.utbot.framework.plugin.api.util.shortArrayClassId
import org.utbot.framework.plugin.api.util.voidClassId
import java.io.File
import org.utbot.framework.plugin.api.util.longClassId
import org.utbot.framework.plugin.api.util.objectArrayClassId
import org.utbot.framework.plugin.api.util.voidWrapperClassId

data class TestClassFile(val packageName: String, val imports: List<Import>, val testClass: String)
Expand Down Expand Up @@ -183,6 +186,8 @@ sealed class TestFramework(
abstract val methodSourceAnnotation: String
abstract val methodSourceAnnotationId: ClassId
abstract val methodSourceAnnotationFqn: String
abstract val nestedClassesShouldBeStatic: Boolean
abstract val argListClassId: ClassId

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

Expand Down Expand Up @@ -301,6 +306,30 @@ object TestNg : TestFramework(displayName = "TestNG") {
simpleName = "DataProvider"
)

override val nestedClassesShouldBeStatic = true

override val argListClassId: ClassId
get() {
val outerArrayId = Array<Array<Any?>?>::class.id
val innerArrayId = BuiltinClassId(
name = objectArrayClassId.name,
simpleName = objectArrayClassId.simpleName,
canonicalName = objectArrayClassId.canonicalName,
packageName = objectArrayClassId.packageName,
elementClassId = objectClassId,
typeParameters = TypeParameters(listOf(objectClassId))
)

return BuiltinClassId(
name = outerArrayId.name,
simpleName = outerArrayId.simpleName,
canonicalName = outerArrayId.canonicalName,
packageName = outerArrayId.packageName,
elementClassId = innerArrayId,
typeParameters = TypeParameters(listOf(innerArrayId))
)
}

@OptIn(ExperimentalStdlibApi::class)
override fun getRunTestsCommand(
executionInvoke: String,
Expand Down Expand Up @@ -346,14 +375,21 @@ object TestNg : TestFramework(displayName = "TestNG") {
}

object Junit4 : TestFramework("JUnit4") {
private val parametrizedTestsNotSupportedError: Nothing
get() = error("Parametrized tests are not supported for JUnit4")

override val mainPackage: String = JUNIT4_PACKAGE
override val testAnnotation = "@$mainPackage.Test"
override val testAnnotationFqn: String = "$mainPackage.Test"

override val parameterizedTestAnnotation = "Parameterized tests are not supported for JUnit4"
override val parameterizedTestAnnotationFqn = "Parameterized tests are not supported for JUnit4"
override val methodSourceAnnotation = "Parameterized tests are not supported for JUnit4"
override val methodSourceAnnotationFqn = "Parameterized tests are not supported for JUnit4"
override val parameterizedTestAnnotation
get() = parametrizedTestsNotSupportedError
override val parameterizedTestAnnotationFqn
get() = parametrizedTestsNotSupportedError
override val methodSourceAnnotation
get() = parametrizedTestsNotSupportedError
override val methodSourceAnnotationFqn
get() = parametrizedTestsNotSupportedError

override val testAnnotationId = BuiltinClassId(
name = "$JUNIT4_PACKAGE.Test",
Expand Down Expand Up @@ -385,6 +421,17 @@ object Junit4 : TestFramework("JUnit4") {
)
}

val enclosedClassId = BuiltinClassId(
name = "org.junit.experimental.runners.Enclosed",
canonicalName = "org.junit.experimental.runners.Enclosed",
simpleName = "Enclosed"
)

override val nestedClassesShouldBeStatic = true

override val argListClassId: ClassId
get() = parametrizedTestsNotSupportedError

@OptIn(ExperimentalStdlibApi::class)
override fun getRunTestsCommand(
executionInvoke: String,
Expand Down Expand Up @@ -440,6 +487,12 @@ object Junit5 : TestFramework("JUnit5") {
arguments = arrayOf(longClassId)
)

val nestedTestClassAnnotationId = BuiltinClassId(
name = "$JUNIT5_PACKAGE.Nested",
canonicalName = "$JUNIT5_PACKAGE.Nested",
simpleName = "Nested"
)

override val testAnnotationId = BuiltinClassId(
name = "$JUNIT5_PACKAGE.Test",
canonicalName = "$JUNIT5_PACKAGE.Test",
Expand Down Expand Up @@ -501,6 +554,20 @@ object Junit5 : TestFramework("JUnit5") {
)
}

override val nestedClassesShouldBeStatic = false

override val argListClassId: ClassId
get() {
val arrayListId = java.util.ArrayList::class.id
return BuiltinClassId(
name = arrayListId.name,
simpleName = arrayListId.simpleName,
canonicalName = arrayListId.canonicalName,
packageName = arrayListId.packageName,
typeParameters = TypeParameters(listOf(argumentsClassId))
)
}

private const val junitVersion = "1.7.1" // TODO read it from gradle.properties
private const val platformJarName: String = "junit-platform-console-standalone-$junitVersion.jar"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import org.utbot.framework.plugin.api.CodegenLanguage
import org.utbot.framework.plugin.api.ExecutableId
import org.utbot.framework.plugin.api.MockFramework
import org.utbot.framework.plugin.api.UtMethodTestSet
import org.utbot.framework.codegen.model.constructor.TestClassModel

class CodeGenerator(
private val classUnderTest: ClassId,
Expand Down Expand Up @@ -62,12 +63,13 @@ class CodeGenerator(
return generateAsStringWithTestReport(cgTestSets, testClassCustomName)
}

fun generateAsStringWithTestReport(
private fun generateAsStringWithTestReport(
cgTestSets: List<CgMethodTestSet>,
testClassCustomName: String? = null,
): TestsCodeWithTestReport = withCustomContext(testClassCustomName) {
context.withClassScope {
val testClassFile = CgTestClassConstructor(context).construct(cgTestSets)
context.withTestClassFileScope {
val testClassModel = TestClassModel.fromTestSets(classUnderTest, cgTestSets)
val testClassFile = CgTestClassConstructor(context).construct(testClassModel)
TestsCodeWithTestReport(renderClassFile(testClassFile), testClassFile.testsGenerationReport)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.utbot.framework.codegen.model.constructor

import org.utbot.framework.codegen.model.constructor.context.CgContextOwner
import org.utbot.framework.codegen.model.tree.CgAnnotation
import org.utbot.framework.codegen.model.tree.CgMethod
import org.utbot.framework.plugin.api.ClassId
import org.utbot.framework.codegen.model.tree.CgTestClass

/**
* This class stores context information needed to build [CgTestClass].
* Should only be used in [CgContextOwner].
*/
internal data class TestClassContext(
// set of interfaces that the test class must inherit
val collectedTestClassInterfaces: MutableSet<ClassId> = mutableSetOf(),

// set of annotations of the test class
val collectedTestClassAnnotations: MutableSet<CgAnnotation> = mutableSetOf(),

// list of data provider methods that test class must implement
val cgDataProviderMethods: MutableList<CgMethod> = mutableListOf(),
) {
// test class superclass (if needed)
var testClassSuperclass: ClassId? = null
set(value) {
// Assigning a value to the testClassSuperclass when it is already non-null
// means that we need the test class to have more than one superclass
// which is impossible in Java and Kotlin.
require(value == null || field == null) { "It is impossible for the test class to have more than one superclass" }
field = value
}

fun clear() {
collectedTestClassAnnotations.clear()
collectedTestClassInterfaces.clear()
cgDataProviderMethods.clear()
testClassSuperclass = null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package org.utbot.framework.codegen.model.constructor

import org.utbot.framework.plugin.api.ClassId
import org.utbot.framework.plugin.api.util.enclosingClass

// TODO: seems like this class needs to be renamed
/**
* Stores method testsets in a structure that replicates structure of their methods in [classUnderTest].
* I.e., if some method is declared in nested class of [classUnderTest], its testset will be put
* in [TestClassModel] in one of [nestedClasses]
*/
data class TestClassModel(
val classUnderTest: ClassId,
val methodTestSets: List<CgMethodTestSet>,
val nestedClasses: List<TestClassModel> = listOf()
) {
companion object {
fun fromTestSets(classUnderTest: ClassId, testSets: List<CgMethodTestSet>): TestClassModel {
// For each class stores list of methods declared in this class (methods from nested classes are excluded)
val class2methodTestSets = testSets.groupBy { it.executableId.classId }

val classesWithMethodsUnderTest = testSets
.distinctBy { it.executableId.classId }
.map { it.executableId.classId }

// For each class stores list of its "direct" nested classes
val class2nestedClasses = mutableMapOf<ClassId, MutableList<ClassId>>()

for (classId in classesWithMethodsUnderTest) {
var currentClass = classId
var enclosingClass = currentClass.enclosingClass
// while we haven't reached the top of nested class hierarchy or the main class under test
while (enclosingClass != null && currentClass != classUnderTest) {
class2nestedClasses.getOrPut(enclosingClass) { mutableListOf() } += currentClass
currentClass = enclosingClass
enclosingClass = enclosingClass.enclosingClass
}
}
return constructRecursively(classUnderTest, class2methodTestSets, class2nestedClasses)
}

private fun constructRecursively(
clazz: ClassId,
class2methodTestSets: Map<ClassId, List<CgMethodTestSet>>,
class2nestedClasses: Map<ClassId, List<ClassId>>
): TestClassModel {
val currentNestedClasses = class2nestedClasses.getOrDefault(clazz, listOf())
val currentMethodTestSets = class2methodTestSets.getOrDefault(clazz, listOf())
return TestClassModel(
clazz,
currentMethodTestSets,
currentNestedClasses.map {
constructRecursively(it, class2methodTestSets, class2nestedClasses)
}
)
}
}
}
Loading