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 621e1377a2..e7c2e0bbc7 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 @@ -610,10 +610,13 @@ val Type.classId: ClassId * [elementClassId] if this class id represents an array class, then this property * represents the class id of the array's elements. Otherwise, this property is null. */ -open class ClassId( +open class ClassId @JvmOverloads constructor( val name: String, - val elementClassId: ClassId? = null + val elementClassId: ClassId? = null, + // Treat simple class ids as non-nullable + open val isNullable: Boolean = false ) { + open val canonicalName: String get() = jClass.canonicalName ?: error("ClassId $name does not have canonical name") @@ -677,10 +680,6 @@ open class ClassId( open val isSynthetic: Boolean get() = jClass.isSynthetic - open val isNullable: Boolean - // Treat simple class ids as non-nullable - get() = false - /** * Collects all declared methods (including private and protected) from class and all its superclasses to sequence */ @@ -755,6 +754,7 @@ class BuiltinClassId( override val simpleName: String, // by default we assume that the class is not a member class override val simpleNameWithEnclosings: String = simpleName, + override val isNullable: Boolean = false, override val isPublic: Boolean = true, override val isProtected: Boolean = false, override val isPrivate: Boolean = false, @@ -774,7 +774,7 @@ class BuiltinClassId( -1, 0 -> "" else -> canonicalName.substring(0, index) }, -) : ClassId(name) { +) : ClassId(name = name, isNullable = isNullable) { init { BUILTIN_CLASSES_BY_NAMES[name] = this } diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt index 9d2173115b..02779a6e93 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt @@ -236,6 +236,21 @@ val idToPrimitive: Map> = mapOf( */ fun isPrimitiveWrapperOrString(type: ClassId): Boolean = (type in primitiveWrappers) || (type == stringClassId) +/** + * Returns a wrapper of a given type if it is primitive or a type itself otherwise. + */ +fun wrapIfPrimitive(type: ClassId): ClassId = when (type) { + booleanClassId -> booleanWrapperClassId + byteClassId -> byteWrapperClassId + charClassId -> charWrapperClassId + shortClassId -> shortWrapperClassId + intClassId -> intWrapperClassId + longClassId -> longWrapperClassId + floatClassId -> floatWrapperClassId + doubleClassId -> doubleWrapperClassId + else -> type +} + /** * Note: currently uses class$innerClass form to load classes with classloader. */ diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/assemble/AssemblePrimitiveWrapper.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/assemble/AssemblePrimitiveWrapper.kt new file mode 100644 index 0000000000..2b83b35897 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/assemble/AssemblePrimitiveWrapper.kt @@ -0,0 +1,50 @@ +package org.utbot.framework.assemble + +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +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.executableId +import org.utbot.framework.plugin.api.util.floatClassId +import org.utbot.framework.plugin.api.util.intClassId +import org.utbot.framework.plugin.api.util.longClassId +import org.utbot.framework.plugin.api.util.shortClassId +import org.utbot.framework.plugin.api.util.wrapIfPrimitive + +/** + * Creates [UtAssembleModel] of the wrapper for a given [UtPrimitiveModel]. + */ +fun assemble(model: UtPrimitiveModel): UtAssembleModel { + val modelType = model.classId + val assembledModelType = wrapIfPrimitive(modelType) + + val constructorCall = when (modelType) { + shortClassId -> java.lang.Short::class.java.getConstructor(Short::class.java) + intClassId -> java.lang.Integer::class.java.getConstructor(Int::class.java) + longClassId -> java.lang.Long::class.java.getConstructor(Long::class.java) + charClassId -> java.lang.Character::class.java.getConstructor(Char::class.java) + byteClassId -> java.lang.Byte::class.java.getConstructor(Byte::class.java) + booleanClassId -> java.lang.Boolean::class.java.getConstructor(Boolean::class.java) + floatClassId -> java.lang.Float::class.java.getConstructor(Float::class.java) + doubleClassId -> java.lang.Double::class.java.getConstructor(Double::class.java) + else -> error("Model type $modelType is void or non-primitive") + } + + val constructorCallModel = UtExecutableCallModel( + instance = null, + executable = constructorCall.executableId, + params = listOf(model), + returnValue = null, + ) + + return UtAssembleModel( + id = null, + classId = assembledModelType, + modelName = modelType.canonicalName, + instantiationChain = listOf(constructorCallModel), + modificationsChain = emptyList(), + ) +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt index 7b10d5d469..bd72b6bf77 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 @@ -54,6 +54,7 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.persistentSetOf import org.utbot.framework.codegen.model.constructor.builtin.streamsDeepEqualsMethodId +import org.utbot.framework.codegen.model.tree.CgParameterKind import org.utbot.framework.plugin.api.util.executableId import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.isCheckedException @@ -70,8 +71,8 @@ import org.utbot.framework.plugin.api.util.jClass * For example, [currentTestClass] and [currentExecutable] can be reassigned * when we start generating another method or test class * - * [variables] and [existingVariableNames] are 'var' properties - * that can be reverted to their previous values on exit from a name scope + * [existingVariableNames] is a 'var' property + * that can be reverted to its previous value on exit from a name scope * * @see [CgContextOwner.withNameScope] */ @@ -155,9 +156,6 @@ internal interface CgContextOwner { // variable names being used in the current name scope var existingVariableNames: PersistentSet - // all declared variables in the current name scope - var variables: PersistentSet - // variables of java.lang.Class type declared in the current name scope var declaredClassRefs: PersistentMap @@ -190,6 +188,9 @@ internal interface CgContextOwner { // use it to compare stateBefore and result variables - in case of equality do not create new variable var valueByModelId: MutableMap + // parameters of the method currently being generated + val currentMethodParameters: MutableMap + val testClassCustomName: String? /** @@ -282,7 +283,6 @@ internal interface CgContextOwner { } fun updateVariableScope(variable: CgVariable, model: UtModel? = null) { - variables = variables.add(variable) model?.let { valueByModel[it] = variable (model as UtReferenceModel).let { refModel -> @@ -293,7 +293,6 @@ internal interface CgContextOwner { fun withNameScope(block: () -> R): R { val prevVariableNames = existingVariableNames - val prevVariables = variables val prevDeclaredClassRefs = declaredClassRefs val prevDeclaredExecutableRefs = declaredExecutableRefs val prevValueByModel = IdentityHashMap(valueByModel) @@ -302,7 +301,6 @@ internal interface CgContextOwner { block() } finally { existingVariableNames = prevVariableNames - variables = prevVariables declaredClassRefs = prevDeclaredClassRefs declaredExecutableRefs = prevDeclaredExecutableRefs valueByModel = prevValueByModel @@ -413,7 +411,6 @@ internal data class CgContext( override var mockFrameworkUsed: Boolean = false, override var currentBlock: PersistentList = persistentListOf(), override var existingVariableNames: PersistentSet = persistentSetOf(), - override var variables: PersistentSet = persistentSetOf(), override var declaredClassRefs: PersistentMap = persistentMapOf(), override var declaredExecutableRefs: PersistentMap = persistentMapOf(), override var thisInstance: CgValue? = null, @@ -454,5 +451,7 @@ internal data class CgContext( override var valueByModelId: MutableMap = mutableMapOf() + override val currentMethodParameters: MutableMap = mutableMapOf() + override val testClassThisInstance: CgThisInstance = CgThisInstance(currentTestClass) } \ No newline at end of file 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 59e0d032a8..c9b7f2f231 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt @@ -3,6 +3,7 @@ package org.utbot.framework.codegen.model.constructor.tree import org.utbot.common.PathUtil 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 @@ -49,7 +50,9 @@ import org.utbot.framework.codegen.model.tree.CgLiteral import org.utbot.framework.codegen.model.tree.CgMethod import org.utbot.framework.codegen.model.tree.CgMethodCall import org.utbot.framework.codegen.model.tree.CgMultilineComment +import org.utbot.framework.codegen.model.tree.CgNotNullAssertion 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 @@ -111,23 +114,18 @@ import org.utbot.framework.plugin.api.UtExecutionFailure import org.utbot.framework.plugin.api.UtExecutionSuccess import org.utbot.framework.plugin.api.UtExplicitlyThrownException import org.utbot.framework.plugin.api.UtMethod +import org.utbot.framework.plugin.api.UtMethodTestSet import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtNewInstanceInstrumentation import org.utbot.framework.plugin.api.UtNullModel import org.utbot.framework.plugin.api.UtPrimitiveModel import org.utbot.framework.plugin.api.UtReferenceModel import org.utbot.framework.plugin.api.UtStaticMethodInstrumentation -import org.utbot.framework.plugin.api.UtMethodTestSet import org.utbot.framework.plugin.api.UtTimeoutException 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.booleanWrapperClassId -import org.utbot.framework.plugin.api.util.byteClassId -import org.utbot.framework.plugin.api.util.byteWrapperClassId -import org.utbot.framework.plugin.api.util.charClassId -import org.utbot.framework.plugin.api.util.charWrapperClassId import org.utbot.framework.plugin.api.util.doubleArrayClassId import org.utbot.framework.plugin.api.util.doubleClassId import org.utbot.framework.plugin.api.util.doubleWrapperClassId @@ -138,23 +136,21 @@ import org.utbot.framework.plugin.api.util.floatWrapperClassId import org.utbot.framework.plugin.api.util.hasField import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.intClassId -import org.utbot.framework.plugin.api.util.intWrapperClassId import org.utbot.framework.plugin.api.util.isArray import org.utbot.framework.plugin.api.util.isInnerClassEnclosingClassReference import org.utbot.framework.plugin.api.util.isIterableOrMap import org.utbot.framework.plugin.api.util.isPrimitive import org.utbot.framework.plugin.api.util.isPrimitiveArray +import org.utbot.framework.plugin.api.util.isPrimitiveWrapper +import org.utbot.framework.plugin.api.util.isRefType import org.utbot.framework.plugin.api.util.jClass import org.utbot.framework.plugin.api.util.kClass -import org.utbot.framework.plugin.api.util.longClassId -import org.utbot.framework.plugin.api.util.longWrapperClassId import org.utbot.framework.plugin.api.util.methodId import org.utbot.framework.plugin.api.util.objectArrayClassId import org.utbot.framework.plugin.api.util.objectClassId -import org.utbot.framework.plugin.api.util.shortClassId -import org.utbot.framework.plugin.api.util.shortWrapperClassId import org.utbot.framework.plugin.api.util.stringClassId import org.utbot.framework.plugin.api.util.voidClassId +import org.utbot.framework.plugin.api.util.wrapIfPrimitive import org.utbot.framework.util.isUnit import org.utbot.summary.SummarySentenceConstants.TAB import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl @@ -448,30 +444,15 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c if (result.isUnit()) { +thisInstance[method](*methodArguments.toTypedArray()) } else { - resultModel = result - - val expected = variableConstructor.parameterizedVariable( - result.classId, - expectedResultVarName, - isNotNull = true - ) + //"generic" expected variable is represented with a wrapper if + //actual result is primitive to support cases with exceptions. + resultModel = if (result is UtPrimitiveModel) assemble(result) else result - val actualVariableName = when (codegenLanguage) { - CodegenLanguage.JAVA -> when (method.returnType) { - intClassId -> "${intWrapperClassId.simpleName.capitalize()}.valueOf(actual)" - shortClassId -> "${shortWrapperClassId.simpleName.capitalize()}.valueOf(actual)" - longClassId -> "${longWrapperClassId.simpleName.capitalize()}.valueOf(actual)" - byteClassId -> "${byteWrapperClassId.simpleName.capitalize()}.valueOf(actual)" - booleanClassId -> "${booleanWrapperClassId.simpleName.capitalize()}.valueOf(actual)" - charClassId -> "${charWrapperClassId.simpleName.capitalize()}.valueOf(actual)" - floatClassId -> "${floatWrapperClassId.simpleName.capitalize()}.valueOf(actual)" - doubleWrapperClassId -> "${doubleWrapperClassId.simpleName.capitalize()}.valueOf(actual)" - else -> "actual" - } - CodegenLanguage.KOTLIN -> "actual" - } + val expectedVariable = currentMethodParameters[CgParameterKind.ExpectedResult]!! + val expectedExpression = CgNotNullAssertion(expectedVariable) - assertEquality(expected, CgVariable(actualVariableName, method.returnType)) + assertEquality(expectedExpression, actual) + println() } } .onFailure { thisInstance[method](*methodArguments.toTypedArray()).intercepted() } @@ -646,6 +627,11 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c ).toStatement() } is UtAssembleModel -> { + if (expectedModel.classId.isPrimitiveWrapper) { + statements += 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) @@ -1059,13 +1045,19 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c } } else -> { - if (expected is CgLiteral) { - // Literal can only be Primitive or String, can use equals here - testFrameworkManager.assertEquals(expected, actual) - return + when (expected) { + is CgLiteral -> { + // Literal can only be Primitive or String, can use equals here + testFrameworkManager.assertEquals(expected, actual) + } + is CgNotNullAssertion -> { + require(expected.expression is CgVariable) { + "Only Variable wrapped in CgNotNullAssertion is supported in deep equals" + } + currentBlock = currentBlock.addAll(generateDeepEqualsAssertion(expected.expression, actual)) + } + else -> generateDeepEqualsOrNullAssertion(expected, actual) } - - generateDeepEqualsOrNullAssertion(expected, actual) } } } @@ -1084,14 +1076,18 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c ParametrizedTestSource.DO_NOT_PARAMETRIZE -> currentBlock = currentBlock.addAll(generateDeepEqualsAssertion(expected, actual)) ParametrizedTestSource.PARAMETRIZE -> { - val assertNullStmt = listOf(testFrameworkManager.assertions[testFramework.assertNull](actual).toStatement()) - currentBlock = currentBlock.add( - CgIfStatement( - CgEqualTo(expected, nullLiteral()), - assertNullStmt, - generateDeepEqualsAssertion(expected, actual) + 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) + ) ) - ) + } } } } @@ -1222,105 +1218,140 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c private val expectedErrorVarName = "expectedError" fun createParameterizedTestMethod(testSet: UtMethodTestSet, dataProviderMethodName: String): CgTestMethod? { - val methodUnderTest = testSet.method - val methodUnderTestParameters = testSet.method.callable.parameters - if (testSet.executions.isEmpty()) { return null } - //TODO: orientation on arbitrary execution may be misleading, but what is the alternative? + //TODO: orientation on generic execution may be misleading, but what is the alternative? //may be a heuristic to select a model with minimal number of internal nulls should be used - val arbitraryExecution = testSet.executions + val genericExecution = testSet.executions .firstOrNull { it.result is UtExecutionSuccess && (it.result as UtExecutionSuccess).model !is UtNullModel } ?: testSet.executions.first() - return withTestMethodScope(arbitraryExecution) { + return withTestMethodScope(genericExecution) { val testName = nameGenerator.parameterizedTestMethodName(dataProviderMethodName) - val testArguments = mutableListOf() - val mainBody = { - // build this instance - thisInstance = arbitraryExecution.stateBefore.thisInstance?.let { - val thisInstanceVariable = constructInstanceVariable(it) - testArguments += CgParameterDeclaration(thisInstanceVariable) - thisInstanceVariable - } - - // build arguments for method under test and parameterized test - for (index in arbitraryExecution.stateBefore.parameters.indices) { - val argumentName = paramNames[methodUnderTest]?.get(index) - val paramIndex = if (methodUnderTest.isStatic) index else index + 1 - val paramType = methodUnderTestParameters[paramIndex].type.javaType + withNameScope { + val testParameterDeclarations = createParameterDeclarations(testSet, genericExecution) + val mainBody = { + // build this instance + thisInstance = genericExecution.stateBefore.thisInstance?.let { currentMethodParameters[CgParameterKind.ThisInstance] } - val argumentClassId = when { - paramType is Class<*> && paramType.isArray -> paramType.id - paramType is ParameterizedTypeImpl -> paramType.rawType.id - else -> ClassId(paramType.typeName) + // build arguments for method under test and parameterized test + for (index in genericExecution.stateBefore.parameters.indices) { + methodArguments += currentMethodParameters[CgParameterKind.Argument(index)]!! } - val argument = variableConstructor.parameterizedVariable(argumentClassId, argumentName) - methodArguments += argument - testArguments += CgParameterDeclaration( - argument.name, argument.type, isReferenceType = !argument.type.isPrimitive - ) + //record result and generate result assertions + recordActualResult() + generateAssertionsForParameterizedTest() } - val method = currentExecutable as MethodId - val containsFailureExecution = containsFailureExecution(testSet) - val expectedResultClassId = wrapTypeIfRequired(method.returnType) - - if (expectedResultClassId != voidClassId) { - testArguments += CgParameterDeclaration( - expectedResultVarName, resultClassId(expectedResultClassId), - isReferenceType = containsFailureExecution || !expectedResultClassId.isPrimitive - ) + methodType = PARAMETRIZED + testMethod( + testName, + displayName = null, + testParameterDeclarations, + parameterized = true, + dataProviderMethodName + ) { + if (containsFailureExecution(testSet)) { + +tryBlock(mainBody) + .catch(Throwable::class.java.id) { e -> + val pseudoExceptionVarName = when (codegenLanguage) { + CodegenLanguage.JAVA -> "${expectedErrorVarName}.isInstance(${e.name.decapitalize()})" + CodegenLanguage.KOTLIN -> "${expectedErrorVarName}!!.isInstance(${e.name.decapitalize()})" + } + + testFrameworkManager.assertBoolean(CgVariable(pseudoExceptionVarName, booleanClassId)) + } + } else { + mainBody() + } } - if (containsFailureExecution) { - testArguments += CgParameterDeclaration( - expectedErrorVarName, - throwableClassId(), - isReferenceType = true - ) + } + } + } + + private fun createParameterDeclarations( + testSet: UtMethodTestSet, + genericExecution: UtExecution, + ): List { + val methodUnderTest = testSet.method + val methodUnderTestParameters = testSet.method.callable.parameters + + return mutableListOf().apply { + // this instance + val thisInstanceModel = genericExecution.stateBefore.thisInstance + if (thisInstanceModel != null) { + val type = thisInstanceModel.classId + val thisInstance = CgParameterDeclaration( + parameter = declareParameter( + type = type, + name = nameGenerator.variableName(type) + ), + isReferenceType = true + ) + this += thisInstance + currentMethodParameters[CgParameterKind.ThisInstance] = thisInstance.parameter + } + // arguments + for (index in genericExecution.stateBefore.parameters.indices) { + val argumentName = paramNames[methodUnderTest]?.get(index) + val paramIndex = if (methodUnderTest.isStatic) index else index + 1 + val paramType = methodUnderTestParameters[paramIndex].type.javaType + + val argumentType = when { + paramType is Class<*> && paramType.isArray -> paramType.id + paramType is ParameterizedTypeImpl -> paramType.rawType.id + else -> ClassId(paramType.typeName) } - //record result and generate result assertions - recordActualResult() - generateAssertionsForParameterizedTest() + val argument = CgParameterDeclaration( + parameter = declareParameter( + type = argumentType, + name = nameGenerator.variableName(argumentType, argumentName), + ), + isReferenceType = argumentType.isRefType + ) + this += argument + currentMethodParameters[CgParameterKind.Argument(index)] = argument.parameter } - methodType = PARAMETRIZED - testMethod( - testName, - displayName = null, - testArguments, - parameterized = true, - dataProviderMethodName - ) { - if (containsFailureExecution(testSet)) { - +tryBlock(mainBody) - .catch(Throwable::class.java.id) { e -> - val pseudoExceptionVarName = when (codegenLanguage) { - CodegenLanguage.JAVA -> "${expectedErrorVarName}.isInstance(${e.name.decapitalize()})" - CodegenLanguage.KOTLIN -> "${expectedErrorVarName}!!.isInstance(${e.name.decapitalize()})" - } + val method = currentExecutable as MethodId + val containsFailureExecution = containsFailureExecution(testSet) + + val expectedResultClassId = wrapTypeIfRequired(method.returnType) + + if (expectedResultClassId != voidClassId) { + val wrappedType = wrapIfPrimitive(expectedResultClassId) + //We are required to wrap the type of expected result if it is primitive + //to support nulls for throwing exceptions executions. + val expectedResult = CgParameterDeclaration( + parameter = declareParameter( + type = wrappedType, + name = nameGenerator.variableName(expectedResultVarName) + ), + isReferenceType = wrappedType.isRefType + ) + this += expectedResult + currentMethodParameters[CgParameterKind.ExpectedResult] = expectedResult.parameter + } - testFrameworkManager.assertBoolean(CgVariable(pseudoExceptionVarName, booleanClassId)) - } - } else { - mainBody() - } + if (containsFailureExecution) { + val expectedException = CgParameterDeclaration( + parameter = declareParameter( + type = throwableClassId(), + name = nameGenerator.variableName(expectedErrorVarName) + ), + // exceptions are always reference type + isReferenceType = true + ) + this += expectedException + currentMethodParameters[CgParameterKind.ExpectedException] = expectedException.parameter } } } - /** - * Constructs a variable for the instance parameter of parametrized test. - */ - private fun constructInstanceVariable(instanceModel: UtModel): CgVariable { - val className = instanceModel.classId.simpleName.decapitalize() - return variableConstructor.parameterizedVariable(instanceModel.classId, className) - } - /** * Constructs data provider method for parameterized tests. * @@ -1420,6 +1451,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c methodArguments.clear() currentExecution = null mockFrameworkManager.clearExecutionResources() + currentMethodParameters.clear() } /** @@ -1549,18 +1581,6 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c private fun containsFailureExecution(testSet: UtMethodTestSet) = testSet.executions.any { it.result is UtExecutionFailure } - private fun resultClassId(returnType: ClassId): ClassId = when (returnType) { - booleanClassId -> booleanWrapperClassId - byteClassId -> byteWrapperClassId - charClassId -> charWrapperClassId - shortClassId -> shortWrapperClassId - intClassId -> intWrapperClassId - longClassId -> longWrapperClassId - floatClassId -> floatWrapperClassId - doubleClassId -> doubleWrapperClassId - else -> returnType - } - /** * A [ClassId] for Class. */ 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 534505dec5..e66b988daa 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 @@ -18,7 +18,7 @@ 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.CgNotNullVariable +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 @@ -114,14 +114,6 @@ internal class CgVariableConstructor(val context: CgContext) : } } - /** - * Creates general variable of type (to replace with concrete value in each test case). - */ - fun parameterizedVariable(classId: ClassId, baseName: String? = null, isNotNull: Boolean = false): CgVariable { - val name = nameGenerator.variableName(type = classId, base = baseName, isMock = false) - return if (isNotNull) CgNotNullVariable(name, classId) else CgVariable(name, classId) - } - private fun constructComposite(model: UtCompositeModel, baseName: String): CgVariable { val obj = if (model.isMock) { mockFrameworkManager.createMockFor(model, baseName) 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 c36aac6f28..faa3fc155a 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 @@ -6,6 +6,7 @@ import org.utbot.framework.codegen.Import import org.utbot.framework.codegen.model.constructor.tree.TestsGenerationReport import org.utbot.framework.codegen.model.util.CgExceptionHandler import org.utbot.framework.codegen.model.visitor.CgVisitor +import org.utbot.framework.plugin.api.BuiltinClassId import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.ConstructorId import org.utbot.framework.plugin.api.DocClassLinkStmt @@ -75,8 +76,7 @@ interface CgElement { is CgAssignment -> visit(element) is CgTypeCast -> visit(element) is CgThisInstance -> visit(element) - //Not that order of variables is important - is CgNotNullVariable -> visit(element) + is CgNotNullAssertion -> visit(element) is CgVariable -> visit(element) is CgParameterDeclaration -> visit(element) is CgLiteral -> visit(element) @@ -449,7 +449,7 @@ data class CgTryCatch( data class CgErrorWrapper( val message: String, val expression: CgExpression, -): CgExpression { +) : CgExpression { override val type: ClassId get() = expression.type } @@ -520,6 +520,7 @@ interface CgExpression : CgStatement { } // marker interface representing expressions returning reference +// TODO: it seems that not all [CgValue] implementations are reference expressions interface CgReferenceExpression : CgExpression /** @@ -580,7 +581,22 @@ open class CgVariable( * - in Java it is an equivalent of [CgVariable] * - in Kotlin the difference is in addition of "!!" to the name */ -class CgNotNullVariable(name: String, type: ClassId) : CgVariable(name, type) +class CgNotNullAssertion(val expression: CgExpression) : CgValue { + override val type: ClassId + get() = when (val expressionType = expression.type) { + is BuiltinClassId -> BuiltinClassId( + name = expressionType.name, + canonicalName = expressionType.canonicalName, + simpleName = expressionType.simpleName, + isNullable = false, + ) + else -> ClassId( + expressionType.name, + expressionType.elementClassId, + isNullable = false, + ) + } +} /** * Method parameters declaration @@ -604,6 +620,21 @@ data class CgParameterDeclaration( get() = parameter.type } +/** + * Test method parameter can be one of the following types: + * - this instance for method under test (MUT) + * - argument of MUT with a certain index + * - result expected from MUT with the given arguments + * - exception expected from MUT with the given arguments + */ +sealed class CgParameterKind { + object ThisInstance : CgParameterKind() + data class Argument(val index: Int) : CgParameterKind() + object ExpectedResult : CgParameterKind() + object ExpectedException : CgParameterKind() +} + + // Primitive and String literals class CgLiteral(override val type: ClassId, val value: Any?) : CgValue { @@ -627,7 +658,7 @@ class CgLiteral(override val type: ClassId, val value: Any?) : CgValue { } // Runnable like this::toString or (new Object())::toString (non-static) or Random::nextRandomInt (static) etc -abstract class CgRunnable(override val type: ClassId, val methodId: MethodId): CgValue +abstract class CgRunnable(override val type: ClassId, val methodId: MethodId) : CgValue /** * [referenceExpression] is "this" in this::toString or (new Object()) in (new Object())::toString (non-static) @@ -636,18 +667,23 @@ class CgNonStaticRunnable( type: ClassId, val referenceExpression: CgReferenceExpression, methodId: MethodId -): CgRunnable(type, methodId) +) : CgRunnable(type, methodId) /** * [classId] is Random is Random::nextRandomInt (static) etc */ -class CgStaticRunnable(type: ClassId, val classId: ClassId, methodId: MethodId): CgRunnable(type, methodId) +class CgStaticRunnable(type: ClassId, val classId: ClassId, methodId: MethodId) : CgRunnable(type, methodId) // Array allocation -open class CgAllocateArray(type: ClassId, elementType: ClassId, val size: Int) - : CgReferenceExpression { - override val type: ClassId by lazy { CgClassId(type.name, updateElementType(elementType), isNullable = type.isNullable) } +open class CgAllocateArray(type: ClassId, elementType: ClassId, val size: Int) : CgReferenceExpression { + override val type: ClassId by lazy { + CgClassId( + type.name, + updateElementType(elementType), + isNullable = type.isNullable + ) + } val elementType: ClassId by lazy { workaround(WorkaroundReason.ARRAY_ELEMENT_TYPES_ALWAYS_NULLABLE) { // for now all array element types are nullable @@ -663,13 +699,13 @@ open class CgAllocateArray(type: ClassId, elementType: ClassId, val size: Int) } } -class CgAllocateInitializedArray(val model: UtArrayModel) - : CgAllocateArray(model.classId, model.classId.elementClassId!!, model.length) +class CgAllocateInitializedArray(val model: UtArrayModel) : + CgAllocateArray(model.classId, model.classId.elementClassId!!, model.length) // Spread operator (for Kotlin, empty for Java) -class CgSpread(override val type: ClassId, val array: CgExpression): CgExpression +class CgSpread(override val type: ClassId, val array: CgExpression) : CgExpression // Enum constant 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 866787fb17..0b3d68d737 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 @@ -50,7 +50,6 @@ import org.utbot.framework.codegen.model.tree.CgMultilineComment import org.utbot.framework.codegen.model.tree.CgMultipleArgsAnnotation import org.utbot.framework.codegen.model.tree.CgNamedAnnotationArgument import org.utbot.framework.codegen.model.tree.CgNonStaticRunnable -import org.utbot.framework.codegen.model.tree.CgNotNullVariable import org.utbot.framework.codegen.model.tree.CgParameterDeclaration import org.utbot.framework.codegen.model.tree.CgParameterizedTestDataProviderMethod import org.utbot.framework.codegen.model.tree.CgRegion @@ -540,8 +539,6 @@ internal abstract class CgAbstractRenderer(val context: CgContext, val printer: print(element.name.escapeNamePossibleKeyword()) } - abstract override fun visit(element: CgNotNullVariable) - // Method parameters abstract override fun visit(element: CgParameterDeclaration) 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 dd6882690c..90b60d72d0 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 @@ -22,7 +22,7 @@ import org.utbot.framework.codegen.model.tree.CgGetKotlinClass import org.utbot.framework.codegen.model.tree.CgGetLength import org.utbot.framework.codegen.model.tree.CgInnerBlock import org.utbot.framework.codegen.model.tree.CgMethod -import org.utbot.framework.codegen.model.tree.CgNotNullVariable +import org.utbot.framework.codegen.model.tree.CgNotNullAssertion import org.utbot.framework.codegen.model.tree.CgParameterDeclaration import org.utbot.framework.codegen.model.tree.CgParameterizedTestDataProviderMethod import org.utbot.framework.codegen.model.tree.CgReturnStatement @@ -127,6 +127,12 @@ internal class CgJavaRenderer(context: CgContext, printer: CgPrinter = CgPrinter element.expression.accept(this) } + // Not-null assertion + + override fun visit(element: CgNotNullAssertion) { + element.expression.accept(this) + } + override fun visit(element: CgParameterDeclaration) { if (element.isVararg) { print(element.type.elementClassId!!.asString()) @@ -150,10 +156,6 @@ internal class CgJavaRenderer(context: CgContext, printer: CgPrinter = CgPrinter error("KClass attempted to be used in the Java test class") } - override fun visit(element: CgNotNullVariable) { - print(element.name.escapeNamePossibleKeyword()) - } - override fun visit(element: CgAllocateArray) { // TODO: Arsen strongly required to rewrite later val typeName = element.type.canonicalName.substringBefore("[") 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 c5fe8c3bd0..28f5626f16 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 @@ -27,7 +27,7 @@ import org.utbot.framework.codegen.model.tree.CgGetKotlinClass import org.utbot.framework.codegen.model.tree.CgGetLength import org.utbot.framework.codegen.model.tree.CgInnerBlock import org.utbot.framework.codegen.model.tree.CgMethod -import org.utbot.framework.codegen.model.tree.CgNotNullVariable +import org.utbot.framework.codegen.model.tree.CgNotNullAssertion import org.utbot.framework.codegen.model.tree.CgParameterDeclaration import org.utbot.framework.codegen.model.tree.CgParameterizedTestDataProviderMethod import org.utbot.framework.codegen.model.tree.CgSpread @@ -231,8 +231,9 @@ internal class CgKotlinRenderer(context: CgContext, printer: CgPrinter = CgPrint print("::class") } - override fun visit(element: CgNotNullVariable) { - print("${element.name.escapeNamePossibleKeyword()}!!") + override fun visit(element: CgNotNullAssertion) { + element.expression.accept(this) + print("!!") } override fun visit(element: CgAllocateArray) { 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 23cf060706..db1146154f 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 @@ -52,7 +52,7 @@ import org.utbot.framework.codegen.model.tree.CgMultilineComment import org.utbot.framework.codegen.model.tree.CgMultipleArgsAnnotation import org.utbot.framework.codegen.model.tree.CgNamedAnnotationArgument import org.utbot.framework.codegen.model.tree.CgNonStaticRunnable -import org.utbot.framework.codegen.model.tree.CgNotNullVariable +import org.utbot.framework.codegen.model.tree.CgNotNullAssertion import org.utbot.framework.codegen.model.tree.CgParameterDeclaration import org.utbot.framework.codegen.model.tree.CgParameterizedTestDataProviderMethod import org.utbot.framework.codegen.model.tree.CgReturnStatement @@ -184,7 +184,10 @@ interface CgVisitor { // Variables fun visit(element: CgVariable): R - fun visit(element: CgNotNullVariable): R + + // Not-null assertion + + fun visit(element: CgNotNullAssertion): R // Method parameters fun visit(element: CgParameterDeclaration): R diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/UtValueTestCaseChecker.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/UtValueTestCaseChecker.kt index 43957a73cf..bbb28788aa 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/UtValueTestCaseChecker.kt +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/UtValueTestCaseChecker.kt @@ -2550,7 +2550,7 @@ abstract class UtValueTestCaseChecker( val substituteStatics: Boolean ) - data class MethodResult(val testCase: UtMethodTestSet, val coverage: Coverage) + data class MethodResult(val testSet: UtMethodTestSet, val coverage: Coverage) } @Suppress("UNCHECKED_CAST") diff --git a/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/GenerateTestsAndSarifReportTask.kt b/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/GenerateTestsAndSarifReportTask.kt index 9c2f3b0eb2..c21a3555ef 100644 --- a/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/GenerateTestsAndSarifReportTask.kt +++ b/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/GenerateTestsAndSarifReportTask.kt @@ -46,6 +46,7 @@ open class GenerateTestsAndSarifReportTask @Inject constructor( return } try { + generateForProjectRecursively(rootGradleProject) GenerateTestsAndSarifReportFacade.mergeReports( sarifReports = rootGradleProject.collectReportsRecursively(),