From 2b3c85c33546d0ff811533253080fceffe4249db Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Thu, 21 Jul 2022 16:24:33 +0300 Subject: [PATCH 1/6] Refactor code related to block construction Code generation relies on CgContext for storing information about the currently available variables, imports, statements, etc. The context also stores info about code block currently being generated (currentBlock). It is possible to collect statements into some list and then update the currentBlock with them, but it is discouraged, because this way it is very easy to occacionally lose some of the statements. That's because some statements will go to the currentBlock, but then will be overriden on update from the new list of statements. Hence, it is recommended to always work with the statements using currentBlock and DSL methods that also use it. Such methods can be found in CgStatementConstructor.kt, see method `ifStatement` for example. This PR makes more use of currentBlock instead of using separate lists. That is done in order to prevent possible future bugs. --- .../constructor/tree/CgMethodConstructor.kt | 170 +++++++----------- .../util/CgStatementConstructor.kt | 8 +- 2 files changed, 73 insertions(+), 105 deletions(-) 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..5313774ffd 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 @@ -27,6 +27,7 @@ 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 @@ -45,7 +46,6 @@ 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 @@ -505,7 +505,6 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c expectedModel: UtModel, expected: CgVariable?, actual: CgVariable, - statements: MutableList, depth: Int, visitedModels: MutableSet, ) { @@ -521,14 +520,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 +562,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c }.toStatement() } is UtEnumConstantModel -> { - statements += assertions[assertEquals]( + currentBlock += assertions[assertEquals]( expected, actual ).toStatement() @@ -571,26 +570,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 +602,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 +623,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 +651,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 +669,6 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c fieldModel, expected, actual, - statements, depth, visitedModels ) @@ -685,18 +679,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 +705,6 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c fieldModel, expected, actual, - statements, depth, visitedModels ) @@ -728,15 +721,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 +743,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 +754,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 +769,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 } } @@ -863,7 +843,6 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c fieldModel: UtModel, expected: CgVariable, actual: CgVariable, - statements: MutableList, depth: Int, visitedModels: MutableSet ) { @@ -885,22 +864,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 +1049,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 +1064,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() { @@ -1406,8 +1373,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c //create a block for current test case dataProviderStatements += innerBlock( {}, - block(executionArgumentsBody) - + createArgumentsCallRepresentation(execIndex, argListVariable, arguments) + block(executionArgumentsBody).addAll(createArgumentsCallRepresentation(execIndex, argListVariable, arguments)) ) dataProviderExceptions += collectedExceptions 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..6cb988765f 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 @@ -106,7 +106,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) @@ -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 } } From 954ea0451a89cf559047eb326849ed963362ab0a Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Thu, 28 Jul 2022 02:39:21 +0300 Subject: [PATCH 2/6] Refactor data provider method construction This idea of this refactoring is the same as in the previous commit. Instead of collecting statements into some lists we now collect them right into the currentBlock. --- .../model/constructor/context/CgContext.kt | 6 +- .../constructor/tree/CgMethodConstructor.kt | 262 +++++++++--------- .../constructor/tree/TestFrameworkManager.kt | 22 +- .../util/CgStatementConstructor.kt | 14 +- .../framework/codegen/model/tree/Builders.kt | 8 - 5 files changed, 155 insertions(+), 157 deletions(-) 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 5313774ffd..4a5cf4d3be 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 @@ -32,14 +32,11 @@ 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 @@ -55,11 +52,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 @@ -827,17 +822,6 @@ 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, @@ -1323,79 +1307,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).addAll(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 { @@ -1403,9 +1386,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() @@ -1422,38 +1420,32 @@ 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))) - ) - } - 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](arguments)) + TestNg -> { + val argsArray = newVar(objectArrayClassId, "testCaseObjects") { + CgAllocateArray(Array::class.java.id, objectClassId, arguments.size) + } + for ((i, argument) in arguments.withIndex()) { + setArgumentsArrayElement(argsArray, i, argument) + } + setArgumentsArrayElement(argsVariable, executionIndex, argsArray) } - statements += setArgumentsArrayElement(argsVariable, executionIndex, argsArrayDeclaration.variable) - - statements + 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 @@ -1474,16 +1466,20 @@ 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") } /** @@ -1512,7 +1508,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c /** * A [MethodId] to add an item into [ArrayList]. */ - private fun addToListMethodId(): MethodId = methodId( + private val addToListMethodId: MethodId = methodId( classId = ArrayList::class.id, name = "add", returnType = booleanClassId, @@ -1520,23 +1516,24 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c ) /** - * A [MethodId] to call JUnit Arguments method. + * A [ClassId] of class `org.junit.jupiter.params.provider.Arguments` */ - private fun argumentsMethodId(): MethodId { - val argumentsClassId = BuiltinClassId( - name = "org.junit.jupiter.params.provider.Arguments", - simpleName = "Arguments", - canonicalName = "org.junit.jupiter.params.provider.Arguments", - packageName = "org.junit.jupiter.params.provider", - ) + private val argumentsClassId: ClassId = BuiltinClassId( + name = "org.junit.jupiter.params.provider.Arguments", + simpleName = "Arguments", + canonicalName = "org.junit.jupiter.params.provider.Arguments", + packageName = "org.junit.jupiter.params.provider", + ) - return methodId( - classId = argumentsClassId, - name = "arguments", - returnType = argumentsClassId, - arguments = arrayOf(Object::class.id), - ) - } + /** + * A [MethodId] to call JUnit Arguments method. + */ + private val argumentsMethodId: MethodId = methodId( + classId = argumentsClassId, + name = "arguments", + returnType = argumentsClassId, + arguments = arrayOf(Object::class.id), + ) private fun containsFailureExecution(testSet: UtMethodTestSet) = testSet.executions.any { it.result is UtExecutionFailure } @@ -1575,7 +1572,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)) @@ -1603,9 +1600,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() @@ -1636,6 +1634,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/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 6cb988765f..9f06de09bf 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 @@ -117,7 +115,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) @@ -302,12 +300,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 { 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..d8597b3ef7 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 @@ -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 From 0575523bd15b27b3b1ae28d7bb1fe319ad678773 Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Fri, 29 Jul 2022 05:39:14 +0300 Subject: [PATCH 3/6] Fix ClassWithEnumTest.testOrdinal test The problem was in the parameterized tests. There were multiple bugs: wrong class ids (class id's name must not include generic parameters, but they did), wrong method id for Arguments.arguments() method (its argument must be vararg, but it wasn't). Also, when a method accepts a vararg and is called via reflection, an array of vararg arguments should be additionally wrapped into an array of Objects (e.g. new Object[]{new Integer[]{1, 2, 3}}). However, when calling this method without reflection there must not be any such wrappers. --- .../plugin/api/util/SignatureUtil.kt | 18 +++ .../constructor/tree/CgMethodConstructor.kt | 114 +++++++++--------- .../constructor/tree/CgVariableConstructor.kt | 12 +- .../constructor/util/ConstructorUtils.kt | 36 ++++++ .../framework/codegen/model/tree/Builders.kt | 4 +- .../framework/codegen/model/tree/CgElement.kt | 17 ++- .../model/visitor/CgAbstractRenderer.kt | 38 ++---- .../codegen/model/visitor/CgJavaRenderer.kt | 17 ++- .../codegen/model/visitor/CgKotlinRenderer.kt | 24 ++-- .../codegen/model/visitor/CgVisitor.kt | 2 + 10 files changed, 180 insertions(+), 102 deletions(-) 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/tree/CgMethodConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt index 4a5cf4d3be..d7bd804695 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 @@ -26,6 +25,7 @@ import org.utbot.framework.codegen.model.constructor.util.EnvironmentFieldStateC 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.newArrayOf 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 @@ -67,7 +67,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 @@ -121,6 +120,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.builtinMethodId import org.utbot.framework.plugin.api.util.doubleArrayClassId import org.utbot.framework.plugin.api.util.doubleClassId import org.utbot.framework.plugin.api.util.doubleWrapperClassId @@ -1285,7 +1285,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c if (containsFailureExecution) { val expectedException = CgParameterDeclaration( parameter = declareParameter( - type = throwableClassId(), + type = Class::class.id, name = nameGenerator.variableName(expectedErrorVarName) ), // exceptions are always reference type @@ -1422,7 +1422,13 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c arguments: List, ) { when (testFramework) { - Junit5 -> argsVariable[addToListMethodId](argumentsClassId[argumentsMethodId](arguments)) + Junit5 -> { + +argsVariable[addToListMethodId]( + argumentsClassId[argumentsMethodId]( + newArrayOf(objectClassId, arguments) + ) + ) + } TestNg -> { val argsArray = newVar(objectArrayClassId, "testCaseObjects") { CgAllocateArray(Array::class.java.id, objectClassId, arguments.size) @@ -1442,7 +1448,9 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c */ private fun setArgumentsArrayElement(array: CgVariable, index: Int, value: CgExpression) { when (array.type) { - objectClassId -> java.lang.reflect.Array::class.id[setArrayElement](array, index, value) + objectClassId -> { + +java.lang.reflect.Array::class.id[setArrayElement](array, index, value) + } else -> array.at(index) `=` value } } @@ -1470,13 +1478,13 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c val argListName = "argList" return when (testFramework) { Junit5 -> - newVar(argListClassId(), argListName) { - val constructor = ConstructorId(argListClassId(), emptyList()) + newVar(argListClassId, argListName) { + val constructor = ConstructorId(argListClassId, emptyList()) constructor.invoke() } TestNg -> - newVar(argListClassId(), argListName) { - CgAllocateArray(argListClassId(), Array::class.java.id, length) + newVar(argListClassId, argListName) { + CgAllocateArray(argListClassId, Array::class.java.id, length) } Junit4 -> error("Parameterized tests are not supported for JUnit4") } @@ -1485,69 +1493,59 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c /** * 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 -> java.util.ArrayList::class.id + 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") + } /** * A [MethodId] to add an item into [ArrayList]. */ - private val 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 [ClassId] of class `org.junit.jupiter.params.provider.Arguments` */ - private val argumentsClassId: ClassId = BuiltinClassId( - name = "org.junit.jupiter.params.provider.Arguments", - simpleName = "Arguments", - canonicalName = "org.junit.jupiter.params.provider.Arguments", - packageName = "org.junit.jupiter.params.provider", - ) + 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: MethodId = methodId( - classId = argumentsClassId, - name = "arguments", - returnType = argumentsClassId, - arguments = arrayOf(Object::class.id), - ) + private val argumentsMethodId: BuiltinMethodId + get() = builtinMethodId( + classId = argumentsClassId, + name = "arguments", + returnType = argumentsClassId, + // 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) { @@ -1637,12 +1635,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 = 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) + exceptions += collectedExceptions + annotations += createDataProviderAnnotations(dataProviderMethodName) } } 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..3194d0f00e 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,18 @@ 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.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 @@ -225,7 +224,14 @@ internal class CgVariableConstructor(val context: CgContext) : val canInitWithValues = elementModels.all { it is UtPrimitiveModel } || elementModels.all { it is UtNullModel } 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/util/ConstructorUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/ConstructorUtils.kt index 094988d99a..98c4220c12 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, @@ -216,6 +219,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 d8597b3ef7..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) 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..d356825b84 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 }") } } 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 From 449400bbba21dfb2a96e28c8b7570830a2fecbcd Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Sun, 31 Jul 2022 19:28:01 +0300 Subject: [PATCH 4/6] Fix Arguments#arguments method call - Make Arguments#arguments method id static. This allowed us to call this method without reflection. Previously we used reflection, because the method id was non-static by mistake. - Construct an array of arguments for Arguments#arguments call. This way we don't have to worry about multiple cases of vararg method calls, but we should think about them later. --- .../constructor/tree/CgMethodConstructor.kt | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) 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 d7bd804695..db00a42429 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 @@ -25,7 +25,6 @@ import org.utbot.framework.codegen.model.constructor.util.EnvironmentFieldStateC 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.newArrayOf 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 @@ -120,7 +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.builtinMethodId +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 @@ -1421,21 +1420,19 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c argsVariable: CgVariable, arguments: List, ) { + val argsArray = newVar(objectArrayClassId, "testCaseObjects") { + CgAllocateArray(objectArrayClassId, objectClassId, arguments.size) + } + for ((i, argument) in arguments.withIndex()) { + setArgumentsArrayElement(argsArray, i, argument) + } when (testFramework) { Junit5 -> { +argsVariable[addToListMethodId]( - argumentsClassId[argumentsMethodId]( - newArrayOf(objectClassId, arguments) - ) + argumentsClassId[argumentsMethodId](argsArray) ) } TestNg -> { - val argsArray = newVar(objectArrayClassId, "testCaseObjects") { - CgAllocateArray(Array::class.java.id, objectClassId, arguments.size) - } - for ((i, argument) in arguments.withIndex()) { - setArgumentsArrayElement(argsArray, i, argument) - } setArgumentsArrayElement(argsVariable, executionIndex, argsArray) } Junit4 -> error("Parameterized tests are not supported for JUnit4") @@ -1535,7 +1532,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c * A [MethodId] to call JUnit Arguments method. */ private val argumentsMethodId: BuiltinMethodId - get() = builtinMethodId( + get() = builtinStaticMethodId( classId = argumentsClassId, name = "arguments", returnType = argumentsClassId, From 5541d7b77338f10d7fe44dabd2a62ae8e2d077f5 Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Sun, 31 Jul 2022 19:32:12 +0300 Subject: [PATCH 5/6] Limit the maximum length of an array initializer + use guards for both array allocation and initialization --- .../constructor/tree/CgVariableConstructor.kt | 7 ++++++- .../constructor/util/CgStatementConstructor.kt | 16 +++++++++++++--- .../model/constructor/util/ConstructorUtils.kt | 2 ++ 3 files changed, 21 insertions(+), 4 deletions(-) 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 3194d0f00e..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,6 +6,7 @@ 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 @@ -221,7 +222,11 @@ 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) { val elements = elementModels.map { model -> 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 9f06de09bf..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 @@ -57,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 @@ -174,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) } @@ -419,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 98c4220c12..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 @@ -156,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) From ce9d3e07f31bfdf6940a759aa8b0683360cf5ce7 Mon Sep 17 00:00:00 2001 From: Arsen Nagdalian Date: Sun, 31 Jul 2022 20:54:07 +0300 Subject: [PATCH 6/6] Rework type parameters for some class ids Earlier type parameters were hardcoded in the class name, simple name, etc. Then they were removed from names completely, but that lead to compilation errors for Kotlin, where type parameters must always be specified. So, this commit adds type parameters, but instead of writing them into names, we add them as a separate property 'typeParameters' of BuiltinClassId. --- .../org/utbot/framework/plugin/api/Api.kt | 4 +- .../constructor/tree/CgMethodConstructor.kt | 49 ++++++++++++++----- .../codegen/model/visitor/CgKotlinRenderer.kt | 3 +- 3 files changed, 42 insertions(+), 14 deletions(-) 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/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 db00a42429..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 @@ -1282,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 = Class::class.id, + 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 @@ -1492,16 +1499,36 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c */ private val argListClassId: ClassId get() = when (testFramework) { - Junit5 -> java.util.ArrayList::class.id - 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, - ) + 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") } 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 d356825b84..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 @@ -310,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") }