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 45a7e53068..07f0be271b 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 @@ -753,6 +753,7 @@ open class ClassId @JvmOverloads constructor( */ class BuiltinClassId( name: String, + elementClassId: ClassId? = null, override val canonicalName: String, override val simpleName: String, // by default we assume that the class is not a member class @@ -769,6 +770,7 @@ class BuiltinClassId( override val isInner: Boolean = false, override val isNested: Boolean = false, override val isSynthetic: Boolean = false, + override val typeParameters: TypeParameters = TypeParameters(), override val allMethods: Sequence = emptySequence(), override val allConstructors: Sequence = emptySequence(), override val outerClass: Class<*>? = null, @@ -777,7 +779,7 @@ class BuiltinClassId( -1, 0 -> "" else -> canonicalName.substring(0, index) }, -) : ClassId(name = name, isNullable = isNullable) { +) : ClassId(name = name, isNullable = isNullable, elementClassId = elementClassId) { init { BUILTIN_CLASSES_BY_NAMES[name] = this } diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/SignatureUtil.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/SignatureUtil.kt index 6f0649a41c..ddba01d312 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/SignatureUtil.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/SignatureUtil.kt @@ -1,5 +1,6 @@ package org.utbot.framework.plugin.api.util +import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.ExecutableId import java.lang.reflect.Constructor import java.lang.reflect.Executable @@ -39,6 +40,23 @@ fun Constructor<*>.bytecodeSignature() = buildString { fun Class<*>.bytecodeSignature(): String = id.jvmName +/** + * Method [Class.getName] works differently for arrays than for other types. + * - When an element of an array is a reference type (e.g. `java.lang.Object`), + * the array of `java.lang.Object` will have name `[Ljava.lang.Object;`. + * - When an element of an array is a primitive type (e.g. `int`), + * the array of `int` will have name `[I`. + * + * So, this property returns the name of the given class in the format of an array element type name. + * Basically, it performs conversions for primitives and reference types (e.g. `int` -> `I`, `java.lang.Object` -> `Ljava.lang.Object;`. + */ +val ClassId.arrayLikeName: String + get() = when { + isPrimitive -> primitiveTypeJvmNameOrNull()!! + isRefType -> "L$name;" + else -> name + } + fun String.toReferenceTypeBytecodeSignature(): String { val packageName = this .takeIf { "." in this } 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 bd72b6bf77..c271cce902 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 @@ -99,7 +99,7 @@ internal interface CgContextOwner { val collectedExceptions: MutableSet // annotations required by the current method being built - val collectedTestMethodAnnotations: MutableSet + val collectedMethodAnnotations: MutableSet // imports required by the test class being built val collectedImports: MutableSet @@ -259,7 +259,7 @@ internal interface CgContextOwner { } fun addAnnotation(annotation: CgAnnotation) { - if (collectedTestMethodAnnotations.add(annotation)) { + if (collectedMethodAnnotations.add(annotation)) { importIfNeeded(annotation.classId) // TODO: check how JUnit annotations are loaded } } @@ -391,7 +391,7 @@ internal data class CgContext( override val collectedTestClassInterfaces: MutableSet = mutableSetOf(), override val collectedTestClassAnnotations: MutableSet = mutableSetOf(), override val collectedExceptions: MutableSet = mutableSetOf(), - override val collectedTestMethodAnnotations: MutableSet = mutableSetOf(), + override val collectedMethodAnnotations: MutableSet = mutableSetOf(), override val collectedImports: MutableSet = mutableSetOf(), override val importedStaticMethods: MutableSet = mutableSetOf(), override val importedClasses: MutableSet = mutableSetOf(), 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 5d02dc403a..165134a0c5 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 @@ -5,7 +5,6 @@ import org.utbot.common.packageName import org.utbot.engine.isStatic import org.utbot.framework.assemble.assemble import org.utbot.framework.codegen.ForceStaticMocking -import org.utbot.framework.codegen.JUNIT5_PARAMETERIZED_PACKAGE import org.utbot.framework.codegen.Junit4 import org.utbot.framework.codegen.Junit5 import org.utbot.framework.codegen.ParametrizedTestSource @@ -27,25 +26,22 @@ import org.utbot.framework.codegen.model.constructor.util.FieldStateCache 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.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.CgAssignment import org.utbot.framework.codegen.model.tree.CgClassId -import org.utbot.framework.codegen.model.tree.CgConstructorCall import org.utbot.framework.codegen.model.tree.CgDeclaration import org.utbot.framework.codegen.model.tree.CgDocPreTagStatement import org.utbot.framework.codegen.model.tree.CgDocRegularStmt import org.utbot.framework.codegen.model.tree.CgDocumentationComment -import org.utbot.framework.codegen.model.tree.CgEmptyLine import org.utbot.framework.codegen.model.tree.CgEqualTo import org.utbot.framework.codegen.model.tree.CgErrorTestMethod import org.utbot.framework.codegen.model.tree.CgExecutableCall import org.utbot.framework.codegen.model.tree.CgExpression import org.utbot.framework.codegen.model.tree.CgFieldAccess import org.utbot.framework.codegen.model.tree.CgGetJavaClass -import org.utbot.framework.codegen.model.tree.CgIfStatement import org.utbot.framework.codegen.model.tree.CgLiteral import org.utbot.framework.codegen.model.tree.CgMethod import org.utbot.framework.codegen.model.tree.CgMethodCall @@ -55,11 +51,9 @@ import org.utbot.framework.codegen.model.tree.CgParameterDeclaration import org.utbot.framework.codegen.model.tree.CgParameterKind import org.utbot.framework.codegen.model.tree.CgParameterizedTestDataProviderMethod import org.utbot.framework.codegen.model.tree.CgRegion -import org.utbot.framework.codegen.model.tree.CgReturnStatement import org.utbot.framework.codegen.model.tree.CgSimpleRegion import org.utbot.framework.codegen.model.tree.CgSingleLineComment import org.utbot.framework.codegen.model.tree.CgStatement -import org.utbot.framework.codegen.model.tree.CgStatementExecutableCall import org.utbot.framework.codegen.model.tree.CgStaticFieldAccess import org.utbot.framework.codegen.model.tree.CgTestMethod import org.utbot.framework.codegen.model.tree.CgTestMethodType @@ -72,7 +66,6 @@ import org.utbot.framework.codegen.model.tree.CgTryCatch import org.utbot.framework.codegen.model.tree.CgTypeCast import org.utbot.framework.codegen.model.tree.CgValue import org.utbot.framework.codegen.model.tree.CgVariable -import org.utbot.framework.codegen.model.tree.buildForLoop import org.utbot.framework.codegen.model.tree.buildParameterizedTestDataProviderMethod import org.utbot.framework.codegen.model.tree.buildTestMethod import org.utbot.framework.codegen.model.tree.convertDocToCg @@ -126,6 +119,7 @@ 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 @@ -505,7 +499,6 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c expectedModel: UtModel, expected: CgVariable?, actual: CgVariable, - statements: MutableList, depth: Int, visitedModels: MutableSet, ) { @@ -521,14 +514,14 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c with(testFrameworkManager) { if (depth >= DEEP_EQUALS_MAX_DEPTH) { - statements += CgSingleLineComment("Current deep equals depth exceeds max depth $DEEP_EQUALS_MAX_DEPTH") - statements += getDeepEqualsAssertion(expected, actual).toStatement() + currentBlock += CgSingleLineComment("Current deep equals depth exceeds max depth $DEEP_EQUALS_MAX_DEPTH") + currentBlock += getDeepEqualsAssertion(expected, actual).toStatement() return } when (expectedModel) { is UtPrimitiveModel -> { - statements += when { + currentBlock += when { (expected.type == floatClassId || expected.type == floatWrapperClassId) -> assertions[assertFloatEquals]( // cast have to be not safe here because of signature typeCast(floatClassId, expected, isSafetyCast = false), @@ -563,7 +556,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c }.toStatement() } is UtEnumConstantModel -> { - statements += assertions[assertEquals]( + currentBlock += assertions[assertEquals]( expected, actual ).toStatement() @@ -571,26 +564,22 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c is UtClassRefModel -> { // TODO this stuff is needed because Kotlin has javaclass property instead of Java getClass method // probably it is better to change getClass method behaviour in the future - val actualObject: CgVariable - if (codegenLanguage == CodegenLanguage.KOTLIN) { - val actualCastedToObject = CgDeclaration( - objectClassId, - variableConstructor.constructVarName("actualObject"), - CgTypeCast(objectClassId, actual) + val actualObject: CgVariable = when (codegenLanguage) { + CodegenLanguage.KOTLIN -> newVar( + baseType = objectClassId, + baseName = variableConstructor.constructVarName("actualObject"), + init = { CgTypeCast(objectClassId, actual) } ) - statements += actualCastedToObject - actualObject = actualCastedToObject.variable - } else { - actualObject = actual + else -> actual } - statements += assertions[assertEquals]( + currentBlock += assertions[assertEquals]( CgGetJavaClass(expected.type), actualObject[getClass]() ).toStatement() } is UtNullModel -> { - statements += assertions[assertNull](actual).toStatement() + currentBlock += assertions[assertNull](actual).toStatement() } is UtArrayModel -> { val arrayInfo = expectedModel.collectArrayInfo() @@ -607,20 +596,20 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c // actual[1] instance of int[][][] // actual[2] instance of Object[] - addArraysLengthAssertion(expected, actual, statements) - statements += getDeepEqualsAssertion(expected, actual).toStatement() + addArraysLengthAssertion(expected, actual) + currentBlock += getDeepEqualsAssertion(expected, actual).toStatement() return } // It does not work for Double and Float because JUnit does not have equals overloading with wrappers if (nestedElementClassId == floatClassId || nestedElementClassId == doubleClassId) { - floatingPointArraysDeepEquals(arrayInfo, expected, actual, statements) + floatingPointArraysDeepEquals(arrayInfo, expected, actual) return } // common primitive array, can use default array equals - addArraysLengthAssertion(expected, actual, statements) - statements += getArrayEqualsAssertion( + addArraysLengthAssertion(expected, actual) + currentBlock += getArrayEqualsAssertion( expectedModel.classId, typeCast(expectedModel.classId, expected, isSafetyCast = true), typeCast(expectedModel.classId, actual, isSafetyCast = true) @@ -628,19 +617,19 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c } is UtAssembleModel -> { if (expectedModel.classId.isPrimitiveWrapper) { - statements += assertions[assertEquals](expected, actual).toStatement() + currentBlock += assertions[assertEquals](expected, actual).toStatement() return } // UtCompositeModel deep equals is much more easier and human friendly expectedModel.origin?.let { - assertDeepEquals(it, expected, actual, statements, depth, visitedModels) + assertDeepEquals(it, expected, actual, depth, visitedModels) return } // special case for strings as they are constructed from UtAssembleModel but can be compared with equals if (expectedModel.classId == stringClassId) { - statements += assertions[assertEquals]( + currentBlock += assertions[assertEquals]( expected, actual ).toStatement() @@ -656,7 +645,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c // We can add some heuristics to process standard assemble models like List, Set and Map. // So, there is a space for improvements if (expectedModel.modificationsChain.isEmpty() || expectedModel.modificationsChain.any { it !is UtDirectSetFieldModel }) { - statements += getDeepEqualsAssertion(expected, actual).toStatement() + currentBlock += getDeepEqualsAssertion(expected, actual).toStatement() return } @@ -674,7 +663,6 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c fieldModel, expected, actual, - statements, depth, visitedModels ) @@ -685,18 +673,18 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c // But it leads to a lot of trash code in each test method, and it is more clear to use // outer deep equals here if (expected.isIterableOrMap()) { - statements += CgSingleLineComment( + currentBlock += CgSingleLineComment( "${expected.type.canonicalName} is iterable or Map, use outer deep equals to iterate over" ) - statements += getDeepEqualsAssertion(expected, actual).toStatement() + currentBlock += getDeepEqualsAssertion(expected, actual).toStatement() return } if (expected.hasNotParametrizedCustomEquals()) { // We rely on already existing equals - statements += CgSingleLineComment("${expected.type.canonicalName} has overridden equals method") - statements += assertions[assertEquals](expected, actual).toStatement() + currentBlock += CgSingleLineComment("${expected.type.canonicalName} has overridden equals method") + currentBlock += assertions[assertEquals](expected, actual).toStatement() return } @@ -711,7 +699,6 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c fieldModel, expected, actual, - statements, depth, visitedModels ) @@ -728,15 +715,14 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c private fun TestFrameworkManager.addArraysLengthAssertion( expected: CgVariable, actual: CgVariable, - statements: MutableList ): CgDeclaration { val cgGetLengthDeclaration = CgDeclaration( intClassId, variableConstructor.constructVarName("${expected.name}Size"), expected.length(this, testClassThisInstance, getArrayLength) ) - statements += cgGetLengthDeclaration - statements += assertions[assertEquals]( + currentBlock += cgGetLengthDeclaration + currentBlock += assertions[assertEquals]( cgGetLengthDeclaration.variable, actual.length(this, testClassThisInstance, getArrayLength) ).toStatement() @@ -751,9 +737,8 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c expectedArrayInfo: ClassIdArrayInfo, expected: CgVariable, actual: CgVariable, - statements: MutableList, ) { - val cgGetLengthDeclaration = addArraysLengthAssertion(expected, actual, statements) + val cgGetLengthDeclaration = addArraysLengthAssertion(expected, actual) val nestedElementClassId = expectedArrayInfo.nestedElementClassId ?: error("Expected from floating point array ${expectedArrayInfo.classId} to contain elements but null found") @@ -763,7 +748,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c if (expectedArrayInfo.isSingleDimensionalArray) { // we can use array equals for all single dimensional arrays - statements += when (nestedElementClassId) { + currentBlock += when (nestedElementClassId) { floatClassId -> getFloatArrayEqualsAssertion( typeCast(floatArrayClassId, expected, isSafetyCast = true), typeCast(floatArrayClassId, actual, isSafetyCast = true), @@ -778,51 +763,40 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c } else { // we can't use array equals for multidimensional double and float arrays // so we need to go deeper to single-dimensional array - val loop = buildForLoop { + forLoop { val (i, init) = variableConstructor.loopInitialization(intClassId, "i", initializer = 0) initialization = init condition = i lessThan cgGetLengthDeclaration.variable.resolve() update = i.inc() - val loopStatements = mutableListOf() - val expectedNestedElement = CgDeclaration( - expected.type.elementClassId!!, - variableConstructor.constructVarName("${expected.name}NestedElement"), - CgArrayElementAccess(expected, i) - ) - val actualNestedElement = CgDeclaration( - actual.type.elementClassId!!, - variableConstructor.constructVarName("${actual.name}NestedElement"), - CgArrayElementAccess(actual, i) - ) - - loopStatements += expectedNestedElement - loopStatements += actualNestedElement - loopStatements += CgEmptyLine() - - val nullBranchStatements = listOf( - assertions[assertNull](actualNestedElement.variable).toStatement() - ) - - val notNullBranchStatements = mutableListOf() - floatingPointArraysDeepEquals( - expectedArrayInfo.getNested(), - expectedNestedElement.variable, - actualNestedElement.variable, - notNullBranchStatements - ) + statements = block { + val expectedNestedElement = newVar( + baseType = expected.type.elementClassId!!, + baseName = variableConstructor.constructVarName("${expected.name}NestedElement"), + init = { CgArrayElementAccess(expected, i) } + ) - loopStatements += CgIfStatement( - CgEqualTo(expectedNestedElement.variable, nullLiteral()), - nullBranchStatements, - notNullBranchStatements - ) + val actualNestedElement = newVar( + baseType = actual.type.elementClassId!!, + baseName = variableConstructor.constructVarName("${actual.name}NestedElement"), + init = { CgArrayElementAccess(actual, i) } + ) + emptyLine() - this@buildForLoop.statements = loopStatements + ifStatement( + CgEqualTo(expectedNestedElement, nullLiteral()), + trueBranch = { assertions[assertNull](actualNestedElement).toStatement() }, + falseBranch = { + floatingPointArraysDeepEquals( + expectedArrayInfo.getNested(), + expectedNestedElement, + actualNestedElement, + ) + } + ) + } } - - statements += loop } } @@ -847,23 +821,11 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c return false } - /** - * We can't use [emptyLineIfNeeded] from here because it changes field [currentBlock]. - * Also, we can't extract generic part because [currentBlock] is PersistentList (not mutable). - */ - private fun MutableList.addEmptyLineIfNeeded() { - val lastStatement = lastOrNull() ?: return - if (lastStatement is CgEmptyLine) return - - this += CgEmptyLine() - } - private fun traverseFieldRecursively( fieldId: FieldId, fieldModel: UtModel, expected: CgVariable, actual: CgVariable, - statements: MutableList, depth: Int, visitedModels: MutableSet ) { @@ -885,22 +847,21 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c if (needExpectedDeclaration(fieldModel)) { val expectedFieldDeclaration = createDeclarationForFieldFromVariable(fieldId, expected, fieldName) - statements += expectedFieldDeclaration + currentBlock += expectedFieldDeclaration expectedVariable = expectedFieldDeclaration.variable } val actualFieldDeclaration = createDeclarationForFieldFromVariable(fieldId, actual, fieldName) - statements += actualFieldDeclaration + currentBlock += actualFieldDeclaration assertDeepEquals( fieldModel, expectedVariable, actualFieldDeclaration.variable, - statements, depth + 1, visitedModels, ) - statements.addEmptyLineIfNeeded() + emptyLineIfNeeded() } @Suppress("UNUSED_ANONYMOUS_PARAMETER") @@ -1071,21 +1032,14 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c actual: CgVariable, ) { when (parameterizedTestSource) { - ParametrizedTestSource.DO_NOT_PARAMETRIZE -> - currentBlock = currentBlock.addAll(generateDeepEqualsAssertion(expected, actual)) - ParametrizedTestSource.PARAMETRIZE -> { - currentBlock = if (actual.type.isPrimitive) { - currentBlock.addAll(generateDeepEqualsAssertion(expected, actual)) - } else { - val assertNullStmt = listOf(testFrameworkManager.assertions[testFramework.assertNull](actual).toStatement()) - currentBlock.add( - CgIfStatement( - CgEqualTo(expected, nullLiteral()), - assertNullStmt, - generateDeepEqualsAssertion(expected, actual) - ) - ) - } + ParametrizedTestSource.DO_NOT_PARAMETRIZE -> generateDeepEqualsAssertion(expected, actual) + ParametrizedTestSource.PARAMETRIZE -> when { + actual.type.isPrimitive -> generateDeepEqualsAssertion(expected, actual) + else -> ifStatement( + CgEqualTo(expected, nullLiteral()), + trueBranch = { testFrameworkManager.assertions[testFramework.assertNull](actual).toStatement() }, + falseBranch = { generateDeepEqualsAssertion(expected, actual) } + ) } } } @@ -1093,22 +1047,18 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c private fun generateDeepEqualsAssertion( expected: CgValue, actual: CgVariable, - ): List { + ) { require(expected is CgVariable) { "Expected value have to be Literal or Variable but `${expected::class}` found" } - val statements = mutableListOf(CgEmptyLine()) assertDeepEquals( resultModel, expected, actual, - statements, depth = 0, visitedModels = hashSetOf() ) - - return statements.dropLastWhile { it is CgEmptyLine } } private fun recordActualResult() { @@ -1332,9 +1282,16 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c } if (containsFailureExecution) { + val classClassId = Class::class.id val expectedException = CgParameterDeclaration( parameter = declareParameter( - type = throwableClassId(), + type = BuiltinClassId( + name = classClassId.name, + simpleName = classClassId.simpleName, + canonicalName = classClassId.canonicalName, + packageName = classClassId.packageName, + typeParameters = TypeParameters(listOf(Throwable::class.java.id)) + ), name = nameGenerator.variableName(expectedErrorVarName) ), // exceptions are always reference type @@ -1356,80 +1313,78 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c testSet: UtMethodTestSet, dataProviderMethodName: String ): CgParameterizedTestDataProviderMethod { - val dataProviderStatements = mutableListOf() - val dataProviderExceptions = mutableSetOf() - - val argListLength = testSet.executions.size - val argListDeclaration = createArgList(argListLength) - val argListVariable = argListDeclaration.variable - - dataProviderStatements += argListDeclaration - dataProviderStatements += CgEmptyLine() - - for ((execIndex, execution) in testSet.executions.withIndex()) { - withTestMethodScope(execution) { - //collect arguments - val arguments = mutableListOf() - val executionArgumentsBody = { - execution.stateBefore.thisInstance?.let { - arguments += variableConstructor.getOrCreateVariable(it) - } + return withDataProviderScope { + dataProviderMethod(dataProviderMethodName) { + val argListLength = testSet.executions.size + val argListVariable = createArgList(argListLength) - for ((paramIndex, paramModel) in execution.stateBefore.parameters.withIndex()) { - val argumentName = paramNames[testSet.method]?.get(paramIndex) - arguments += variableConstructor.getOrCreateVariable(paramModel, argumentName) - } + emptyLine() - val method = currentExecutable as MethodId - val needsReturnValue = method.returnType != voidClassId - val containsFailureExecution = containsFailureExecution(testSet) - execution.result - .onSuccess { - if (needsReturnValue) { - arguments += variableConstructor.getOrCreateVariable(it) - } - if (containsFailureExecution) { - arguments += nullLiteral() - } - } - .onFailure { - if (needsReturnValue) { - arguments += nullLiteral() - } - if (containsFailureExecution) { - arguments += CgGetJavaClass(it::class.id) - } - } - emptyLineIfNeeded() + for ((execIndex, execution) in testSet.executions.withIndex()) { + // create a block for current test case + innerBlock { + val arguments = createExecutionArguments(testSet, execution) + createArgumentsCallRepresentation(execIndex, argListVariable, arguments) + } } - //create a block for current test case - dataProviderStatements += innerBlock( - {}, - block(executionArgumentsBody) - + createArgumentsCallRepresentation(execIndex, argListVariable, arguments) - ) + emptyLineIfNeeded() - dataProviderExceptions += collectedExceptions + returnStatement { argListVariable } } } + } - dataProviderStatements.addEmptyLineIfNeeded() - dataProviderStatements += CgReturnStatement(argListVariable) + private fun createExecutionArguments(testSet: UtMethodTestSet, execution: UtExecution): List { + val arguments = mutableListOf() + execution.stateBefore.thisInstance?.let { + arguments += variableConstructor.getOrCreateVariable(it) + } - return buildParameterizedTestDataProviderMethod { - name = dataProviderMethodName - returnType = argListClassId() - statements = dataProviderStatements - annotations = createDataProviderAnnotations(dataProviderMethodName) - exceptions = dataProviderExceptions + for ((paramIndex, paramModel) in execution.stateBefore.parameters.withIndex()) { + val argumentName = paramNames[testSet.method]?.get(paramIndex) + arguments += variableConstructor.getOrCreateVariable(paramModel, argumentName) } + + val method = currentExecutable as MethodId + val needsReturnValue = method.returnType != voidClassId + val containsFailureExecution = containsFailureExecution(testSet) + execution.result + .onSuccess { + if (needsReturnValue) { + arguments += variableConstructor.getOrCreateVariable(it) + } + if (containsFailureExecution) { + arguments += nullLiteral() + } + } + .onFailure { + if (needsReturnValue) { + arguments += nullLiteral() + } + if (containsFailureExecution) { + arguments += CgGetJavaClass(it::class.id) + } + } + + emptyLineIfNeeded() + + return arguments } private fun withTestMethodScope(execution: UtExecution, block: () -> R): R { - clearMethodScope() + clearTestMethodScope() currentExecution = execution statesCache = EnvironmentFieldStateCache.emptyCacheFor(execution) + return try { + block() + } finally { + clearTestMethodScope() + } + } + + private fun withDataProviderScope(block: () -> R): R { + clearMethodScope() return try { block() } finally { @@ -1437,9 +1392,24 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c } } + /** + * This function makes sure some information about the method currently being generated is empty. + * It clears only the information that is relevant to all kinds of methods: + * - test methods + * - data provider methods + * - and any other kinds of methods that may be added in the future + */ private fun clearMethodScope() { collectedExceptions.clear() - collectedTestMethodAnnotations.clear() + collectedMethodAnnotations.clear() + } + + /** + * This function makes sure some information about the **test method** currently being generated is empty. + * It is used at the start of test method generation and right after it. + */ + private fun clearTestMethodScope() { + clearMethodScope() prevStaticFieldValues.clear() thisInstance = null methodArguments.clear() @@ -1456,38 +1426,38 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c executionIndex: Int, argsVariable: CgVariable, arguments: List, - ): List = when (testFramework) { - Junit5 -> { - val argumentsMethodCall = CgMethodCall(caller = null, argumentsMethodId(), arguments) - listOf( - CgStatementExecutableCall(CgMethodCall(argsVariable, addToListMethodId(), listOf(argumentsMethodCall))) - ) + ) { + val argsArray = newVar(objectArrayClassId, "testCaseObjects") { + CgAllocateArray(objectArrayClassId, objectClassId, arguments.size) + } + for ((i, argument) in arguments.withIndex()) { + setArgumentsArrayElement(argsArray, i, argument) } - TestNg -> { - val statements = mutableListOf() - val argsArrayAllocation = CgAllocateArray(Array::class.java.id, objectClassId, arguments.size) - val argsArrayDeclaration = CgDeclaration(objectArrayClassId, "testCaseObjects", argsArrayAllocation) - statements += argsArrayDeclaration - for ((i, argument) in arguments.withIndex()) { - statements += setArgumentsArrayElement(argsArrayDeclaration.variable, i, argument) + when (testFramework) { + Junit5 -> { + +argsVariable[addToListMethodId]( + argumentsClassId[argumentsMethodId](argsArray) + ) } - statements += setArgumentsArrayElement(argsVariable, executionIndex, argsArrayDeclaration.variable) - - statements + TestNg -> { + setArgumentsArrayElement(argsVariable, executionIndex, argsArray) + } + Junit4 -> error("Parameterized tests are not supported for JUnit4") } - 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): CgStatement = - if (array.type == objectClassId) { - java.lang.reflect.Array::class.id[setArrayElement](array, index, value) - } else { - CgAssignment(array.at(index), value) + 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 } + } /** * Creates annotations for data provider method in parameterized tests @@ -1508,83 +1478,98 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c /** * Creates declaration of argList collection in parameterized tests. */ - private fun createArgList(length: Int): CgDeclaration = when (testFramework) { - Junit5 -> { - val constructorCall = CgConstructorCall(ConstructorId(argListClassId(), emptyList()), emptyList()) - CgDeclaration(argListClassId(), "argList", constructorCall) - } - TestNg -> { - val allocateArrayCall = CgAllocateArray(argListClassId(), Array::class.java.id, length) - CgDeclaration(argListClassId(), "argList", allocateArrayCall) + 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") } - Junit4 -> error("Parameterized tests are not supported for JUnit4") } /** * Creates a [ClassId] for arguments collection. */ - private fun argListClassId(): ClassId = when (testFramework) { - Junit5 -> BuiltinClassId( - name = "java.util.ArrayList<${JUNIT5_PARAMETERIZED_PACKAGE}.provider.Arguments>", - simpleName = "ArrayList<${JUNIT5_PARAMETERIZED_PACKAGE}.provider.Arguments>", - canonicalName = "java.util.ArrayList<${JUNIT5_PARAMETERIZED_PACKAGE}.provider.Arguments>", - packageName = "java.util", - ) - TestNg -> BuiltinClassId( - name = Array?>::class.java.name, - simpleName = when (codegenLanguage) { - CodegenLanguage.JAVA -> "Object[][]" - CodegenLanguage.KOTLIN -> "Array?>" - }, - canonicalName = Array?>::class.java.canonicalName, - packageName = Array?>::class.java.packageName, - ) - Junit4 -> error("Parameterized tests are not supported for JUnit4") - } + 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 fun addToListMethodId(): MethodId = methodId( - classId = ArrayList::class.id, - name = "add", - returnType = booleanClassId, - arguments = arrayOf(Object::class.id), - ) + private val addToListMethodId: MethodId + get() = methodId( + classId = ArrayList::class.id, + name = "add", + returnType = booleanClassId, + arguments = arrayOf(Object::class.id), + ) /** - * A [MethodId] to call JUnit Arguments method. + * A [ClassId] of class `org.junit.jupiter.params.provider.Arguments` */ - private fun argumentsMethodId(): MethodId { - val argumentsClassId = BuiltinClassId( + 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", + packageName = "org.junit.jupiter.params.provider" ) - return methodId( + /** + * A [MethodId] to call JUnit Arguments method. + */ + private val argumentsMethodId: BuiltinMethodId + get() = builtinStaticMethodId( classId = argumentsClassId, name = "arguments", returnType = argumentsClassId, - arguments = arrayOf(Object::class.id), + // vararg of Objects + arguments = arrayOf(objectArrayClassId) ) - } private fun containsFailureExecution(testSet: UtMethodTestSet) = testSet.executions.any { it.result is UtExecutionFailure } - /** - * A [ClassId] for Class. - */ - private fun throwableClassId(): ClassId = BuiltinClassId( - name = "java.lang.Class", - simpleName = "Class", - canonicalName = "java.lang.Class", - packageName = "java.lang", - ) - private fun collectParameterizedTestAnnotations(dataProviderMethodName: String?): Set = when (testFramework) { @@ -1609,7 +1594,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c dataProviderMethodName: String? = null, body: () -> Unit, ): CgTestMethod { - collectedTestMethodAnnotations += if (parameterized) { + collectedMethodAnnotations += if (parameterized) { collectParameterizedTestAnnotations(dataProviderMethodName) } else { setOf(annotation(testFramework.testAnnotationId)) @@ -1637,9 +1622,10 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c name = methodName parameters = params statements = block(body) - // Exceptions and annotations assignment must run after everything else is set up + // 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 += collectedTestMethodAnnotations + annotations += collectedMethodAnnotations methodType = this@CgMethodConstructor.methodType val docComment = currentExecution?.summary?.map { convertDocToCg(it) }?.toMutableList() ?: mutableListOf() @@ -1670,6 +1656,18 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c return testMethod } + private fun dataProviderMethod(dataProviderMethodName: String, body: () -> Unit): CgParameterizedTestDataProviderMethod { + return buildParameterizedTestDataProviderMethod { + name = dataProviderMethodName + returnType = 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) + } + } + fun errorMethod(method: UtMethod<*>, errors: Map): CgRegion { val name = nameGenerator.errorMethodNameFor(method) val body = block { diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt index e66b988daa..880b199022 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt @@ -6,19 +6,19 @@ 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.CgStatementConstructor +import org.utbot.framework.codegen.model.constructor.util.MAX_ARRAY_INITIALIZER_SIZE +import org.utbot.framework.codegen.model.constructor.util.arrayInitializer import org.utbot.framework.codegen.model.constructor.util.get import org.utbot.framework.codegen.model.constructor.util.isDefaultValueOf import org.utbot.framework.codegen.model.constructor.util.isNotDefaultValueOf import org.utbot.framework.codegen.model.constructor.util.typeCast import org.utbot.framework.codegen.model.tree.CgAllocateArray -import org.utbot.framework.codegen.model.tree.CgAllocateInitializedArray import org.utbot.framework.codegen.model.tree.CgDeclaration import org.utbot.framework.codegen.model.tree.CgEnumConstantAccess import org.utbot.framework.codegen.model.tree.CgExpression import org.utbot.framework.codegen.model.tree.CgFieldAccess import org.utbot.framework.codegen.model.tree.CgGetJavaClass import org.utbot.framework.codegen.model.tree.CgLiteral -import org.utbot.framework.codegen.model.tree.CgNotNullAssertion import org.utbot.framework.codegen.model.tree.CgStaticFieldAccess import org.utbot.framework.codegen.model.tree.CgValue import org.utbot.framework.codegen.model.tree.CgVariable @@ -222,10 +222,21 @@ internal class CgVariableConstructor(val context: CgContext) : arrayModel.stores.getOrDefault(it, arrayModel.constModel) } - val canInitWithValues = elementModels.all { it is UtPrimitiveModel } || elementModels.all { it is UtNullModel } + val allPrimitives = elementModels.all { it is UtPrimitiveModel } + val allNulls = elementModels.all { it is UtNullModel } + // we can use array initializer if all elements are primitives or all of them are null, + // and the size of an array is not greater than the fixed maximum size + val canInitWithValues = (allPrimitives || allNulls) && elementModels.size <= MAX_ARRAY_INITIALIZER_SIZE val initializer = if (canInitWithValues) { - CgAllocateInitializedArray(arrayModel) + val elements = elementModels.map { model -> + when (model) { + is UtPrimitiveModel -> model.value.resolve() + is UtNullModel -> null.resolve() + else -> error("Non primitive or null model $model is unexpected in array initializer") + } + } + arrayInitializer(arrayModel.classId, elementType, elements) } else { CgAllocateArray(arrayModel.classId, elementType, arrayModel.length) } 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 6fde079657..df85554695 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 @@ -162,12 +162,12 @@ internal abstract class TestFrameworkManager(val context: CgContext) name = timeoutArgumentName, value = timeoutMs.resolve() ) - val testAnnotation = collectedTestMethodAnnotations.singleOrNull { it.classId == testFramework.testAnnotationId } + val testAnnotation = collectedMethodAnnotations.singleOrNull { it.classId == testFramework.testAnnotationId } if (testAnnotation is CgMultipleArgsAnnotation) { testAnnotation.arguments += timeout } else { - collectedTestMethodAnnotations += CgMultipleArgsAnnotation( + collectedMethodAnnotations += CgMultipleArgsAnnotation( testFramework.testAnnotationId, mutableListOf(timeout) ) @@ -180,7 +180,7 @@ internal abstract class TestFrameworkManager(val context: CgContext) // because other test frameworks do not support such feature. open fun addDisplayName(name: String) { val displayName = CgSingleArgAnnotation(Junit5.displayNameClassId, stringLiteral(name)) - collectedTestMethodAnnotations += CgCommentedAnnotation(displayName) + collectedMethodAnnotations += CgCommentedAnnotation(displayName) } protected fun ClassId.toExceptionClass(): CgExpression = @@ -241,7 +241,7 @@ internal class TestNgManager(context: CgContext) : TestFrameworkManager(context) value = reason.resolve() ) - val testAnnotation = collectedTestMethodAnnotations.singleOrNull { it.classId == testFramework.testAnnotationId } + val testAnnotation = collectedMethodAnnotations.singleOrNull { it.classId == testFramework.testAnnotationId } if (testAnnotation is CgMultipleArgsAnnotation) { testAnnotation.arguments += disabledAnnotationArgument @@ -271,7 +271,7 @@ internal class TestNgManager(context: CgContext) : TestFrameworkManager(context) testAnnotation.arguments += descriptionTestAnnotationArgument } } else { - collectedTestMethodAnnotations += CgMultipleArgsAnnotation( + collectedMethodAnnotations += CgMultipleArgsAnnotation( testFramework.testAnnotationId, mutableListOf(disabledAnnotationArgument, descriptionTestAnnotationArgument) ) @@ -291,11 +291,11 @@ internal class Junit4Manager(context: CgContext) : TestFrameworkManager(context) name = "expected", value = classLiteralAnnotationArgument(exception, codegenLanguage) ) - val testAnnotation = collectedTestMethodAnnotations.singleOrNull { it.classId == testFramework.testAnnotationId } + val testAnnotation = collectedMethodAnnotations.singleOrNull { it.classId == testFramework.testAnnotationId } if (testAnnotation is CgMultipleArgsAnnotation) { testAnnotation.arguments += expected } else { - collectedTestMethodAnnotations += CgMultipleArgsAnnotation(testFramework.testAnnotationId, mutableListOf(expected)) + collectedMethodAnnotations += CgMultipleArgsAnnotation(testFramework.testAnnotationId, mutableListOf(expected)) } block() } @@ -303,7 +303,7 @@ internal class Junit4Manager(context: CgContext) : TestFrameworkManager(context) override fun disableTestMethod(reason: String) { require(testFramework is Junit4) { "According to settings, JUnit4 was expected, but got: $testFramework" } - collectedTestMethodAnnotations += CgMultipleArgsAnnotation( + collectedMethodAnnotations += CgMultipleArgsAnnotation( testFramework.ignoreAnnotationClassId, mutableListOf( CgNamedAnnotationArgument( @@ -339,7 +339,7 @@ internal class Junit5Manager(context: CgContext) : TestFrameworkManager(context) override fun addDisplayName(name: String) { require(testFramework is Junit5) { "According to settings, JUnit5 was expected, but got: $testFramework" } - collectedTestMethodAnnotations += statementConstructor.annotation(testFramework.displayNameClassId, name) + collectedMethodAnnotations += statementConstructor.annotation(testFramework.displayNameClassId, name) } override fun setTestExecutionTimeout(timeoutMs: Long) { @@ -358,7 +358,7 @@ internal class Junit5Manager(context: CgContext) : TestFrameworkManager(context) ) importIfNeeded(testFramework.timeunitClassId) - collectedTestMethodAnnotations += CgMultipleArgsAnnotation( + collectedMethodAnnotations += CgMultipleArgsAnnotation( Junit5.timeoutClassId, timeoutAnnotationArguments ) @@ -367,7 +367,7 @@ internal class Junit5Manager(context: CgContext) : TestFrameworkManager(context) override fun disableTestMethod(reason: String) { require(testFramework is Junit5) { "According to settings, JUnit5 was expected, but got: $testFramework" } - collectedTestMethodAnnotations += CgMultipleArgsAnnotation( + collectedMethodAnnotations += CgMultipleArgsAnnotation( testFramework.disabledAnnotationClassId, mutableListOf( CgNamedAnnotationArgument( diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/CgStatementConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/CgStatementConstructor.kt index ba1f8e1faa..53c45ac091 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/CgStatementConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/CgStatementConstructor.kt @@ -31,7 +31,6 @@ import org.utbot.framework.codegen.model.tree.CgParameterDeclaration import org.utbot.framework.codegen.model.tree.CgReturnStatement import org.utbot.framework.codegen.model.tree.CgSingleArgAnnotation import org.utbot.framework.codegen.model.tree.CgSingleLineComment -import org.utbot.framework.codegen.model.tree.CgStatement import org.utbot.framework.codegen.model.tree.CgThrowStatement import org.utbot.framework.codegen.model.tree.CgTryCatch import org.utbot.framework.codegen.model.tree.CgVariable @@ -40,7 +39,6 @@ import org.utbot.framework.codegen.model.tree.buildCgForEachLoop import org.utbot.framework.codegen.model.tree.buildDeclaration import org.utbot.framework.codegen.model.tree.buildDoWhileLoop import org.utbot.framework.codegen.model.tree.buildForLoop -import org.utbot.framework.codegen.model.tree.buildSimpleBlock import org.utbot.framework.codegen.model.tree.buildTryCatch import org.utbot.framework.codegen.model.tree.buildWhileLoop import org.utbot.framework.codegen.model.util.buildExceptionHandler @@ -59,6 +57,7 @@ import org.utbot.framework.plugin.api.util.isSubtypeOf import org.utbot.framework.plugin.api.util.objectArrayClassId import org.utbot.framework.plugin.api.util.objectClassId import fj.data.Either +import org.utbot.framework.codegen.model.tree.CgArrayInitializer import java.lang.reflect.Constructor import java.lang.reflect.Method import kotlin.reflect.KFunction @@ -106,7 +105,7 @@ interface CgStatementConstructor { infix fun CgExpression.`=`(value: Any?) infix fun CgExpression.and(other: CgExpression): CgLogicalAnd infix fun CgExpression.or(other: CgExpression): CgLogicalOr - fun ifStatement(condition: CgExpression, trueBranch: () -> Unit): CgIfStatement + fun ifStatement(condition: CgExpression, trueBranch: () -> Unit, falseBranch: (() -> Unit)? = null): CgIfStatement fun forLoop(init: CgForLoopBuilder.() -> Unit) fun whileLoop(condition: CgExpression, statements: () -> Unit) fun doWhileLoop(condition: CgExpression, statements: () -> Unit) @@ -117,7 +116,7 @@ interface CgStatementConstructor { fun CgTryCatch.catch(exception: ClassId, init: (CgVariable) -> Unit): CgTryCatch fun CgTryCatch.finally(init: () -> Unit): CgTryCatch - fun innerBlock(init: () -> Unit, additionalStatements: List): CgInnerBlock + fun innerBlock(init: () -> Unit): CgInnerBlock // fun CgTryCatchBuilder.statements(init: () -> Unit) // fun CgTryCatchBuilder.handler(exception: ClassId, init: (CgVariable) -> Unit) @@ -176,6 +175,7 @@ internal class CgStatementConstructorImpl(context: CgContext) : val (type, expr) = when (baseExpr) { is CgEnumConstantAccess -> guardEnumConstantAccess(baseExpr) is CgAllocateArray -> guardArrayAllocation(baseExpr) + is CgArrayInitializer -> guardArrayInitializer(baseExpr) is CgExecutableCall -> guardExecutableCall(baseType, baseExpr) else -> guardExpression(baseType, baseExpr) } @@ -250,8 +250,10 @@ internal class CgStatementConstructorImpl(context: CgContext) : override fun CgExpression.or(other: CgExpression): CgLogicalOr = CgLogicalOr(this, other) - override fun ifStatement(condition: CgExpression, trueBranch: () -> Unit): CgIfStatement { - return CgIfStatement(condition, block(trueBranch)).also { + override fun ifStatement(condition: CgExpression, trueBranch: () -> Unit, falseBranch: (() -> Unit)?): CgIfStatement { + val trueBranchBlock = block(trueBranch) + val falseBranchBlock = falseBranch?.let { block(it) } + return CgIfStatement(condition, trueBranchBlock, falseBranchBlock).also { currentBlock += it } } @@ -300,12 +302,10 @@ internal class CgStatementConstructorImpl(context: CgContext) : return this.copy(finally = finallyBlock) } - override fun innerBlock( - init: () -> Unit, - additionalStatements: List, - ): CgInnerBlock = buildSimpleBlock { - statements = mutableListOf() + block(init) + additionalStatements - } + override fun innerBlock(init: () -> Unit): CgInnerBlock = + CgInnerBlock(block(init)).also { + currentBlock += it + } override fun comment(text: String): CgComment = CgSingleLineComment(text).also { @@ -421,13 +421,21 @@ internal class CgStatementConstructorImpl(context: CgContext) : } private fun guardArrayAllocation(allocation: CgAllocateArray): ExpressionWithType { + return guardArrayCreation(allocation.type, allocation.size, allocation) + } + + private fun guardArrayInitializer(initializer: CgArrayInitializer): ExpressionWithType { + return guardArrayCreation(initializer.type, initializer.size, initializer) + } + + private fun guardArrayCreation(arrayType: ClassId, arraySize: Int, initialization: CgExpression): ExpressionWithType { // TODO: check if this is the right way to check array type accessibility - return if (allocation.type.isAccessibleFrom(testClassPackageName)) { - ExpressionWithType(allocation.type, allocation) + return if (arrayType.isAccessibleFrom(testClassPackageName)) { + ExpressionWithType(arrayType, initialization) } else { ExpressionWithType( objectArrayClassId, - testClassThisInstance[createArray](allocation.elementType.name, allocation.size) + testClassThisInstance[createArray](arrayType.elementClassId!!.name, arraySize) ) } } 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 094988d99a..021e0bccca 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 @@ -42,6 +42,9 @@ 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.tree.CgAllocateInitializedArray +import org.utbot.framework.codegen.model.tree.CgArrayInitializer +import org.utbot.framework.plugin.api.util.arrayLikeName internal data class EnvironmentFieldStateCache( val thisInstance: FieldStateCache, @@ -153,6 +156,8 @@ private fun FieldPath.toStringList(): List = internal fun infiniteInts(): Sequence = generateSequence(1) { it + 1 } +internal const val MAX_ARRAY_INITIALIZER_SIZE = 10 + /** * Checks if we have already imported a class with such simple name. * If so, we cannot import [type] (because it will be used with simple name and will be clashed with already imported) @@ -216,6 +221,39 @@ internal fun CgContextOwner.typeCast( return CgTypeCast(targetType, expression, isSafetyCast) } +@Suppress("unused") +internal fun newArrayOf(elementType: ClassId, values: List): CgAllocateInitializedArray { + val arrayType = arrayTypeOf(elementType) + return CgAllocateInitializedArray(arrayInitializer(arrayType, elementType, values)) +} + +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[]`. + * + * @param elementType the element type of the returned array class id + * @param isNullable a flag whether returned array is nullable or not + */ +internal fun arrayTypeOf(elementType: ClassId, isNullable: Boolean = false): ClassId { + val arrayIdName = "[${elementType.arrayLikeName}" + return when (elementType) { + is BuiltinClassId -> BuiltinClassId( + name = arrayIdName, + canonicalName = "${elementType.canonicalName}[]", + simpleName = "${elementType.simpleName}[]", + isNullable = isNullable + ) + else -> ClassId( + name = arrayIdName, + elementClassId = elementType, + isNullable = isNullable + ) + } +} + @Suppress("unused") internal fun CgContextOwner.getJavaClass(classId: ClassId): CgGetClass { importIfNeeded(classId) 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 93dca48c75..d328e59217 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 @@ -100,8 +100,8 @@ class CgParameterizedTestDataProviderBuilder : CgMethodBuilder = mutableListOf() override lateinit var statements: List - override lateinit var annotations: MutableList - override lateinit var exceptions: MutableSet + override val annotations: MutableList = mutableListOf() + override val exceptions: MutableSet = mutableSetOf() override var documentation: CgDocumentationComment = CgDocumentationComment(emptyList()) override fun build() = CgParameterizedTestDataProviderMethod(name, statements, returnType, annotations, exceptions) @@ -145,14 +145,6 @@ class CgTryCatchBuilder : CgBuilder { fun buildTryCatch(init: CgTryCatchBuilder.() -> Unit): CgTryCatch = CgTryCatchBuilder().apply(init).build() -class CgBlockBuilder : CgBuilder { - lateinit var statements: List - - override fun build() = CgInnerBlock(statements) -} - -fun buildSimpleBlock(init: CgBlockBuilder.() -> Unit) = CgBlockBuilder().apply(init).build() - // Loops interface CgLoopBuilder : CgBuilder { val condition: CgExpression 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 faa3fc155a..ff80e387b0 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 @@ -19,7 +19,6 @@ import org.utbot.framework.plugin.api.ExecutableId import org.utbot.framework.plugin.api.FieldId import org.utbot.framework.plugin.api.MethodId import org.utbot.framework.plugin.api.TypeParameters -import org.utbot.framework.plugin.api.UtArrayModel import org.utbot.framework.plugin.api.util.booleanClassId import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.intClassId @@ -83,6 +82,7 @@ interface CgElement { is CgNonStaticRunnable -> visit(element) is CgStaticRunnable -> visit(element) is CgAllocateInitializedArray -> visit(element) + is CgArrayInitializer -> visit(element) is CgAllocateArray -> visit(element) is CgEnumConstantAccess -> visit(element) is CgFieldAccess -> visit(element) @@ -699,8 +699,19 @@ open class CgAllocateArray(type: ClassId, elementType: ClassId, val size: Int) : } } -class CgAllocateInitializedArray(val model: UtArrayModel) : - CgAllocateArray(model.classId, model.classId.elementClassId!!, model.length) +/** + * Allocation of an array with initialization. For example: `new String[] {"a", "b", null}`. + */ +class CgAllocateInitializedArray(val initializer: CgArrayInitializer) : + CgAllocateArray(initializer.arrayType, initializer.elementType, initializer.size) + +class CgArrayInitializer(val arrayType: ClassId, val elementType: ClassId, val values: List) : CgExpression { + val size: Int + get() = values.size + + override val type: ClassId + get() = arrayType +} // Spread operator (for Kotlin, empty for Java) 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 0b3d68d737..6938831770 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 @@ -77,21 +77,18 @@ import org.utbot.framework.codegen.model.tree.CgVariable import org.utbot.framework.codegen.model.tree.CgWhileLoop import org.utbot.framework.codegen.model.util.CgPrinter import org.utbot.framework.codegen.model.util.CgPrinterImpl -import org.utbot.framework.codegen.model.util.resolve import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.CodegenLanguage import org.utbot.framework.plugin.api.MethodId import org.utbot.framework.plugin.api.TypeParameters -import org.utbot.framework.plugin.api.UtArrayModel -import org.utbot.framework.plugin.api.UtModel -import org.utbot.framework.plugin.api.UtNullModel -import org.utbot.framework.plugin.api.UtPrimitiveModel import org.utbot.framework.plugin.api.util.booleanClassId import org.utbot.framework.plugin.api.util.byteClassId import org.utbot.framework.plugin.api.util.charClassId import org.utbot.framework.plugin.api.util.doubleClassId import org.utbot.framework.plugin.api.util.floatClassId import org.utbot.framework.plugin.api.util.intClassId +import org.utbot.framework.plugin.api.util.isArray +import org.utbot.framework.plugin.api.util.isRefType import org.utbot.framework.plugin.api.util.longClassId import org.utbot.framework.plugin.api.util.shortClassId @@ -110,14 +107,15 @@ internal abstract class CgAbstractRenderer(val context: CgContext, val printer: protected abstract val langPackage: String - //We may render array elements in initializer in one line or in separate lines: - //items count in one line depends on the value type. - protected fun arrayElementsInLine(constModel: UtModel): Int { - if (constModel is UtNullModel) return 10 - return when (constModel.classId) { + // We may render array elements in initializer in one line or in separate lines: + // items count in one line depends on the element type. + protected fun arrayElementsInLine(elementType: ClassId): Int { + if (elementType.isRefType) return 10 + if (elementType.isArray) return 1 + return when (elementType) { intClassId, byteClassId, longClassId, charClassId -> 8 booleanClassId, shortClassId, doubleClassId, floatClassId -> 6 - else -> error("Non primitive value of type ${constModel.classId} is unexpected in array initializer") + else -> error("Non primitive value of type $elementType is unexpected in array initializer") } } @@ -710,17 +708,6 @@ internal abstract class CgAbstractRenderer(val context: CgContext, val printer: protected abstract fun renderExceptionCatchVariable(exception: CgVariable) - protected fun UtArrayModel.getElementExpr(index: Int): CgExpression { - val itemModel = stores.getOrDefault(index, constModel) - val cgValue: CgExpression = when (itemModel) { - is UtPrimitiveModel -> itemModel.value.resolve() - is UtNullModel -> null.resolve() - else -> error("Non primitive or null model $itemModel is unexpected in array initializer") - } - - return cgValue - } - protected fun getEscapedImportRendering(import: Import): String = import.qualifiedName .split(".") @@ -752,10 +739,11 @@ internal abstract class CgAbstractRenderer(val context: CgContext, val printer: } } - protected fun UtArrayModel.renderElements(length: Int, elementsInLine: Int) { + protected fun List.renderElements(elementsInLine: Int) { + val length = this.size if (length <= elementsInLine) { // one-line array for (i in 0 until length) { - val expr = this.getElementExpr(i) + val expr = this[i] expr.accept(this@CgAbstractRenderer) if (i != length - 1) { print(", ") @@ -765,7 +753,7 @@ internal abstract class CgAbstractRenderer(val context: CgContext, val printer: println() // line break after `int[] x = {` withIndent { for (i in 0 until length) { - val expr = this.getElementExpr(i) + val expr = this[i] expr.accept(this@CgAbstractRenderer) if (i == length - 1) { 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 90b60d72d0..bc712bc51a 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 @@ -8,6 +8,7 @@ import org.utbot.framework.codegen.model.tree.CgAllocateArray import org.utbot.framework.codegen.model.tree.CgAllocateInitializedArray import org.utbot.framework.codegen.model.tree.CgAnonymousFunction import org.utbot.framework.codegen.model.tree.CgArrayAnnotationArgument +import org.utbot.framework.codegen.model.tree.CgArrayInitializer import org.utbot.framework.codegen.model.tree.CgBreakStatement import org.utbot.framework.codegen.model.tree.CgConstructorCall import org.utbot.framework.codegen.model.tree.CgDeclaration @@ -164,11 +165,21 @@ internal class CgJavaRenderer(context: CgContext, printer: CgPrinter = CgPrinter } override fun visit(element: CgAllocateInitializedArray) { - val arrayModel = element.model - val elementsInLine = arrayElementsInLine(arrayModel.constModel) + // TODO: same as in visit(CgAllocateArray): we should rewrite the typeName and otherDimensions variables declaration + // to avoid using substringBefore() and substringAfter() directly + val typeName = element.type.canonicalName.substringBefore("[") + val otherDimensions = element.type.canonicalName.substringAfter("]") + // we can't specify the size of the first dimension when using initializer, + // as opposed to CgAllocateArray where there is no initializer + print("new $typeName[]$otherDimensions") + element.initializer.accept(this) + } + + override fun visit(element: CgArrayInitializer) { + val elementsInLine = arrayElementsInLine(element.elementType) print("{") - arrayModel.renderElements(element.size, elementsInLine) + element.values.renderElements(elementsInLine) print("}") } 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 28f5626f16..a406c93fee 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 @@ -12,6 +12,7 @@ import org.utbot.framework.codegen.model.tree.CgAllocateInitializedArray import org.utbot.framework.codegen.model.tree.CgAnonymousFunction import org.utbot.framework.codegen.model.tree.CgArrayAnnotationArgument import org.utbot.framework.codegen.model.tree.CgArrayElementAccess +import org.utbot.framework.codegen.model.tree.CgArrayInitializer import org.utbot.framework.codegen.model.tree.CgComparison import org.utbot.framework.codegen.model.tree.CgConstructorCall import org.utbot.framework.codegen.model.tree.CgDeclaration @@ -45,7 +46,6 @@ import org.utbot.framework.plugin.api.BuiltinClassId import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.CodegenLanguage import org.utbot.framework.plugin.api.TypeParameters -import org.utbot.framework.plugin.api.UtPrimitiveModel import org.utbot.framework.plugin.api.WildcardTypeParameter import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.isArray @@ -246,18 +246,26 @@ internal class CgKotlinRenderer(context: CgContext, printer: CgPrinter = CgPrint } override fun visit(element: CgAllocateInitializedArray) { - val arrayModel = element.model - val elementsInLine = arrayElementsInLine(arrayModel.constModel) + print(getKotlinClassString(element.type)) + print("(${element.size})") + print(" {") + element.initializer.accept(this) + print(" }") + } - if (arrayModel.constModel is UtPrimitiveModel) { - val prefix = arrayModel.constModel.classId.name.toLowerCase() + override fun visit(element: CgArrayInitializer) { + val elementType = element.elementType + val elementsInLine = arrayElementsInLine(elementType) + + if (elementType.isPrimitive) { + val prefix = elementType.name.toLowerCase() print("${prefix}ArrayOf(") - arrayModel.renderElements(element.size, elementsInLine) + element.values.renderElements(elementsInLine) print(")") } else { - print(getKotlinClassString(element.type)) + print(getKotlinClassString(element.arrayType)) print("(${element.size})") - if (!element.elementType.isPrimitive) print(" { null }") + print(" { null }") } } @@ -302,8 +310,7 @@ internal class CgKotlinRenderer(context: CgContext, printer: CgPrinter = CgPrint } override fun renderMethodSignature(element: CgParameterizedTestDataProviderMethod) { - val returnType = - if (element.returnType.simpleName == "Array?>") "Array?>" else "${element.returnType}" + val returnType = getKotlinClassString(element.returnType) println("fun ${element.name}(): $returnType") } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgVisitor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgVisitor.kt index db1146154f..f2b6c4d905 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgVisitor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgVisitor.kt @@ -7,6 +7,7 @@ import org.utbot.framework.codegen.model.tree.CgAllocateInitializedArray import org.utbot.framework.codegen.model.tree.CgAnonymousFunction import org.utbot.framework.codegen.model.tree.CgArrayAnnotationArgument import org.utbot.framework.codegen.model.tree.CgArrayElementAccess +import org.utbot.framework.codegen.model.tree.CgArrayInitializer import org.utbot.framework.codegen.model.tree.CgAssignment import org.utbot.framework.codegen.model.tree.CgBreakStatement import org.utbot.framework.codegen.model.tree.CgComment @@ -203,6 +204,7 @@ interface CgVisitor { // Array allocation fun visit(element: CgAllocateArray): R fun visit(element: CgAllocateInitializedArray): R + fun visit(element: CgArrayInitializer): R // Spread operator fun visit(element: CgSpread): R