From 8191af5c1a67b321bf837a6d0839ec25f6a29874 Mon Sep 17 00:00:00 2001 From: Ivan Volkov Date: Tue, 2 Aug 2022 16:08:13 +0300 Subject: [PATCH] Fix codegen to generate tests for nested classes into nested testclasses #560 --- .../org/utbot/framework/plugin/api/Api.kt | 1 - .../org/utbot/framework/codegen/Domain.kt | 75 ++++++- .../framework/codegen/model/CodeGenerator.kt | 8 +- .../model/constructor/TestClassContext.kt | 39 ++++ .../model/constructor/TestClassModel.kt | 58 +++++ .../model/constructor/context/CgContext.kt | 177 ++++++++++----- .../tree/CgCallableAccessManager.kt | 4 +- .../constructor/tree/CgMethodConstructor.kt | 184 +--------------- .../tree/CgTestClassConstructor.kt | 95 +++++--- .../constructor/tree/TestFrameworkManager.kt | 205 +++++++++++++++--- .../constructor/util/ConstructorUtils.kt | 60 +++++ .../framework/codegen/model/tree/Builders.kt | 8 +- .../framework/codegen/model/tree/CgElement.kt | 5 +- .../model/visitor/CgAbstractRenderer.kt | 6 +- .../codegen/model/visitor/CgJavaRenderer.kt | 6 +- .../codegen/model/visitor/CgKotlinRenderer.kt | 3 + .../codegen/model/visitor/UtilMethods.kt | 4 +- .../utbot/examples/UtValueTestCaseChecker.kt | 27 ++- .../annotations/LombokAnnotationTest.kt | 47 ---- .../lombok/EnumWithAnnotationsTest.kt | 25 +++ .../lombok/EnumWithoutAnnotationsTest.kt | 16 ++ .../lombok/NotNullAnnotationsTest.kt | 25 +++ .../org/utbot/examples/casts/CastClassTest.kt | 26 +++ .../utbot/examples/casts/CastExampleTest.kt | 9 - .../ClassWithStaticAndInnerClassesTest.kt | 16 ++ .../deepequals/ClassWithNullableFieldTest.kt | 26 +++ .../codegen/deepequals/DeepEqualsTest.kt | 9 - .../utbot/examples/mock/MockFinalClassTest.kt | 2 +- .../ClassWithStaticAndInnerClasses.java | 12 + 29 files changed, 801 insertions(+), 377 deletions(-) create mode 100644 utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/TestClassContext.kt create mode 100644 utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/TestClassModel.kt delete mode 100644 utbot-framework/src/test/kotlin/org/utbot/examples/annotations/LombokAnnotationTest.kt create mode 100644 utbot-framework/src/test/kotlin/org/utbot/examples/annotations/lombok/EnumWithAnnotationsTest.kt create mode 100644 utbot-framework/src/test/kotlin/org/utbot/examples/annotations/lombok/EnumWithoutAnnotationsTest.kt create mode 100644 utbot-framework/src/test/kotlin/org/utbot/examples/annotations/lombok/NotNullAnnotationsTest.kt create mode 100644 utbot-framework/src/test/kotlin/org/utbot/examples/casts/CastClassTest.kt create mode 100644 utbot-framework/src/test/kotlin/org/utbot/examples/codegen/deepequals/ClassWithNullableFieldTest.kt diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt index cc19322abc..143b9d5d48 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt @@ -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 diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/Domain.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/Domain.kt index 2a4dea612c..8f9184f429 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/Domain.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/Domain.kt @@ -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 @@ -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, val testClass: String) @@ -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) } @@ -301,6 +306,30 @@ object TestNg : TestFramework(displayName = "TestNG") { simpleName = "DataProvider" ) + override val nestedClassesShouldBeStatic = true + + override val argListClassId: ClassId + get() { + val outerArrayId = Array?>::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, @@ -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", @@ -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, @@ -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", @@ -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" diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt index 3192782a2a..6e4b997cff 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt @@ -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, @@ -62,12 +63,13 @@ class CodeGenerator( return generateAsStringWithTestReport(cgTestSets, testClassCustomName) } - fun generateAsStringWithTestReport( + private fun generateAsStringWithTestReport( cgTestSets: List, 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) } } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/TestClassContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/TestClassContext.kt new file mode 100644 index 0000000000..4d6f764483 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/TestClassContext.kt @@ -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 = mutableSetOf(), + + // set of annotations of the test class + val collectedTestClassAnnotations: MutableSet = mutableSetOf(), + + // list of data provider methods that test class must implement + val cgDataProviderMethods: MutableList = 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 + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/TestClassModel.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/TestClassModel.kt new file mode 100644 index 0000000000..34b1d81f0d --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/TestClassModel.kt @@ -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, + val nestedClasses: List = listOf() +) { + companion object { + fun fromTestSets(classUnderTest: ClassId, testSets: List): 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>() + + 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>, + class2nestedClasses: Map> + ): TestClassModel { + val currentNestedClasses = class2nestedClasses.getOrDefault(clazz, listOf()) + val currentMethodTestSets = class2methodTestSets.getOrDefault(clazz, listOf()) + return TestClassModel( + clazz, + currentMethodTestSets, + currentNestedClasses.map { + constructRecursively(it, class2methodTestSets, class2nestedClasses) + } + ) + } + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt index 7e6b8bbad7..4e33f68ea8 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt @@ -42,6 +42,8 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.persistentSetOf import org.utbot.framework.codegen.model.constructor.CgMethodTestSet +import org.utbot.framework.codegen.model.constructor.TestClassContext +import org.utbot.framework.codegen.model.constructor.TestClassModel import org.utbot.framework.codegen.model.constructor.builtin.streamsDeepEqualsMethodId import org.utbot.framework.codegen.model.tree.CgParameterKind import org.utbot.framework.plugin.api.BuiltinClassId @@ -66,7 +68,7 @@ import org.utbot.framework.plugin.api.util.jClass * Although, some of the properties are declared as 'var' so that * they can be reassigned as well as modified * - * For example, [currentTestClass] and [currentExecutable] can be reassigned + * For example, [outerMostTestClass] and [currentExecutable] can be reassigned * when we start generating another method or test class * * [existingVariableNames] is a 'var' property @@ -78,20 +80,21 @@ internal interface CgContextOwner { // current class under test val classUnderTest: ClassId - // test class currently being generated - val currentTestClass: ClassId + // test class currently being generated (if series of nested classes is generated, it is the outermost one) + val outerMostTestClass: ClassId + + // test class currently being generated (if series of nested classes is generated, it is the innermost one) + var currentTestClass: ClassId // current executable under test var currentExecutable: ExecutableId? - // test class superclass (if needed) - var testClassSuperclass: ClassId? - - // list of interfaces that the test class must inherit - val collectedTestClassInterfaces: MutableSet + // ClassInfo for the outermost class currently being generated + val outerMostTestClassContext: TestClassContext - // list of annotations of the test class - val collectedTestClassAnnotations: MutableSet + // If generating series of nested classes, it is ClassInfo for the innermost one, + // otherwise it should be equal to outerMostTestClassInfo + val currentTestClassContext: TestClassContext // exceptions that can be thrown inside of current method being built val collectedExceptions: MutableSet @@ -266,14 +269,23 @@ internal interface CgContextOwner { } } - fun withClassScope(block: () -> R): R { - clearClassScope() - return try { - block() - } finally { - clearClassScope() - } - } + /** + * This method sets up context for a new test class file generation and executes the given [block]. + * Afterwards, context is set back to the initial state. + */ + fun withTestClassFileScope(block: () -> R): R + + /** + * This method sets up context for a new test class generation and executes the given [block]. + * Afterwards, context is set back to the initial state. + */ + fun withTestClassScope(block: () -> R): R + + /** + * This method does almost all the same as [withTestClassScope], but for nested test classes. + * The difference is that instead of working with [outerMostTestClassContext] it works with [currentTestClassContext]. + */ + fun withNestedClassScope(testClassModel: TestClassModel, block: () -> R): R /** * Set [mockFrameworkUsed] flag to true if the block is successfully executed @@ -310,22 +322,11 @@ internal interface CgContextOwner { } } - private fun clearClassScope() { - collectedImports.clear() - importedStaticMethods.clear() - importedClasses.clear() - testMethods.clear() - requiredUtilMethods.clear() - valueByModel.clear() - valueByModelId.clear() - mockFrameworkUsed = false - } - /** * Check whether a method is an util method of the current class */ val MethodId.isUtil: Boolean - get() = this in currentTestClass.possibleUtilMethodIds + get() = this in outerMostTestClass.possibleUtilMethodIds /** * Checks is it our util reflection field getter method. @@ -339,49 +340,49 @@ internal interface CgContextOwner { // util methods of current test class val getUnsafeInstance: MethodId - get() = currentTestClass.getUnsafeInstanceMethodId + get() = outerMostTestClass.getUnsafeInstanceMethodId val createInstance: MethodId - get() = currentTestClass.createInstanceMethodId + get() = outerMostTestClass.createInstanceMethodId val createArray: MethodId - get() = currentTestClass.createArrayMethodId + get() = outerMostTestClass.createArrayMethodId val setField: MethodId - get() = currentTestClass.setFieldMethodId + get() = outerMostTestClass.setFieldMethodId val setStaticField: MethodId - get() = currentTestClass.setStaticFieldMethodId + get() = outerMostTestClass.setStaticFieldMethodId val getFieldValue: MethodId - get() = currentTestClass.getFieldValueMethodId + get() = outerMostTestClass.getFieldValueMethodId val getStaticFieldValue: MethodId - get() = currentTestClass.getStaticFieldValueMethodId + get() = outerMostTestClass.getStaticFieldValueMethodId val getEnumConstantByName: MethodId - get() = currentTestClass.getEnumConstantByNameMethodId + get() = outerMostTestClass.getEnumConstantByNameMethodId val deepEquals: MethodId - get() = currentTestClass.deepEqualsMethodId + get() = outerMostTestClass.deepEqualsMethodId val arraysDeepEquals: MethodId - get() = currentTestClass.arraysDeepEqualsMethodId + get() = outerMostTestClass.arraysDeepEqualsMethodId val iterablesDeepEquals: MethodId - get() = currentTestClass.iterablesDeepEqualsMethodId + get() = outerMostTestClass.iterablesDeepEqualsMethodId val streamsDeepEquals: MethodId - get() = currentTestClass.streamsDeepEqualsMethodId + get() = outerMostTestClass.streamsDeepEqualsMethodId val mapsDeepEquals: MethodId - get() = currentTestClass.mapsDeepEqualsMethodId + get() = outerMostTestClass.mapsDeepEqualsMethodId val hasCustomEquals: MethodId - get() = currentTestClass.hasCustomEqualsMethodId + get() = outerMostTestClass.hasCustomEqualsMethodId val getArrayLength: MethodId - get() = currentTestClass.getArrayLengthMethodId + get() = outerMostTestClass.getArrayLengthMethodId } /** @@ -390,8 +391,6 @@ internal interface CgContextOwner { internal data class CgContext( override val classUnderTest: ClassId, override var currentExecutable: ExecutableId? = null, - override val collectedTestClassInterfaces: MutableSet = mutableSetOf(), - override val collectedTestClassAnnotations: MutableSet = mutableSetOf(), override val collectedExceptions: MutableSet = mutableSetOf(), override val collectedMethodAnnotations: MutableSet = mutableSetOf(), override val collectedImports: MutableSet = mutableSetOf(), @@ -430,7 +429,25 @@ internal data class CgContext( override lateinit var statesCache: EnvironmentFieldStateCache override lateinit var actual: CgVariable - override val currentTestClass: ClassId by lazy { + /** + * This property cannot be accessed outside of test class file scope + * (i.e. outside of [CgContextOwner.withTestClassFileScope]). + */ + override val outerMostTestClassContext: TestClassContext + get() = _outerMostTestClassContext ?: error("Accessing outerMostTestClassInfo out of class file scope") + + private var _outerMostTestClassContext: TestClassContext? = null + + /** + * This property cannot be accessed outside of test class scope + * (i.e. outside of [CgContextOwner.withTestClassScope]). + */ + override val currentTestClassContext: TestClassContext + get() = _currentTestClassContext ?: error("Accessing currentTestClassInfo out of class scope") + + private var _currentTestClassContext: TestClassContext? = null + + override val outerMostTestClass: ClassId by lazy { val packagePrefix = if (testClassPackageName.isNotEmpty()) "$testClassPackageName." else "" val simpleName = testClassCustomName ?: "${createTestClassName(classUnderTest.name)}Test" val name = "$packagePrefix$simpleName" @@ -441,14 +458,62 @@ internal data class CgContext( ) } - override 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(field == null) { "It is impossible for the test class to have more than one superclass" } - field = value + override lateinit var currentTestClass: ClassId + + override fun withTestClassFileScope(block: () -> R): R { + clearClassScope() + _outerMostTestClassContext = TestClassContext() + return try { + block() + } finally { + clearClassScope() + } + } + + override fun withTestClassScope(block: () -> R): R { + _currentTestClassContext = outerMostTestClassContext + currentTestClass = outerMostTestClass + return try { + block() + } finally { + _currentTestClassContext = null + } + } + + override fun withNestedClassScope(testClassModel: TestClassModel, block: () -> R): R { + val previousCurrentTestClassInfo = currentTestClassContext + val previousCurrentTestClass = currentTestClass + currentTestClass = createClassIdForNestedClass(testClassModel) + _currentTestClassContext = TestClassContext() + return try { + block() + } finally { + _currentTestClassContext = previousCurrentTestClassInfo + currentTestClass = previousCurrentTestClass } + } + + private fun createClassIdForNestedClass(testClassModel: TestClassModel): ClassId { + val simpleName = "${testClassModel.classUnderTest.simpleName}Test" + return BuiltinClassId( + name = currentTestClass.name + "$" + simpleName, + canonicalName = currentTestClass.canonicalName + "." + simpleName, + simpleName = simpleName + ) + } + + private fun clearClassScope() { + _outerMostTestClassContext = null + collectedImports.clear() + importedStaticMethods.clear() + importedClasses.clear() + testMethods.clear() + requiredUtilMethods.clear() + valueByModel.clear() + valueByModelId.clear() + mockFrameworkUsed = false + } + override var valueByModel: IdentityHashMap = IdentityHashMap() @@ -456,5 +521,5 @@ internal data class CgContext( override val currentMethodParameters: MutableMap = mutableMapOf() - override val testClassThisInstance: CgThisInstance = CgThisInstance(currentTestClass) + override val testClassThisInstance: CgThisInstance = CgThisInstance(outerMostTestClass) } \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgCallableAccessManager.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgCallableAccessManager.kt index 0c00b74706..83b506f889 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgCallableAccessManager.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgCallableAccessManager.kt @@ -160,7 +160,7 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA private fun BuiltinMethodId.findExceptionTypes(): Set { if (!this.isUtil) return emptySet() - with(currentTestClass) { + with(outerMostTestClass) { return when (this@findExceptionTypes) { getEnumConstantByNameMethodId -> setOf(IllegalAccessException::class.id) getStaticFieldValueMethodId, @@ -185,7 +185,7 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA private infix fun CgExpression?.canBeReceiverOf(executable: MethodId): Boolean = when { // TODO: rewrite by using CgMethodId, etc. - currentTestClass == executable.classId && this isThisInstanceOf currentTestClass -> true + outerMostTestClass == executable.classId && this isThisInstanceOf outerMostTestClass -> true executable.isStatic -> true else -> this?.type?.isSubtypeOf(executable.classId) ?: false } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt index 92a8e0320b..91fae0d3a9 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt @@ -3,11 +3,8 @@ package org.utbot.framework.codegen.model.constructor.tree import org.utbot.common.PathUtil import org.utbot.framework.assemble.assemble import org.utbot.framework.codegen.ForceStaticMocking -import org.utbot.framework.codegen.Junit4 -import org.utbot.framework.codegen.Junit5 import org.utbot.framework.codegen.ParametrizedTestSource import org.utbot.framework.codegen.RuntimeExceptionTestsBehaviour.PASS -import org.utbot.framework.codegen.TestNg import org.utbot.framework.codegen.model.constructor.CgMethodTestSet import org.utbot.framework.codegen.model.constructor.builtin.closeMethodIdOrNull import org.utbot.framework.codegen.model.constructor.builtin.forName @@ -15,7 +12,6 @@ import org.utbot.framework.codegen.model.constructor.builtin.getClass import org.utbot.framework.codegen.model.constructor.builtin.getTargetException import org.utbot.framework.codegen.model.constructor.builtin.invoke import org.utbot.framework.codegen.model.constructor.builtin.newInstance -import org.utbot.framework.codegen.model.constructor.builtin.setArrayElement import org.utbot.framework.codegen.model.constructor.context.CgContext import org.utbot.framework.codegen.model.constructor.context.CgContextOwner import org.utbot.framework.codegen.model.constructor.util.CgComponents @@ -26,9 +22,9 @@ import org.utbot.framework.codegen.model.constructor.util.classCgClassId import org.utbot.framework.codegen.model.constructor.util.needExpectedDeclaration import org.utbot.framework.codegen.model.constructor.util.overridesEquals import org.utbot.framework.codegen.model.constructor.util.plus +import org.utbot.framework.codegen.model.constructor.util.setArgumentsArrayElement import org.utbot.framework.codegen.model.constructor.util.typeCast import org.utbot.framework.codegen.model.tree.CgAllocateArray -import org.utbot.framework.codegen.model.tree.CgAnnotation import org.utbot.framework.codegen.model.tree.CgArrayElementAccess import org.utbot.framework.codegen.model.tree.CgClassId import org.utbot.framework.codegen.model.tree.CgDeclaration @@ -70,7 +66,6 @@ import org.utbot.framework.codegen.model.tree.buildParameterizedTestDataProvider import org.utbot.framework.codegen.model.tree.buildTestMethod import org.utbot.framework.codegen.model.tree.convertDocToCg import org.utbot.framework.codegen.model.tree.toStatement -import org.utbot.framework.codegen.model.util.at import org.utbot.framework.codegen.model.util.canBeSetIn import org.utbot.framework.codegen.model.util.equalTo import org.utbot.framework.codegen.model.util.get @@ -118,7 +113,6 @@ import org.utbot.framework.plugin.api.UtVoidModel import org.utbot.framework.plugin.api.onFailure import org.utbot.framework.plugin.api.onSuccess import org.utbot.framework.plugin.api.util.booleanClassId -import org.utbot.framework.plugin.api.util.builtinStaticMethodId import org.utbot.framework.plugin.api.util.doubleArrayClassId import org.utbot.framework.plugin.api.util.doubleClassId import org.utbot.framework.plugin.api.util.doubleWrapperClassId @@ -139,7 +133,6 @@ import org.utbot.framework.plugin.api.util.isPrimitiveWrapper import org.utbot.framework.plugin.api.util.isRefType import org.utbot.framework.plugin.api.util.jClass import org.utbot.framework.plugin.api.util.kClass -import org.utbot.framework.plugin.api.util.methodId import org.utbot.framework.plugin.api.util.objectArrayClassId import org.utbot.framework.plugin.api.util.objectClassId import org.utbot.framework.plugin.api.util.stringClassId @@ -1369,7 +1362,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c return withDataProviderScope { dataProviderMethod(dataProviderMethodName) { val argListLength = testSet.executions.size - val argListVariable = createArgList(argListLength) + val argListVariable = testFrameworkManager.createArgList(argListLength) emptyLine() @@ -1491,161 +1484,15 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c CgAllocateArray(objectArrayClassId, objectClassId, arguments.size) } for ((i, argument) in arguments.withIndex()) { - setArgumentsArrayElement(argsArray, i, argument) - } - when (testFramework) { - Junit5 -> { - +argsVariable[addToListMethodId]( - argumentsClassId[argumentsMethodId](argsArray) - ) - } - TestNg -> { - setArgumentsArrayElement(argsVariable, executionIndex, argsArray) - } - Junit4 -> error("Parameterized tests are not supported for JUnit4") - } - } - - /** - * Sets an element of arguments array in parameterized test, - * if test framework represents arguments as array. - */ - private fun setArgumentsArrayElement(array: CgVariable, index: Int, value: CgExpression) { - when (array.type) { - objectClassId -> { - +java.lang.reflect.Array::class.id[setArrayElement](array, index, value) - } - else -> array.at(index) `=` value + setArgumentsArrayElement(argsArray, i, argument, this) } + testFrameworkManager.passArgumentsToArgsVariable(argsVariable, argsArray, executionIndex) } - /** - * Creates annotations for data provider method in parameterized tests - * depending on test framework. - */ - private fun createDataProviderAnnotations(dataProviderMethodName: String?): MutableList = - when (testFramework) { - Junit5 -> mutableListOf() - TestNg -> mutableListOf( - annotation( - testFramework.methodSourceAnnotationId, - listOf("name" to CgLiteral(stringClassId, dataProviderMethodName)) - ), - ) - Junit4 -> error("Parameterized tests are not supported for JUnit4") - } - - /** - * Creates declaration of argList collection in parameterized tests. - */ - private fun createArgList(length: Int): CgVariable { - val argListName = "argList" - return when (testFramework) { - Junit5 -> - newVar(argListClassId, argListName) { - val constructor = ConstructorId(argListClassId, emptyList()) - constructor.invoke() - } - TestNg -> - newVar(argListClassId, argListName) { - CgAllocateArray(argListClassId, Array::class.java.id, length) - } - Junit4 -> error("Parameterized tests are not supported for JUnit4") - } - } - - /** - * Creates a [ClassId] for arguments collection. - */ - private val argListClassId: ClassId - get() = when (testFramework) { - Junit5 -> { - val arrayListId = java.util.ArrayList::class.id - BuiltinClassId( - name = arrayListId.name, - simpleName = arrayListId.simpleName, - canonicalName = arrayListId.canonicalName, - packageName = arrayListId.packageName, - typeParameters = TypeParameters(listOf(argumentsClassId)) - ) - } - TestNg -> { - val outerArrayId = Array?>::class.id - val innerArrayId = BuiltinClassId( - name = objectArrayClassId.name, - simpleName = objectArrayClassId.simpleName, - canonicalName = objectArrayClassId.canonicalName, - packageName = objectArrayClassId.packageName, - elementClassId = objectClassId, - typeParameters = TypeParameters(listOf(objectClassId)) - ) - - BuiltinClassId( - name = outerArrayId.name, - simpleName = outerArrayId.simpleName, - canonicalName = outerArrayId.canonicalName, - packageName = outerArrayId.packageName, - elementClassId = innerArrayId, - typeParameters = TypeParameters(listOf(innerArrayId)) - ) - } - Junit4 -> error("Parameterized tests are not supported for JUnit4") - } - - - /** - * A [MethodId] to add an item into [ArrayList]. - */ - private val addToListMethodId: MethodId - get() = methodId( - classId = ArrayList::class.id, - name = "add", - returnType = booleanClassId, - arguments = arrayOf(Object::class.id), - ) - - /** - * A [ClassId] of class `org.junit.jupiter.params.provider.Arguments` - */ - private val argumentsClassId: BuiltinClassId - get() = BuiltinClassId( - name = "org.junit.jupiter.params.provider.Arguments", - simpleName = "Arguments", - canonicalName = "org.junit.jupiter.params.provider.Arguments", - packageName = "org.junit.jupiter.params.provider" - ) - - /** - * A [MethodId] to call JUnit Arguments method. - */ - private val argumentsMethodId: BuiltinMethodId - get() = builtinStaticMethodId( - classId = argumentsClassId, - name = "arguments", - returnType = argumentsClassId, - // vararg of Objects - arguments = arrayOf(objectArrayClassId) - ) - private fun containsFailureExecution(testSet: CgMethodTestSet) = testSet.executions.any { it.result is UtExecutionFailure } - private fun collectParameterizedTestAnnotations(dataProviderMethodName: String?): Set = - when (testFramework) { - Junit5 -> setOf( - annotation(testFramework.parameterizedTestAnnotationId), - annotation(testFramework.methodSourceAnnotationId, dataProviderMethodName), - ) - TestNg -> setOf( - annotation( - testFramework.parameterizedTestAnnotationId, - listOf("dataProvider" to CgLiteral(stringClassId, dataProviderMethodName)) - ), - ) - Junit4 -> error("Parameterized tests are not supported for JUnit4") - } - private fun testMethod( methodName: String, displayName: String?, @@ -1655,26 +1502,13 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c body: () -> Unit, ): CgTestMethod { collectedMethodAnnotations += if (parameterized) { - collectParameterizedTestAnnotations(dataProviderMethodName) + testFrameworkManager.collectParameterizedTestAnnotations(dataProviderMethodName) } else { setOf(annotation(testFramework.testAnnotationId)) } - /* Add a short test's description depending on the test framework type: - DisplayName annotation in case of JUni5, and description argument to Test annotation in case of TestNG. - */ - if (displayName != null) { - when (testFramework) { - is Junit5 -> { - displayName.let { testFrameworkManager.addDisplayName(it) } - } - is TestNg -> { - testFrameworkManager.addTestDescription(displayName) - } - else -> { - // nothing - } - } + displayName?.let { + testFrameworkManager.addTestDescription(displayName) } val result = currentExecution!!.result @@ -1735,12 +1569,12 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c private fun dataProviderMethod(dataProviderMethodName: String, body: () -> Unit): CgParameterizedTestDataProviderMethod { return buildParameterizedTestDataProviderMethod { name = dataProviderMethodName - returnType = argListClassId + returnType = testFramework.argListClassId statements = block(body) // Exceptions and annotations assignment must run after the statements block is build, // because we collect info about exceptions and required annotations while building the statements exceptions += collectedExceptions - annotations += createDataProviderAnnotations(dataProviderMethodName) + annotations += testFrameworkManager.createDataProviderAnnotations(dataProviderMethodName) } } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt index 97ab11c995..bd331e99a0 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt @@ -14,6 +14,7 @@ import org.utbot.framework.codegen.model.tree.CgParameterDeclaration import org.utbot.framework.codegen.model.tree.CgRegion import org.utbot.framework.codegen.model.tree.CgSimpleRegion import org.utbot.framework.codegen.model.tree.CgStaticsRegion +import org.utbot.framework.codegen.model.tree.CgTestClass import org.utbot.framework.codegen.model.tree.CgTestClassFile import org.utbot.framework.codegen.model.tree.CgTestMethod import org.utbot.framework.codegen.model.tree.CgTestMethodCluster @@ -27,7 +28,7 @@ import org.utbot.framework.codegen.model.visitor.importUtilMethodDependencies import org.utbot.framework.plugin.api.ExecutableId import org.utbot.framework.plugin.api.MethodId import org.utbot.framework.plugin.api.UtMethodTestSet -import org.utbot.framework.plugin.api.UtSymbolicExecution +import org.utbot.framework.codegen.model.constructor.TestClassModel import org.utbot.framework.plugin.api.util.description import org.utbot.framework.plugin.api.util.kClass import kotlin.reflect.KClass @@ -38,48 +39,81 @@ internal class CgTestClassConstructor(val context: CgContext) : private val methodConstructor = CgComponents.getMethodConstructorBy(context) private val nameGenerator = CgComponents.getNameGeneratorBy(context) - - private val cgDataProviderMethods = mutableListOf() + private val testFrameworkManager = CgComponents.getTestFrameworkManagerBy(context) private val testsGenerationReport: TestsGenerationReport = TestsGenerationReport() /** - * Given a list of test sets constructs CgTestClass + * Given a testClass model constructs CgTestClass */ - fun construct(testSets: Collection): CgTestClassFile { + fun construct(testClassModel: TestClassModel): CgTestClassFile { return buildTestClassFile { - testClass = buildTestClass { - // TODO: obtain test class from plugin - id = currentTestClass - body = buildTestClassBody { - cgDataProviderMethods.clear() - for (testSet in testSets) { - updateCurrentExecutable(testSet.executableId) - val currentMethodUnderTestRegions = construct(testSet) ?: continue - val executableUnderTestCluster = CgExecutableUnderTestCluster( - "Test suites for executable $currentExecutable", - currentMethodUnderTestRegions + this.testClass = withTestClassScope { constructTestClass(testClassModel) } + imports += context.collectedImports + testsGenerationReport = this@CgTestClassConstructor.testsGenerationReport + } + } + + private fun constructTestClass(testClassModel: TestClassModel): CgTestClass { + return buildTestClass { + id = currentTestClass + + if (currentTestClass != outerMostTestClass) { + isNested = true + isStatic = testFramework.nestedClassesShouldBeStatic + testFrameworkManager.annotationForNestedClasses?.let { + currentTestClassContext.collectedTestClassAnnotations += it + } + } + if (testClassModel.nestedClasses.isNotEmpty()) { + testFrameworkManager.annotationForOuterClasses?.let { + currentTestClassContext.collectedTestClassAnnotations += it + } + } + + body = buildTestClassBody { + for (nestedClass in testClassModel.nestedClasses) { + nestedClassRegions += CgSimpleRegion( + "Tests for ${nestedClass.classUnderTest.simpleName}", + listOf( + withNestedClassScope(nestedClass) { constructTestClass(nestedClass) } ) - testMethodRegions += executableUnderTestCluster - } + ) + } - dataProvidersAndUtilMethodsRegion += CgStaticsRegion( - "Data providers and utils methods", - cgDataProviderMethods + createUtilMethods() + for (testSet in testClassModel.methodTestSets) { + updateCurrentExecutable(testSet.executableId) + val currentMethodUnderTestRegions = constructTestSet(testSet) ?: continue + val executableUnderTestCluster = CgExecutableUnderTestCluster( + "Test suites for executable $currentExecutable", + currentMethodUnderTestRegions ) + testMethodRegions += executableUnderTestCluster } - // It is important that annotations, superclass and interfaces assignment is run after - // all methods are generated so that all necessary info is already present in the context - annotations += context.collectedTestClassAnnotations - superclass = context.testClassSuperclass - interfaces += context.collectedTestClassInterfaces + + val utilMethods = if (currentTestClass == outerMostTestClass) + createUtilMethods() + else + emptyList() + + val additionalMethods = currentTestClassContext.cgDataProviderMethods + utilMethods + + dataProvidersAndUtilMethodsRegion += CgStaticsRegion( + "Data providers and utils methods", + additionalMethods + ) + } + // It is important that annotations, superclass and interfaces assignment is run after + // all methods are generated so that all necessary info is already present in the context + with (currentTestClassContext) { + annotations += collectedTestClassAnnotations + superclass = testClassSuperclass + interfaces += collectedTestClassInterfaces } - imports += context.collectedImports - testsGenerationReport = this@CgTestClassConstructor.testsGenerationReport } } - private fun construct(testSet: CgMethodTestSet): List>? { + private fun constructTestSet(testSet: CgMethodTestSet): List>? { if (testSet.executions.isEmpty()) { return null } @@ -150,8 +184,9 @@ internal class CgTestClassConstructor(val context: CgContext) : requiredFields += parameterizedTestMethod.requiredFields - cgDataProviderMethods += + testFrameworkManager.addDataProvider( methodConstructor.createParameterizedTestDataProvider(testSet, dataProviderMethodName) + ) regions += CgSimpleRegion( "Parameterized test for method ${methodUnderTest.displayName}", diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/TestFrameworkManager.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/TestFrameworkManager.kt index b33f2324c2..157328aef6 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/TestFrameworkManager.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/TestFrameworkManager.kt @@ -3,6 +3,7 @@ package org.utbot.framework.codegen.model.constructor.tree import org.utbot.framework.codegen.Junit4 import org.utbot.framework.codegen.Junit5 import org.utbot.framework.codegen.TestNg +import org.utbot.framework.codegen.model.constructor.TestClassContext import org.utbot.framework.codegen.model.constructor.builtin.arraysDeepEqualsMethodId import org.utbot.framework.codegen.model.constructor.builtin.deepEqualsMethodId import org.utbot.framework.codegen.model.constructor.builtin.forName @@ -13,23 +14,34 @@ import org.utbot.framework.codegen.model.constructor.builtin.streamsDeepEqualsMe import org.utbot.framework.codegen.model.constructor.context.CgContext import org.utbot.framework.codegen.model.constructor.context.CgContextOwner import org.utbot.framework.codegen.model.constructor.util.CgComponents +import org.utbot.framework.codegen.model.constructor.util.addToListMethodId +import org.utbot.framework.codegen.model.constructor.util.argumentsClassId +import org.utbot.framework.codegen.model.constructor.util.argumentsMethodId import org.utbot.framework.codegen.model.constructor.util.classCgClassId import org.utbot.framework.codegen.model.constructor.util.importIfNeeded +import org.utbot.framework.codegen.model.constructor.util.setArgumentsArrayElement +import org.utbot.framework.codegen.model.tree.CgAllocateArray +import org.utbot.framework.codegen.model.tree.CgAnnotation import org.utbot.framework.codegen.model.tree.CgEnumConstantAccess import org.utbot.framework.codegen.model.tree.CgExpression import org.utbot.framework.codegen.model.tree.CgGetJavaClass +import org.utbot.framework.codegen.model.tree.CgGetKotlinClass import org.utbot.framework.codegen.model.tree.CgLiteral +import org.utbot.framework.codegen.model.tree.CgMethod import org.utbot.framework.codegen.model.tree.CgMethodCall import org.utbot.framework.codegen.model.tree.CgMultipleArgsAnnotation import org.utbot.framework.codegen.model.tree.CgNamedAnnotationArgument import org.utbot.framework.codegen.model.tree.CgSingleArgAnnotation import org.utbot.framework.codegen.model.tree.CgValue +import org.utbot.framework.codegen.model.tree.CgVariable import org.utbot.framework.codegen.model.util.classLiteralAnnotationArgument import org.utbot.framework.codegen.model.util.isAccessibleFrom import org.utbot.framework.codegen.model.util.resolve import org.utbot.framework.codegen.model.util.stringLiteral import org.utbot.framework.plugin.api.BuiltinMethodId import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.framework.plugin.api.ConstructorId import org.utbot.framework.plugin.api.util.booleanArrayClassId import org.utbot.framework.plugin.api.util.byteArrayClassId import org.utbot.framework.plugin.api.util.charArrayClassId @@ -39,6 +51,7 @@ import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.intArrayClassId import org.utbot.framework.plugin.api.util.longArrayClassId import org.utbot.framework.plugin.api.util.shortArrayClassId +import org.utbot.framework.plugin.api.util.stringClassId import java.util.concurrent.TimeUnit @Suppress("MemberVisibilityCanBePrivate") @@ -66,8 +79,17 @@ internal abstract class TestFrameworkManager(val context: CgContext) val assertFloatArrayEquals = context.testFramework.assertFloatArrayEquals val assertDoubleArrayEquals = context.testFramework.assertDoubleArrayEquals + // Points to the class, into which data provider methods in parametrized tests should be put (current or outermost). + // It is needed, because data provider methods are static and thus may not be put into inner classes, e.g. in JUnit5 + // all data providers should be placed in the outermost class. + protected abstract val dataProviderMethodsHolder: TestClassContext + protected val statementConstructor = CgComponents.getStatementConstructorBy(context) + abstract val annotationForNestedClasses: CgAnnotation? + + abstract val annotationForOuterClasses: CgAnnotation? + protected open val timeoutArgumentName: String = "timeout" open fun assertEquals(expected: CgValue, actual: CgValue) { @@ -100,12 +122,12 @@ internal abstract class TestFrameworkManager(val context: CgContext) } open fun getDeepEqualsAssertion(expected: CgExpression, actual: CgExpression): CgMethodCall { - requiredUtilMethods += currentTestClass.deepEqualsMethodId - requiredUtilMethods += currentTestClass.arraysDeepEqualsMethodId - requiredUtilMethods += currentTestClass.iterablesDeepEqualsMethodId - requiredUtilMethods += currentTestClass.streamsDeepEqualsMethodId - requiredUtilMethods += currentTestClass.mapsDeepEqualsMethodId - requiredUtilMethods += currentTestClass.hasCustomEqualsMethodId + requiredUtilMethods += outerMostTestClass.deepEqualsMethodId + requiredUtilMethods += outerMostTestClass.arraysDeepEqualsMethodId + requiredUtilMethods += outerMostTestClass.iterablesDeepEqualsMethodId + requiredUtilMethods += outerMostTestClass.streamsDeepEqualsMethodId + requiredUtilMethods += outerMostTestClass.mapsDeepEqualsMethodId + requiredUtilMethods += outerMostTestClass.hasCustomEqualsMethodId // TODO we cannot use common assertEquals because of using custom deepEquals // For this reason we have to use assertTrue here @@ -154,6 +176,20 @@ internal abstract class TestFrameworkManager(val context: CgContext) // TestNg allows both approaches, we use similar to JUnit5 abstract fun expectException(exception: ClassId, block: () -> Unit) + /** + * Creates annotations for data provider method in parameterized tests + */ + abstract fun createDataProviderAnnotations(dataProviderMethodName: String): MutableList + + /** + * Creates declaration of argList collection in parameterized tests. + */ + abstract fun createArgList(length: Int): CgVariable + + abstract fun collectParameterizedTestAnnotations(dataProviderMethodName: String?): Set + + abstract fun passArgumentsToArgsVariable(argsVariable: CgVariable, argsArray: CgVariable, executionIndex: Int) + open fun expectTimeout(timeoutMs: Long, block: () -> Unit) {} open fun setTestExecutionTimeout(timeoutMs: Long) { @@ -173,28 +209,11 @@ internal abstract class TestFrameworkManager(val context: CgContext) } } + /** - * Supplements TestNG @Test annotation with a description. - * It looks like @Test(description="...") - * - * Should be used only with TestNG. - * @see issue-576 on GitHub + * Add a short test's description depending on the test framework type: */ - open fun addTestDescription(description: String?) { - if (description == null) return - val testAnnotation = - collectedMethodAnnotations.singleOrNull { it.classId == testFramework.testAnnotationId } - - val descriptionArgument = CgNamedAnnotationArgument("description", stringLiteral(description)) - if (testAnnotation is CgMultipleArgsAnnotation) { - testAnnotation.arguments += descriptionArgument - } else { - collectedMethodAnnotations += CgMultipleArgsAnnotation( - testFramework.testAnnotationId, - mutableListOf(descriptionArgument) - ) - } - } + abstract fun addTestDescription(description: String) abstract fun disableTestMethod(reason: String) @@ -214,9 +233,22 @@ internal abstract class TestFrameworkManager(val context: CgContext) } else { statementConstructor.newVar(classCgClassId) { Class::class.id[forName](name) } } + + fun addDataProvider(dataProvider: CgMethod) { + dataProviderMethodsHolder.cgDataProviderMethods += dataProvider + } } internal class TestNgManager(context: CgContext) : TestFrameworkManager(context) { + override val dataProviderMethodsHolder: TestClassContext + get() = currentTestClassContext + + override val annotationForNestedClasses: CgAnnotation? + get() = null + + override val annotationForOuterClasses: CgAnnotation? + get() = null + override val timeoutArgumentName: String = "timeOut" private val assertThrows: BuiltinMethodId @@ -252,6 +284,52 @@ internal class TestNgManager(context: CgContext) : TestFrameworkManager(context) +assertions[assertThrows](exception.toExceptionClass(), lambda) } + override fun createDataProviderAnnotations(dataProviderMethodName: String) = + mutableListOf( + statementConstructor.annotation( + testFramework.methodSourceAnnotationId, + listOf("name" to stringLiteral(dataProviderMethodName)) + ), + ) + + override fun createArgList(length: Int) = + statementConstructor.newVar(testFramework.argListClassId, "argList") { + CgAllocateArray(testFramework.argListClassId, Array::class.java.id, length) + } + + override fun collectParameterizedTestAnnotations(dataProviderMethodName: String?) = setOf( + statementConstructor.annotation( + testFramework.parameterizedTestAnnotationId, + listOf("dataProvider" to CgLiteral(stringClassId, dataProviderMethodName)) + ) + ) + + override fun passArgumentsToArgsVariable(argsVariable: CgVariable, argsArray: CgVariable, executionIndex: Int) = + setArgumentsArrayElement(argsVariable, executionIndex, argsArray, statementConstructor) + + /** + * Supplements TestNG @Test annotation with a description. + * It looks like @Test(description="...") + * + * @see issue-576 on GitHub + */ + private fun addDescriptionAnnotation(description: String) { + val testAnnotation = + collectedMethodAnnotations.singleOrNull { it.classId == testFramework.testAnnotationId } + + val descriptionArgument = CgNamedAnnotationArgument("description", stringLiteral(description)) + if (testAnnotation is CgMultipleArgsAnnotation) { + testAnnotation.arguments += descriptionArgument + } else { + collectedMethodAnnotations += CgMultipleArgsAnnotation( + testFramework.testAnnotationId, + mutableListOf(descriptionArgument) + ) + } + } + + override fun addTestDescription(description: String) = addDescriptionAnnotation(description) + override fun disableTestMethod(reason: String) { require(testFramework is TestNg) { "According to settings, TestNg was expected, but got: $testFramework" } @@ -305,6 +383,29 @@ internal class TestNgManager(context: CgContext) : TestFrameworkManager(context) } internal class Junit4Manager(context: CgContext) : TestFrameworkManager(context) { + private val parametrizedTestsNotSupportedError: Nothing + get() = error("Parametrized tests are not supported for JUnit4") + + override val dataProviderMethodsHolder: TestClassContext + get() = parametrizedTestsNotSupportedError + + override val annotationForNestedClasses: CgAnnotation? + get() = null + + override val annotationForOuterClasses: CgAnnotation + get() { + require(testFramework is Junit4) { "According to settings, JUnit4 was expected, but got: $testFramework" } + return statementConstructor.annotation( + testFramework.runWithAnnotationClassId, + testFramework.enclosedClassId.let { + when (codegenLanguage) { + CodegenLanguage.JAVA -> CgGetJavaClass(it) + CodegenLanguage.KOTLIN -> CgGetKotlinClass(it) + } + } + ) + } + override fun expectException(exception: ClassId, block: () -> Unit) { require(testFramework is Junit4) { "According to settings, JUnit4 was expected, but got: $testFramework" } @@ -325,6 +426,20 @@ internal class Junit4Manager(context: CgContext) : TestFrameworkManager(context) block() } + override fun createDataProviderAnnotations(dataProviderMethodName: String) = + parametrizedTestsNotSupportedError + + override fun createArgList(length: Int) = + parametrizedTestsNotSupportedError + + override fun collectParameterizedTestAnnotations(dataProviderMethodName: String?) = + parametrizedTestsNotSupportedError + + override fun passArgumentsToArgsVariable(argsVariable: CgVariable, argsArray: CgVariable, executionIndex: Int) = + parametrizedTestsNotSupportedError + + override fun addTestDescription(description: String) = Unit + override fun disableTestMethod(reason: String) { require(testFramework is Junit4) { "According to settings, JUnit4 was expected, but got: $testFramework" } @@ -341,6 +456,19 @@ internal class Junit4Manager(context: CgContext) : TestFrameworkManager(context) } internal class Junit5Manager(context: CgContext) : TestFrameworkManager(context) { + override val dataProviderMethodsHolder: TestClassContext + get() = outerMostTestClassContext + + override val annotationForNestedClasses: CgAnnotation + get() { + require(testFramework is Junit5) { "According to settings, JUnit5 was expected, but got: $testFramework" } + + return statementConstructor.annotation(testFramework.nestedTestClassAnnotationId) + } + + override val annotationForOuterClasses: CgAnnotation? + get() = null + private val assertThrows: BuiltinMethodId get() { require(testFramework is Junit5) { "According to settings, JUnit5 was expected, but got: $testFramework" } @@ -354,6 +482,29 @@ internal class Junit5Manager(context: CgContext) : TestFrameworkManager(context) +assertions[assertThrows](exception.toExceptionClass(), lambda) } + override fun createDataProviderAnnotations(dataProviderMethodName: String) = mutableListOf() + + override fun createArgList(length: Int) = + statementConstructor.newVar(testFramework.argListClassId, "argList") { + val constructor = ConstructorId(testFramework.argListClassId, emptyList()) + constructor.invoke() + } + + override fun collectParameterizedTestAnnotations(dataProviderMethodName: String?) = setOf( + statementConstructor.annotation(testFramework.parameterizedTestAnnotationId), + statementConstructor.annotation( + testFramework.methodSourceAnnotationId, + "${outerMostTestClass.canonicalName}#$dataProviderMethodName" + ) + ) + + override fun passArgumentsToArgsVariable(argsVariable: CgVariable, argsArray: CgVariable, executionIndex: Int) { + +argsVariable[addToListMethodId]( + argumentsClassId[argumentsMethodId](argsArray) + ) + } + + override fun expectTimeout(timeoutMs : Long, block: () -> Unit) { require(testFramework is Junit5) { "According to settings, JUnit5 was expected, but got: $testFramework" } val lambda = statementConstructor.lambda(testFramework.executableClassId) { block() } @@ -389,6 +540,8 @@ internal class Junit5Manager(context: CgContext) : TestFrameworkManager(context) ) } + override fun addTestDescription(description: String) = addDisplayName(description) + override fun disableTestMethod(reason: String) { require(testFramework is Junit5) { "According to settings, JUnit5 was expected, but got: $testFramework" } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/ConstructorUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/ConstructorUtils.kt index d2102adf0c..179e9272e0 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/ConstructorUtils.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/ConstructorUtils.kt @@ -30,8 +30,11 @@ import org.utbot.framework.plugin.api.util.shortClassId import org.utbot.framework.plugin.api.util.underlyingType import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.PersistentSet +import org.utbot.framework.codegen.model.constructor.builtin.setArrayElement +import org.utbot.framework.codegen.model.constructor.tree.CgCallableAccessManager import org.utbot.framework.codegen.model.tree.CgAllocateInitializedArray import org.utbot.framework.codegen.model.tree.CgArrayInitializer +import org.utbot.framework.codegen.model.util.at import org.utbot.framework.plugin.api.BuiltinClassId import org.utbot.framework.plugin.api.BuiltinMethodId import org.utbot.framework.plugin.api.ClassId @@ -45,6 +48,10 @@ import org.utbot.framework.plugin.api.UtNullModel import org.utbot.framework.plugin.api.UtPrimitiveModel import org.utbot.framework.plugin.api.WildcardTypeParameter import org.utbot.framework.plugin.api.util.arrayLikeName +import org.utbot.framework.plugin.api.util.builtinStaticMethodId +import org.utbot.framework.plugin.api.util.methodId +import org.utbot.framework.plugin.api.util.objectArrayClassId +import org.utbot.framework.plugin.api.util.objectClassId internal data class EnvironmentFieldStateCache( val thisInstance: FieldStateCache, @@ -124,6 +131,40 @@ data class ExpressionWithType(val type: ClassId, val expression: CgExpression) val classCgClassId = CgClassId(Class::class.id, typeParameters = WildcardTypeParameter(), isNullable = false) +/** + * A [MethodId] to add an item into [ArrayList]. + */ +internal val addToListMethodId: MethodId + get() = methodId( + classId = ArrayList::class.id, + name = "add", + returnType = booleanClassId, + arguments = arrayOf(Object::class.id), + ) + +/** + * A [ClassId] of class `org.junit.jupiter.params.provider.Arguments` + */ +internal val argumentsClassId: BuiltinClassId + get() = BuiltinClassId( + name = "org.junit.jupiter.params.provider.Arguments", + simpleName = "Arguments", + canonicalName = "org.junit.jupiter.params.provider.Arguments", + packageName = "org.junit.jupiter.params.provider" + ) + +/** + * A [MethodId] to call JUnit Arguments method. + */ +internal val argumentsMethodId: BuiltinMethodId + get() = builtinStaticMethodId( + classId = argumentsClassId, + name = "arguments", + returnType = argumentsClassId, + // vararg of Objects + arguments = arrayOf(objectArrayClassId) + ) + internal fun getStaticFieldVariableName(owner: ClassId, path: FieldPath): String { val elements = mutableListOf() elements += owner.simpleName.decapitalize() @@ -221,6 +262,24 @@ internal fun CgContextOwner.typeCast( return CgTypeCast(targetType, expression, isSafetyCast) } +/** + * Sets an element of arguments array in parameterized test, + * if test framework represents arguments as array. + */ +internal fun T.setArgumentsArrayElement( + array: CgVariable, + index: Int, + value: CgExpression, + constructor: CgStatementConstructor +) where T : CgContextOwner, T: CgCallableAccessManager { + when (array.type) { + objectClassId -> { + +java.lang.reflect.Array::class.id[setArrayElement](array, index, value) + } + else -> with(constructor) { array.at(index) `=` value } + } +} + @Suppress("unused") internal fun newArrayOf(elementType: ClassId, values: List): CgAllocateInitializedArray { val arrayType = arrayTypeOf(elementType) @@ -230,6 +289,7 @@ internal fun newArrayOf(elementType: ClassId, values: List): CgAll internal fun arrayInitializer(arrayType: ClassId, elementType: ClassId, values: List): CgArrayInitializer = CgArrayInitializer(arrayType, elementType, values) + /** * For a given [elementType] returns a [ClassId] of an array with elements of this type. * For example, for an id of `int` the result will be an id of `int[]`. diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/Builders.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/Builders.kt index d328e59217..57a91672a3 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/Builders.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/Builders.kt @@ -27,9 +27,11 @@ class CgTestClassBuilder : CgBuilder { val annotations: MutableList = mutableListOf() var superclass: ClassId? = null val interfaces: MutableList = mutableListOf() + var isStatic: Boolean = false + var isNested: Boolean = false lateinit var body: CgTestClassBody - override fun build() = CgTestClass(id, annotations, superclass, interfaces, body) + override fun build() = CgTestClass(id, annotations, superclass, interfaces, body, isStatic, isNested) } fun buildTestClass(init: CgTestClassBuilder.() -> Unit) = CgTestClassBuilder().apply(init).build() @@ -39,7 +41,9 @@ class CgTestClassBodyBuilder : CgBuilder { val dataProvidersAndUtilMethodsRegion: MutableList> = mutableListOf() - override fun build() = CgTestClassBody(testMethodRegions, dataProvidersAndUtilMethodsRegion) + val nestedClassRegions: MutableList> = mutableListOf() + + override fun build() = CgTestClassBody(testMethodRegions, dataProvidersAndUtilMethodsRegion, nestedClassRegions) } fun buildTestClassBody(init: CgTestClassBodyBuilder.() -> Unit) = CgTestClassBodyBuilder().apply(init).build() diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/CgElement.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/CgElement.kt index f3ce6cea94..ec78d73dea 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/CgElement.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/CgElement.kt @@ -122,6 +122,8 @@ data class CgTestClass( val superclass: ClassId?, val interfaces: List, val body: CgTestClassBody, + val isStatic: Boolean, + val isNested: Boolean ) : CgElement { val packageName = id.packageName val simpleName = id.simpleName @@ -129,7 +131,8 @@ data class CgTestClass( data class CgTestClassBody( val testMethodRegions: List, - val utilsRegion: List> + val utilsRegion: List>, + val nestedClassRegions: List> ) : CgElement { val regions: List> get() = testMethodRegions diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt index a2ce14285b..92e4aebf78 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt @@ -121,7 +121,7 @@ internal abstract class CgAbstractRenderer(val context: CgContext, val printer: } private val MethodId.accessibleByName: Boolean - get() = (context.shouldOptimizeImports && this in context.importedStaticMethods) || classId == context.currentTestClass + get() = (context.shouldOptimizeImports && this in context.importedStaticMethods) || classId == context.outerMostTestClass override fun visit(element: CgElement) { val error = @@ -137,7 +137,7 @@ internal abstract class CgAbstractRenderer(val context: CgContext, val printer: override fun visit(element: CgTestClassBody) { // render regions for test methods and utils - for ((i, region) in (element.regions + element.utilsRegion).withIndex()) { + for ((i, region) in (element.regions + element.nestedClassRegions + element.utilsRegion).withIndex()) { if (i != 0) println() region.accept(this) @@ -196,7 +196,7 @@ internal abstract class CgAbstractRenderer(val context: CgContext, val printer: } override fun visit(element: CgUtilMethod) { - context.currentTestClass + context.outerMostTestClass .utilMethodById(element.id, context) .split("\n") .forEach { line -> println(line) } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgJavaRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgJavaRenderer.kt index bc712bc51a..b1f00fa728 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgJavaRenderer.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgJavaRenderer.kt @@ -62,7 +62,11 @@ internal class CgJavaRenderer(context: CgContext, printer: CgPrinter = CgPrinter for (annotation in element.annotations) { annotation.accept(this) } - print("public class ") + print("public ") + if (element.isStatic) { + print("static ") + } + print("class ") print(element.simpleName) if (element.superclass != null) { print(" extends ${element.superclass.asString()}") diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt index a406c93fee..8a25ad5a7f 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt @@ -72,6 +72,9 @@ internal class CgKotlinRenderer(context: CgContext, printer: CgPrinter = CgPrint for (annotation in element.annotations) { annotation.accept(this) } + if (!element.isStatic && element.isNested) { + print("inner ") + } print("class ") print(element.simpleName) if (element.superclass != null || element.interfaces.isNotEmpty()) { diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/UtilMethods.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/UtilMethods.kt index 2098303280..2b403f8730 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/UtilMethods.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/UtilMethods.kt @@ -818,10 +818,10 @@ fun getArrayLength(codegenLanguage: CodegenLanguage) = } internal fun CgContextOwner.importUtilMethodDependencies(id: MethodId) { - for (classId in currentTestClass.regularImportsByUtilMethod(id, codegenLanguage)) { + for (classId in outerMostTestClass.regularImportsByUtilMethod(id, codegenLanguage)) { importIfNeeded(classId) } - for (methodId in currentTestClass.staticImportsByUtilMethod(id)) { + for (methodId in outerMostTestClass.staticImportsByUtilMethod(id)) { collectedImports += StaticImport(methodId.classId.canonicalName, methodId.name) } } diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/UtValueTestCaseChecker.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/UtValueTestCaseChecker.kt index 9dbd65868a..00e370d25f 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/UtValueTestCaseChecker.kt +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/UtValueTestCaseChecker.kt @@ -9,6 +9,7 @@ import org.utbot.common.FileUtil.clearTempDirectory import org.utbot.common.FileUtil.findPathToClassFiles import org.utbot.common.FileUtil.locateClass import org.utbot.engine.prettify +import org.utbot.examples.codegen.ClassWithStaticAndInnerClassesTest import org.utbot.framework.PathSelectorType import org.utbot.framework.TestSelectionStrategyType import org.utbot.framework.UtSettings @@ -52,7 +53,10 @@ import org.utbot.framework.plugin.api.UtPrimitiveModel import org.utbot.framework.plugin.api.UtMethodTestSet import org.utbot.framework.plugin.api.UtValueExecution import org.utbot.framework.plugin.api.util.UtContext +import org.utbot.framework.plugin.api.util.enclosingClass import org.utbot.framework.plugin.api.util.executableId +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.kClass import org.utbot.framework.plugin.api.util.withUtContext import org.utbot.framework.util.Conflict import org.utbot.framework.util.toValueTestCase @@ -2247,7 +2251,13 @@ abstract class UtValueTestCaseChecker( //endregion - fun checkAllCombinations(method: KFunction<*>) { + /** + * @param method method under test + * @param generateWithNested a flag indicating if we need to generate nested test classes + * or just generate one top-level test class + * @see [ClassWithStaticAndInnerClassesTest] + */ + fun checkAllCombinations(method: KFunction<*>, generateWithNested: Boolean = false) { val failed = mutableListOf() val succeeded = mutableListOf() @@ -2255,7 +2265,7 @@ abstract class UtValueTestCaseChecker( .filterNot { it.isDisabled } .forEach { config -> runCatching { - internalCheckForCodeGeneration(method, config) + internalCheckForCodeGeneration(method, config, generateWithNested) }.onFailure { failed += config }.onSuccess { @@ -2278,7 +2288,8 @@ abstract class UtValueTestCaseChecker( @Suppress("ControlFlowWithEmptyBody", "UNUSED_VARIABLE") private fun internalCheckForCodeGeneration( method: KFunction<*>, - testFrameworkConfiguration: TestFrameworkConfiguration + testFrameworkConfiguration: TestFrameworkConfiguration, + generateWithNested: Boolean ) { withSettingsFromTestFrameworkConfiguration(testFrameworkConfiguration) { with(testFrameworkConfiguration) { @@ -2308,13 +2319,19 @@ abstract class UtValueTestCaseChecker( // TODO JIRA:1407 } - val testClass = testSet.method.clazz + val methodUnderTestOwner = testSet.method.clazz + val classUnderTest = if (generateWithNested) { + generateSequence(methodUnderTestOwner.id) { clazz -> clazz.enclosingClass }.last().kClass + } else { + methodUnderTestOwner + } + val stageStatusCheck = StageStatusCheck( firstStage = CodeGeneration, lastStage = TestExecution, status = ExecutionStatus.SUCCESS ) - val classStages = listOf(ClassStages(testClass, stageStatusCheck, listOf(testSet))) + val classStages = listOf(ClassStages(classUnderTest, stageStatusCheck, listOf(testSet))) TestCodeGeneratorPipeline(testFrameworkConfiguration).runClassesCodeGenerationTests(classStages) } diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/annotations/LombokAnnotationTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/annotations/LombokAnnotationTest.kt deleted file mode 100644 index 36a40d4c7c..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/annotations/LombokAnnotationTest.kt +++ /dev/null @@ -1,47 +0,0 @@ -package org.utbot.examples.annotations - -import org.utbot.examples.UtValueTestCaseChecker -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.annotations.lombok.EnumWithAnnotations -import org.utbot.examples.annotations.lombok.EnumWithoutAnnotations -import org.utbot.examples.annotations.lombok.NotNullAnnotations -import org.utbot.examples.eq -import org.junit.jupiter.api.Test - -/** - * Tests for code with Lombok annotations - * - * We do not calculate coverage here as Lombok always make it pure - * (see, i.e. https://stackoverflow.com/questions/44584487/improve-lombok-data-code-coverage) - * and Lombok code is considered to be already tested itself. - */ -class LombokAnnotationTest : UtValueTestCaseChecker(testClass = EnumWithAnnotations::class) { - - @Test - fun testGetterWithAnnotations() { - check( - EnumWithAnnotations::getConstant, - eq(1), - coverage = DoNotCalculate, - ) - } - - @Test - fun testGetterWithoutAnnotations() { - check( - EnumWithoutAnnotations::getConstant, - eq(1), - coverage = DoNotCalculate, - ) - } - - @Test - fun testNonNullAnnotations() { - check( - NotNullAnnotations::lombokNonNull, - eq(1), - { value, r -> value == r }, - coverage = DoNotCalculate, - ) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/annotations/lombok/EnumWithAnnotationsTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/annotations/lombok/EnumWithAnnotationsTest.kt new file mode 100644 index 0000000000..7f5494a31b --- /dev/null +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/annotations/lombok/EnumWithAnnotationsTest.kt @@ -0,0 +1,25 @@ +package org.utbot.examples.annotations.lombok + +import org.junit.jupiter.api.Test +import org.utbot.examples.DoNotCalculate +import org.utbot.examples.UtValueTestCaseChecker +import org.utbot.examples.eq + +/** + * Tests for Lombok annotations + * + * We do not calculate coverage here as Lombok always make it pure + * (see, i.e. https://stackoverflow.com/questions/44584487/improve-lombok-data-code-coverage) + * and Lombok code is considered to be already tested itself. + */ +internal class EnumWithAnnotationsTest : UtValueTestCaseChecker(testClass = EnumWithAnnotations::class) { + @Test + fun testGetterWithAnnotations() { + check( + EnumWithAnnotations::getConstant, + eq(1), + { r -> r == "Constant_1" }, + coverage = DoNotCalculate, + ) + } +} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/annotations/lombok/EnumWithoutAnnotationsTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/annotations/lombok/EnumWithoutAnnotationsTest.kt new file mode 100644 index 0000000000..37dc03c27c --- /dev/null +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/annotations/lombok/EnumWithoutAnnotationsTest.kt @@ -0,0 +1,16 @@ +package org.utbot.examples.annotations.lombok + +import org.junit.jupiter.api.Test +import org.utbot.examples.UtValueTestCaseChecker +import org.utbot.examples.eq + +internal class EnumWithoutAnnotationsTest : UtValueTestCaseChecker(testClass = EnumWithoutAnnotations::class) { + @Test + fun testGetterWithoutAnnotations() { + check( + EnumWithoutAnnotations::getConstant, + eq(1), + { r -> r == "Constant_1" }, + ) + } +} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/annotations/lombok/NotNullAnnotationsTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/annotations/lombok/NotNullAnnotationsTest.kt new file mode 100644 index 0000000000..31334c78ea --- /dev/null +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/annotations/lombok/NotNullAnnotationsTest.kt @@ -0,0 +1,25 @@ +package org.utbot.examples.annotations.lombok + +import org.junit.jupiter.api.Test +import org.utbot.examples.DoNotCalculate +import org.utbot.examples.UtValueTestCaseChecker +import org.utbot.examples.eq + +/** + * Tests for Lombok NonNull annotation + * + * We do not calculate coverage here as Lombok always make it pure + * (see, i.e. https://stackoverflow.com/questions/44584487/improve-lombok-data-code-coverage) + * and Lombok code is considered to be already tested itself. + */ +internal class NotNullAnnotationsTest : UtValueTestCaseChecker(testClass = NotNullAnnotations::class) { + @Test + fun testNonNullAnnotations() { + check( + NotNullAnnotations::lombokNonNull, + eq(1), + { value, r -> value == r }, + coverage = DoNotCalculate, + ) + } +} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/casts/CastClassTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/casts/CastClassTest.kt new file mode 100644 index 0000000000..9fc9be6ef9 --- /dev/null +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/casts/CastClassTest.kt @@ -0,0 +1,26 @@ +package org.utbot.examples.casts + +import org.junit.jupiter.api.Test +import org.utbot.examples.DoNotCalculate +import org.utbot.examples.UtValueTestCaseChecker +import org.utbot.examples.eq +import org.utbot.framework.codegen.CodeGeneration +import org.utbot.framework.plugin.api.CodegenLanguage + +internal class CastClassTest : UtValueTestCaseChecker( + testClass = CastClass::class, + testCodeGeneration = true, + languagePipelines = listOf( + CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), + CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) + ) +) { + @Test + fun testThisTypeChoice() { + check( + CastClass::castToInheritor, + eq(0), + coverage = DoNotCalculate + ) + } +} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/casts/CastExampleTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/casts/CastExampleTest.kt index 758c8a0cd5..20060b5c4d 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/casts/CastExampleTest.kt +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/casts/CastExampleTest.kt @@ -88,13 +88,4 @@ internal class CastExampleTest : UtValueTestCaseChecker( coverage = DoNotCalculate ) } - - @Test - fun testThisTypeChoice() { - check( - CastClass::castToInheritor, - eq(0), - coverage = DoNotCalculate - ) - } } \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/codegen/ClassWithStaticAndInnerClassesTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/codegen/ClassWithStaticAndInnerClassesTest.kt index f3d6f1b187..52810696eb 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/codegen/ClassWithStaticAndInnerClassesTest.kt +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/codegen/ClassWithStaticAndInnerClassesTest.kt @@ -96,4 +96,20 @@ internal class ClassWithStaticAndInnerClassesTest : UtValueTestCaseChecker(testC coverage = DoNotCalculate ) } + + @Test + fun testPublicStaticClassWithPrivateField_DeepNestedStatic_g() { + checkAllCombinations( + ClassWithStaticAndInnerClasses.PublicStaticClassWithPrivateField.DeepNestedStatic::g, + generateWithNested = true + ) + } + + @Test + fun testPublicStaticClassWithPrivateField_DeepNested_h() { + checkAllCombinations( + ClassWithStaticAndInnerClasses.PublicStaticClassWithPrivateField.DeepNested::h, + generateWithNested = true + ) + } } \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/codegen/deepequals/ClassWithNullableFieldTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/codegen/deepequals/ClassWithNullableFieldTest.kt new file mode 100644 index 0000000000..9882b16bc7 --- /dev/null +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/codegen/deepequals/ClassWithNullableFieldTest.kt @@ -0,0 +1,26 @@ +package org.utbot.examples.codegen.deepequals + +import org.junit.jupiter.api.Test +import org.utbot.examples.DoNotCalculate +import org.utbot.examples.UtValueTestCaseChecker +import org.utbot.examples.eq +import org.utbot.framework.codegen.CodeGeneration +import org.utbot.framework.plugin.api.CodegenLanguage + +class ClassWithNullableFieldTest : UtValueTestCaseChecker( + testClass = ClassWithNullableField::class, + testCodeGeneration = true, + languagePipelines = listOf( + CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), + CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) + ) +) { + @Test + fun testClassWithNullableField() { + check( + ClassWithNullableField::returnCompoundWithNullableField, + eq(2), + coverage = DoNotCalculate + ) + } +} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/codegen/deepequals/DeepEqualsTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/codegen/deepequals/DeepEqualsTest.kt index d70e001b70..a2e2f85e4a 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/codegen/deepequals/DeepEqualsTest.kt +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/codegen/deepequals/DeepEqualsTest.kt @@ -164,13 +164,4 @@ class DeepEqualsTest : UtValueTestCaseChecker( coverage = DoNotCalculate ) } - - @Test - fun testClassWithNullableField() { - check( - ClassWithNullableField::returnCompoundWithNullableField, - eq(2), - coverage = DoNotCalculate - ) - } } \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/mock/MockFinalClassTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/mock/MockFinalClassTest.kt index 6a82bfd39f..6e5005e22f 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/mock/MockFinalClassTest.kt +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/mock/MockFinalClassTest.kt @@ -9,7 +9,7 @@ import org.utbot.examples.value import org.utbot.framework.plugin.api.MockStrategyApi.OTHER_CLASSES import org.junit.jupiter.api.Test -internal class MockFinalClassTest : UtValueTestCaseChecker(testClass = MockReturnObjectExample::class) { +internal class MockFinalClassTest : UtValueTestCaseChecker(testClass = MockFinalClassExample::class) { @Test fun testFinalClass() { checkMocks( diff --git a/utbot-sample/src/main/java/org/utbot/examples/codegen/ClassWithStaticAndInnerClasses.java b/utbot-sample/src/main/java/org/utbot/examples/codegen/ClassWithStaticAndInnerClasses.java index 0d40009771..5e3761d2b1 100644 --- a/utbot-sample/src/main/java/org/utbot/examples/codegen/ClassWithStaticAndInnerClasses.java +++ b/utbot-sample/src/main/java/org/utbot/examples/codegen/ClassWithStaticAndInnerClasses.java @@ -52,6 +52,18 @@ public PublicStaticClassWithPublicField createFromConst(int x) { } public static class PublicStaticClassWithPrivateField { + + public static class DeepNestedStatic { + public int g(int x) { + return x + 1; + } + } + + public class DeepNested { + public int h(int x) { + return x + 2; + } + } private int x; public PublicStaticClassWithPrivateField(int x) {