diff --git a/utbot-framework-api/build.gradle b/utbot-framework-api/build.gradle index d922375eef..9a71cda4a1 100644 --- a/utbot-framework-api/build.gradle +++ b/utbot-framework-api/build.gradle @@ -14,6 +14,12 @@ dependencies { implementation group: 'io.github.microutils', name: 'kotlin-logging', version: kotlin_logging_version } +compileKotlin { + kotlinOptions { + freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn" + } +} + shadowJar { configurations = [project.configurations.compileClasspath] archiveClassifier.set('') 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 04317cd909..9b719c8d6e 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 @@ -51,6 +51,8 @@ import soot.jimple.JimpleBody import soot.jimple.Stmt import java.io.File import java.lang.reflect.Modifier +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract import kotlin.jvm.internal.CallableReference import kotlin.reflect.KCallable import kotlin.reflect.KClass @@ -59,6 +61,8 @@ import kotlin.reflect.full.instanceParameter import kotlin.reflect.jvm.javaConstructor import kotlin.reflect.jvm.javaType +const val SYMBOLIC_NULL_ADDR: Int = 0 + data class UtMethod( val callable: KCallable, val clazz: KClass<*> @@ -267,6 +271,27 @@ fun UtModel.hasDefaultValue() = */ fun UtModel.isMockModel() = this is UtCompositeModel && isMock +/** + * Get model id (symbolic null value for UtNullModel) + * or null if model has no id (e.g., a primitive model) or the id is null. + */ +fun UtModel.idOrNull(): Int? = when (this) { + is UtNullModel -> SYMBOLIC_NULL_ADDR + is UtReferenceModel -> id + else -> null +} + +/** + * Returns the model id if it is available, or throws an [IllegalStateException]. + */ +@OptIn(ExperimentalContracts::class) +fun UtModel?.getIdOrThrow(): Int { + contract { + returns() implies (this@getIdOrThrow != null) + } + return this?.idOrNull() ?: throw IllegalStateException("Model id must not be null: $this") +} + /** * Model for nulls. */ @@ -308,20 +333,24 @@ object UtVoidModel : UtModel(voidClassId) * Model for enum constant */ data class UtEnumConstantModel( + override val id: Int?, override val classId: ClassId, val value: Enum<*> -) : UtModel(classId) { - override fun toString(): String = "$value" +) : UtReferenceModel(id, classId) { + // Model id is included for debugging purposes + override fun toString(): String = "$value@$id" } /** * Model for class reference */ data class UtClassRefModel( + override val id: Int?, override val classId: ClassId, val value: Class<*> -) : UtModel(classId) { - override fun toString(): String = "$value" +) : UtReferenceModel(id, classId) { + // Model id is included for debugging purposes + override fun toString(): String = "$value@$id" } /** 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 29e4cc8995..a233887008 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 @@ -117,6 +117,9 @@ val ClassId.isFloatType: Boolean val ClassId.isDoubleType: Boolean get() = this == doubleClassId || this == doubleWrapperClassId +val ClassId.isClassType: Boolean + get() = this == classClassId + val voidClassId = ClassId("void") val booleanClassId = ClassId("boolean") val byteClassId = ClassId("byte") @@ -138,6 +141,8 @@ val longWrapperClassId = java.lang.Long::class.id val floatWrapperClassId = java.lang.Float::class.id val doubleWrapperClassId = java.lang.Double::class.id +val classClassId = java.lang.Class::class.id + // We consider void wrapper as primitive wrapper // because voidClassId is considered primitive here val primitiveWrappers = setOf( @@ -285,6 +290,9 @@ val ClassId.isMap: Boolean val ClassId.isIterableOrMap: Boolean get() = isIterable || isMap +val ClassId.isEnum: Boolean + get() = jClass.isEnum + fun ClassId.findFieldByIdOrNull(fieldId: FieldId): Field? { if (isNotSubtypeOf(fieldId.declaringClass)) { return null diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt index c5f3cde9e9..6671f25ac2 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt @@ -24,7 +24,8 @@ import org.utbot.framework.plugin.api.UtCompositeModel 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.UtReferenceModel +import org.utbot.framework.plugin.api.getIdOrThrow +import org.utbot.framework.plugin.api.idOrNull import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.objectArrayClassId import org.utbot.framework.plugin.api.util.objectClassId @@ -398,7 +399,7 @@ class AssociativeArrayWrapper : WrapperInterface { UtNullModel(objectClassId), stores = (0 until sizeValue).associateTo(mutableMapOf()) { i -> val model = touchedValues.stores[i] - val addr = if (model is UtNullModel) 0 else (model as UtReferenceModel).id!! + val addr = model.getIdOrThrow() addr to resolver.resolveModel( ObjectValue( TypeStorage(OBJECT_TYPE), diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionWrappers.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionWrappers.kt index e5c6558558..2e2039dddc 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionWrappers.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionWrappers.kt @@ -22,9 +22,9 @@ import org.utbot.framework.plugin.api.UtCompositeModel import org.utbot.framework.plugin.api.UtExecutableCallModel import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtNullModel -import org.utbot.framework.plugin.api.UtReferenceModel import org.utbot.framework.plugin.api.UtStatementModel import org.utbot.framework.plugin.api.classId +import org.utbot.framework.plugin.api.getIdOrThrow import org.utbot.framework.util.graph import org.utbot.framework.plugin.api.id import org.utbot.framework.plugin.api.util.booleanClassId @@ -373,7 +373,7 @@ private fun constructKeysAndValues(keysModel: UtModel, valuesModel: UtModel, siz keysModel is UtArrayModel && valuesModel is UtArrayModel -> { List(size) { keysModel.stores[it].let { model -> - val addr = if (model is UtNullModel) 0 else (model as UtReferenceModel).id + val addr = model.getIdOrThrow() // as we do not support generics for now, valuesModel.classId.elementClassId is unknown, // but it can be known with generics support val defaultValue = UtNullModel(valuesModel.classId.elementClassId ?: objectClassId) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt index 549d02f16b..6f06ac74a7 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt @@ -56,6 +56,7 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentSetOf import kotlinx.collections.immutable.toPersistentList import kotlinx.collections.immutable.toPersistentMap +import org.utbot.framework.plugin.api.classId import soot.ArrayType import soot.BooleanType import soot.ByteType @@ -147,7 +148,8 @@ data class Memory( // TODO: split purely symbolic memory and information about s private val speculativelyNotNullAddresses: UtArrayExpressionBase = UtConstArrayExpression( UtFalse, UtArraySort(UtAddrSort, UtBoolSort) - ) + ), + private val symbolicEnumValues: PersistentList = persistentListOf() ) { val chunkIds: Set get() = initial.keys @@ -297,7 +299,8 @@ data class Memory( // TODO: split purely symbolic memory and information about s visitedValues = updVisitedValues, touchedAddresses = updTouchedAddresses, instanceFieldReadOperations = instanceFieldReadOperations.addAll(update.instanceFieldReads), - speculativelyNotNullAddresses = updSpeculativelyNotNullAddresses + speculativelyNotNullAddresses = updSpeculativelyNotNullAddresses, + symbolicEnumValues = symbolicEnumValues.addAll(update.symbolicEnumValues) ) } @@ -307,7 +310,6 @@ data class Memory( // TODO: split purely symbolic memory and information about s */ fun memoryForNestedMethod(): Memory = this.copy(updates = MemoryUpdate()) - /** * Returns copy of queued [updates] which consists only of updates of static fields. * This is necessary for substituting unbounded symbolic variables into the static fields. @@ -350,6 +352,9 @@ data class Memory( // TODO: split purely symbolic memory and information about s fun findStaticInstanceOrNull(id: ClassId): ObjectValue? = staticInstanceStorage[id] fun findTypeForArrayOrNull(addr: UtAddrExpression): ArrayType? = addrToArrayType[addr] + + fun getSymbolicEnumValues(classId: ClassId): List = + symbolicEnumValues.filter { it.type.classId == classId } } /** @@ -967,7 +972,8 @@ data class MemoryUpdate( val touchedAddresses: PersistentList = persistentListOf(), val classIdToClearStatics: ClassId? = null, val instanceFieldReads: PersistentSet = persistentHashSetOf(), - val speculativelyNotNullAddresses: PersistentList = persistentListOf() + val speculativelyNotNullAddresses: PersistentList = persistentListOf(), + val symbolicEnumValues: PersistentList = persistentListOf() ) { operator fun plus(other: MemoryUpdate) = this.copy( @@ -986,7 +992,11 @@ data class MemoryUpdate( classIdToClearStatics = other.classIdToClearStatics, instanceFieldReads = instanceFieldReads.addAll(other.instanceFieldReads), speculativelyNotNullAddresses = speculativelyNotNullAddresses.addAll(other.speculativelyNotNullAddresses), + symbolicEnumValues = symbolicEnumValues.addAll(other.symbolicEnumValues), ) + + fun getSymbolicEnumValues(classId: ClassId): List = + symbolicEnumValues.filter { it.type.classId == classId } } // array - Java Array diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt index 93a1eba650..8729faba80 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt @@ -71,6 +71,7 @@ import kotlin.math.max import kotlin.math.min import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentSetOf +import org.utbot.framework.plugin.api.SYMBOLIC_NULL_ADDR import soot.ArrayType import soot.BooleanType import soot.ByteType @@ -325,7 +326,7 @@ class Resolver( val mockInfoEnriched = mockInfos.getValue(concreteAddr) val mockInfo = mockInfoEnriched.mockInfo - if (concreteAddr == NULL_ADDR) { + if (concreteAddr == SYMBOLIC_NULL_ADDR) { return UtNullModel(mockInfo.classId) } @@ -437,7 +438,7 @@ class Resolver( private fun resolveObject(objectValue: ObjectValue): UtModel { val concreteAddr = holder.concreteAddr(objectValue.addr) - if (concreteAddr == NULL_ADDR) { + if (concreteAddr == SYMBOLIC_NULL_ADDR) { return UtNullModel(objectValue.type.sootClass.id) } @@ -498,7 +499,7 @@ class Resolver( actualType: RefType, ): UtModel { val concreteAddr = holder.concreteAddr(addr) - if (concreteAddr == NULL_ADDR) { + if (concreteAddr == SYMBOLIC_NULL_ADDR) { return UtNullModel(defaultType.sootClass.id) } @@ -615,7 +616,7 @@ class Resolver( val modeledNumDimensions = holder.eval(numDimensionsArray.select(addrExpression)).intValue() val classRef = classRefByName(modeledType, modeledNumDimensions) - val model = UtClassRefModel(CLASS_REF_CLASS_ID, classRef) + val model = UtClassRefModel(addr, CLASS_REF_CLASS_ID, classRef) addConstructedModel(addr, model) return model @@ -640,7 +641,7 @@ class Resolver( clazz.enumConstants.indices.random() } val value = clazz.enumConstants[index] as Enum<*> - val model = UtEnumConstantModel(clazz.id, value) + val model = UtEnumConstantModel(addr, clazz.id, value) addConstructedModel(addr, model) return model @@ -795,7 +796,7 @@ class Resolver( */ private fun constructArrayModel(instance: ArrayValue): UtModel { val concreteAddr = holder.concreteAddr(instance.addr) - if (concreteAddr == NULL_ADDR) { + if (concreteAddr == SYMBOLIC_NULL_ADDR) { return UtNullModel(instance.type.id) } @@ -829,7 +830,7 @@ class Resolver( concreteAddr: Address, details: ArrayExtractionDetails, ): UtModel { - if (concreteAddr == NULL_ADDR) { + if (concreteAddr == SYMBOLIC_NULL_ADDR) { return UtNullModel(actualType.id) } @@ -903,7 +904,7 @@ class Resolver( elementType: ArrayType, details: ArrayExtractionDetails ): UtModel { - if (addr == NULL_ADDR) { + if (addr == SYMBOLIC_NULL_ADDR) { return UtNullModel(elementType.id) } @@ -927,7 +928,7 @@ class Resolver( * Uses [constructTypeOrNull] to evaluate possible element type. */ private fun arrayOfObjectsElementModel(concreteAddr: Address, defaultType: RefType): UtModel { - if (concreteAddr == NULL_ADDR) { + if (concreteAddr == SYMBOLIC_NULL_ADDR) { return UtNullModel(defaultType.id) } @@ -976,8 +977,7 @@ private data class ArrayExtractionDetails( val oneDimensionalArray: UtArrayExpressionBase ) -private const val NULL_ADDR = 0 -internal val nullObjectAddr = UtAddrExpression(mkInt(NULL_ADDR)) +internal val nullObjectAddr = UtAddrExpression(mkInt(SYMBOLIC_NULL_ADDR)) fun SymbolicValue.isNullObject() = diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt index 74c8d7b624..3712db126b 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt @@ -497,7 +497,7 @@ class Traverser( val declaringClass = field.declaringClass val updates = if (declaringClass.isEnum) { - makeConcreteUpdatesForEnums(fieldId, declaringClass, stmt) + makeConcreteUpdatesForEnumsWithStmt(fieldId, declaringClass, stmt) } else { makeConcreteUpdatesForNonEnumStaticField(field, fieldId, declaringClass, stmt) } @@ -518,13 +518,10 @@ class Traverser( return true } - @Suppress("UnnecessaryVariable") - private fun makeConcreteUpdatesForEnums( - fieldId: FieldId, - declaringClass: SootClass, - stmt: Stmt - ): SymbolicStateUpdate { - val type = declaringClass.type + private fun makeConcreteUpdatesForEnum( + type: RefType, + fieldId: FieldId? = null + ): Pair { val jClass = type.id.jClass // symbolic value for enum class itself @@ -545,7 +542,7 @@ class Traverser( val (staticFieldUpdates, curFieldSymbolicValueForLocalVariable) = makeEnumStaticFieldsUpdates( staticFields, - declaringClass, + type.sootClass, enumConstantSymbolicResultsByName, enumConstantSymbolicValues, enumClassValue, @@ -564,14 +561,25 @@ class Traverser( val initializedStaticFieldsMemoryUpdate = MemoryUpdate( initializedStaticFields = staticFields.associate { it.first.fieldId to it.second.single() }.toPersistentMap(), - meaningfulStaticFields = meaningfulStaticFields.map { it.first.fieldId }.toPersistentSet() + meaningfulStaticFields = meaningfulStaticFields.map { it.first.fieldId }.toPersistentSet(), + symbolicEnumValues = enumConstantSymbolicValues.toPersistentList() ) - val allUpdates = staticFieldUpdates + - nonStaticFieldsUpdates + - initializedStaticFieldsMemoryUpdate + - createConcreteLocalValueUpdate(stmt, curFieldSymbolicValueForLocalVariable) + return Pair( + staticFieldUpdates + nonStaticFieldsUpdates + initializedStaticFieldsMemoryUpdate, + curFieldSymbolicValueForLocalVariable + ) + } + @Suppress("UnnecessaryVariable") + private fun makeConcreteUpdatesForEnumsWithStmt( + fieldId: FieldId, + declaringClass: SootClass, + stmt: Stmt + ): SymbolicStateUpdate { + val (enumUpdates, curFieldSymbolicValueForLocalVariable) = + makeConcreteUpdatesForEnum(declaringClass.type, fieldId) + val allUpdates = enumUpdates + createConcreteLocalValueUpdate(stmt, curFieldSymbolicValueForLocalVariable) return allUpdates } @@ -1381,6 +1389,39 @@ class Traverser( queuedSymbolicStateUpdates = queuedSymbolicStateUpdates.copy(memoryUpdates = MemoryUpdate()) } + /** + * Return a symbolic value of the ordinal corresponding to the enum value with the given address. + */ + private fun findEnumOrdinal(type: RefType, addr: UtAddrExpression): PrimitiveValue { + val array = memory.findArray(MemoryChunkDescriptor(ENUM_ORDINAL, type, IntType.v())) + return array.select(addr).toIntValue() + } + + /** + * Initialize enum class: create symbolic values for static enum values and generate constraints + * that restrict the new instance to match one of enum values. + */ + private fun initEnum(type: RefType, addr: UtAddrExpression, ordinal: PrimitiveValue) { + val classId = type.id + var predefinedEnumValues = memory.getSymbolicEnumValues(classId) + if (predefinedEnumValues.isEmpty()) { + val (enumValuesUpdate, _) = makeConcreteUpdatesForEnum(type) + queuedSymbolicStateUpdates += enumValuesUpdate + predefinedEnumValues = enumValuesUpdate.memoryUpdates.getSymbolicEnumValues(classId) + } + + val enumValueConstraints = mkOr( + listOf(addrEq(addr, nullObjectAddr)) + predefinedEnumValues.map { + mkAnd( + addrEq(addr, it.addr), + mkEq(ordinal, findEnumOrdinal(it.type, it.addr)) + ) + } + ) + + queuedSymbolicStateUpdates += enumValueConstraints.asHardConstraint() + } + private fun arrayInstanceOf(value: ArrayValue, checkType: Type): PrimitiveValue { val notNullConstraint = mkNot(addrEq(value.addr, nullObjectAddr)) @@ -1538,6 +1579,11 @@ class Traverser( queuedSymbolicStateUpdates += typeConstraint.asHardConstraint() queuedSymbolicStateUpdates += typeRegistry.zeroDimensionConstraint(objectValue.addr).asHardConstraint() + // If we are casting to an enum class, we should initialize enum values and add value equality constraints + if (typeAfterCast.sootClass?.isEnum == true) { + initEnum(typeAfterCast, castedObject.addr, findEnumOrdinal(typeAfterCast, castedObject.addr)) + } + // TODO add memory constraints JIRA:1523 return castedObject } @@ -1986,13 +2032,13 @@ class Traverser( queuedSymbolicStateUpdates += typeRegistry.typeConstraint(addr, typeStorage).all().asHardConstraint() - val array = memory.findArray(MemoryChunkDescriptor(ENUM_ORDINAL, type, IntType.v())) - val ordinal = array.select(addr).toIntValue() + val ordinal = findEnumOrdinal(type, addr) val enumSize = classLoader.loadClass(type.sootClass.name).enumConstants.size queuedSymbolicStateUpdates += mkOr(Ge(ordinal, 0), addrEq(addr, nullObjectAddr)).asHardConstraint() queuedSymbolicStateUpdates += mkOr(Lt(ordinal, enumSize), addrEq(addr, nullObjectAddr)).asHardConstraint() + initEnum(type, addr, ordinal) touchAddress(addr) return ObjectValue(typeStorage, addr) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt index 54c553b3f1..8762330a3b 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt @@ -82,6 +82,7 @@ import org.utbot.framework.plugin.api.util.utContext import org.utbot.framework.plugin.api.util.description import org.utbot.framework.util.jimpleBody import org.utbot.framework.plugin.api.util.voidClassId +import org.utbot.fuzzer.ReferencePreservingIntIdGenerator import org.utbot.fuzzer.FallbackModelProvider import org.utbot.fuzzer.FuzzedMethodDescription import org.utbot.fuzzer.FuzzedValue @@ -114,8 +115,7 @@ class EngineController { //for debugging purpose only private var stateSelectedCount = 0 -//all id values of synthetic default models must be greater that for real ones -private var nextDefaultModelId = 1500_000_000 +private val defaultIdGenerator = ReferencePreservingIntIdGenerator() private fun pathSelector(graph: InterProceduralUnitGraph, typeRegistry: TypeRegistry) = when (pathSelectorType) { @@ -391,7 +391,7 @@ class UtBotSymbolicEngine( return@flow } - val fallbackModelProvider = FallbackModelProvider { nextDefaultModelId++ } + val fallbackModelProvider = FallbackModelProvider(defaultIdGenerator) val constantValues = collectConstantsForFuzzer(graph) val thisInstance = when { @@ -405,7 +405,7 @@ class UtBotSymbolicEngine( null } else -> { - ObjectModelProvider { nextDefaultModelId++ }.withFallback(fallbackModelProvider).generate( + ObjectModelProvider(ReferencePreservingIntIdGenerator()).withFallback(fallbackModelProvider).generate( FuzzedMethodDescription("thisInstance", voidClassId, listOf(methodUnderTest.clazz.id), constantValues) ).take(10).shuffled(Random(0)).map { it.value.model }.first().apply { if (this is UtNullModel) { // it will definitely fail because of NPE, @@ -427,14 +427,14 @@ class UtBotSymbolicEngine( var attempts = UtSettings.fuzzingMaxAttempts val hasMethodUnderTestParametersToFuzz = executableId.parameters.isNotEmpty() val fuzzedValues = if (hasMethodUnderTestParametersToFuzz) { - fuzz(methodUnderTestDescription, modelProvider(defaultModelProviders { nextDefaultModelId++ })) + fuzz(methodUnderTestDescription, modelProvider(defaultModelProviders(defaultIdGenerator))) } else { // in case a method with no parameters is passed fuzzing tries to fuzz this instance with different constructors, setters and field mutators val thisMethodDescription = FuzzedMethodDescription("thisInstance", voidClassId, listOf(methodUnderTest.clazz.id), constantValues).apply { className = executableId.classId.simpleName packageName = executableId.classId.packageName } - fuzz(thisMethodDescription, ObjectModelProvider { nextDefaultModelId++ }.apply { + fuzz(thisMethodDescription, ObjectModelProvider(defaultIdGenerator).apply { limitValuesCreatedByFieldAccessors = 500 }) } diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/util/statics/concrete/EnumConcreteUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/util/statics/concrete/EnumConcreteUtils.kt index 96329b4c01..f4ca0a88be 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/util/statics/concrete/EnumConcreteUtils.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/util/statics/concrete/EnumConcreteUtils.kt @@ -68,7 +68,7 @@ fun Traverser.makeEnumStaticFieldsUpdates( enumConstantSymbolicResultsByName: Map, enumConstantSymbolicValues: List, enumClassValue: ObjectValue, - fieldId: FieldId + fieldId: FieldId? ): Pair { var staticFieldsUpdates = SymbolicStateUpdate() var symbolicValueForLocal: SymbolicValue? = null @@ -101,7 +101,7 @@ fun Traverser.makeEnumStaticFieldsUpdates( } // save value to associate it with local if required - if (sootStaticField.name == fieldId.name) { + if (fieldId != null && sootStaticField.name == fieldId.name) { symbolicValueForLocal = fieldSymbolicValue } } diff --git a/utbot-framework/src/main/kotlin/org/utbot/external/api/UtModelFactory.kt b/utbot-framework/src/main/kotlin/org/utbot/external/api/UtModelFactory.kt index 2f0c10ce54..35f2756e94 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/external/api/UtModelFactory.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/external/api/UtModelFactory.kt @@ -67,8 +67,10 @@ class UtModelFactory( elements.toMutableMap() ) - fun produceClassRefModel(clazz: Class): UtModel = UtClassRefModel( - classIdForType(clazz), clazz + fun produceClassRefModel(clazz: Class<*>) = UtClassRefModel( + modelIdCounter.incrementAndGet(), + classIdForType(clazz), + clazz ) } 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 0f13a1a958..baa62c3890 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 @@ -101,13 +101,13 @@ internal class CgVariableConstructor(val context: CgContext) : is UtCompositeModel -> constructComposite(model, baseName) is UtAssembleModel -> constructAssemble(model, baseName) is UtArrayModel -> constructArray(model, baseName) + is UtEnumConstantModel -> constructEnumConstant(model, baseName) + is UtClassRefModel -> constructClassRef(model, baseName) } } else valueByModel.getOrPut(model) { when (model) { is UtNullModel -> nullLiteral() is UtPrimitiveModel -> CgLiteral(model.classId, model.value) - is UtEnumConstantModel -> constructEnumConstant(model, baseName) - is UtClassRefModel -> constructClassRef(model, baseName) is UtReferenceModel -> error("Unexpected UtReferenceModel: ${model::class}") is UtVoidModel -> error("Unexpected UtVoidModel: ${model::class}") } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/MockValueConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/MockValueConstructor.kt index 170f9c743b..eca3609bd9 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/MockValueConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/MockValueConstructor.kt @@ -121,7 +121,7 @@ class MockValueConstructor( when (model) { is UtNullModel -> UtConcreteValue(null, model.classId.jClass) is UtPrimitiveModel -> UtConcreteValue(model.value, model.classId.jClass) - is UtEnumConstantModel -> UtConcreteValue(model.value) + is UtEnumConstantModel -> UtConcreteValue(constructEnum(model)) is UtClassRefModel -> UtConcreteValue(model.value) is UtCompositeModel -> UtConcreteValue(constructObject(model), model.classId.jClass) is UtArrayModel -> UtConcreteValue(constructArray(model)) @@ -130,6 +130,15 @@ class MockValueConstructor( } } + /** + * Constructs an Enum<*> instance by model, uses reference-equality cache. + */ + private fun constructEnum(model: UtEnumConstantModel): Any { + constructedObjects[model]?.let { return it } + constructedObjects[model] = model.value + return model.value + } + /** * Constructs object by model, uses reference-equality cache. * diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtExecutionInstrumentation.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtExecutionInstrumentation.kt index 6a7ca07ec2..09ac24c414 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtExecutionInstrumentation.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtExecutionInstrumentation.kt @@ -178,7 +178,16 @@ object UtExecutionInstrumentation : Instrumentation { val stateAfterParametersWithThis = params.map { construct(it.value, it.clazz.id) } val stateAfterStatics = (staticFields.keys/* + traceHandler.computePutStatics()*/) .associateWith { fieldId -> - fieldId.jField.run { construct(withAccessibility { get(null) }, fieldId.type) } + fieldId.jField.run { + val computedValue = withAccessibility { get(null) } + val knownModel = stateBefore.statics[fieldId] + val knownValue = staticFields[fieldId] + if (knownModel != null && knownValue != null && knownValue == computedValue) { + knownModel + } else { + construct(computedValue, fieldId.type) + } + } } val (stateAfterThis, stateAfterParameters) = if (stateBefore.thisInstance == null) { null to stateAfterParametersWithThis diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtModelConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtModelConstructor.kt index 7a8c4ff2f4..2d18f00654 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtModelConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtModelConstructor.kt @@ -218,14 +218,14 @@ internal class UtModelConstructor( private fun constructFromEnum(enum: Enum<*>): UtModel = constructedObjects.getOrElse(enum) { - val utModel = UtEnumConstantModel(enum::class.java.id, enum) + val utModel = UtEnumConstantModel(handleId(enum), enum::class.java.id, enum) constructedObjects[enum] = utModel utModel } private fun constructFromClass(clazz: Class<*>): UtModel = constructedObjects.getOrElse(clazz) { - val utModel = UtClassRefModel(clazz::class.java.id, clazz) + val utModel = UtClassRefModel(handleId(clazz), clazz::class.java.id, clazz) System.err.println("ClassRef: $clazz \t\tClassloader: ${clazz.classLoader}") constructedObjects[clazz] = utModel utModel diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/fields/ExecutionStateAnalyzer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/fields/ExecutionStateAnalyzer.kt index 64d6424f7b..d5ea151257 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/fields/ExecutionStateAnalyzer.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/fields/ExecutionStateAnalyzer.kt @@ -160,23 +160,23 @@ private class FieldStateVisitor : UtModelVisitor() { ) } - override fun visit(element: UtClassRefModel, data: FieldData) { + override fun visit(element: UtNullModel, data: FieldData) { recordFieldState(data, element) } - override fun visit(element: UtEnumConstantModel, data: FieldData) { + override fun visit(element: UtPrimitiveModel, data: FieldData) { recordFieldState(data, element) } - override fun visit(element: UtNullModel, data: FieldData) { + override fun visit(element: UtVoidModel, data: FieldData) { recordFieldState(data, element) } - override fun visit(element: UtPrimitiveModel, data: FieldData) { + override fun visit(element: UtClassRefModel, data: FieldData) { recordFieldState(data, element) } - override fun visit(element: UtVoidModel, data: FieldData) { + override fun visit(element: UtEnumConstantModel, data: FieldData) { recordFieldState(data, element) } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/util/UtModelVisitor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/util/UtModelVisitor.kt index b6d91a8ede..fd043ad3b2 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/util/UtModelVisitor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/util/UtModelVisitor.kt @@ -21,8 +21,6 @@ abstract class UtModelVisitor { abstract fun visit(element: UtModel, data: D) - abstract fun visit(element: UtClassRefModel, data: D) - abstract fun visit(element: UtEnumConstantModel, data: D) abstract fun visit(element: UtNullModel, data: D) abstract fun visit(element: UtPrimitiveModel, data: D) abstract fun visit(element: UtVoidModel, data: D) @@ -30,12 +28,16 @@ abstract class UtModelVisitor { open fun visit(element: UtReferenceModel, data: D) { if (!canTraverseReferenceModel(element)) return when (element) { + is UtClassRefModel -> visit(element, data) + is UtEnumConstantModel -> visit(element, data) is UtArrayModel -> visit(element, data) is UtAssembleModel -> visit(element, data) is UtCompositeModel -> visit(element, data) } } + abstract fun visit(element: UtClassRefModel, data: D) + abstract fun visit(element: UtEnumConstantModel, data: D) protected abstract fun visit(element: UtArrayModel, data: D) protected abstract fun visit(element: UtAssembleModel, data: D) protected abstract fun visit(element: UtCompositeModel, data: D) diff --git a/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FallbackModelProvider.kt b/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FallbackModelProvider.kt index 89f9c21aaf..83623de5b8 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FallbackModelProvider.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FallbackModelProvider.kt @@ -14,6 +14,8 @@ import org.utbot.framework.plugin.api.util.defaultValueModel import org.utbot.framework.plugin.api.util.executableId import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.isArray +import org.utbot.framework.plugin.api.util.isClassType +import org.utbot.framework.plugin.api.util.isEnum import org.utbot.framework.plugin.api.util.isIterable import org.utbot.framework.plugin.api.util.isPrimitive import org.utbot.framework.plugin.api.util.jClass @@ -31,7 +33,7 @@ import kotlin.reflect.KClass * Used as a fallback implementation until other providers cover every type. */ open class FallbackModelProvider( - private val idGenerator: IntSupplier + private val idGenerator: IdGenerator ): AbstractModelProvider() { override fun toModel(classId: ClassId): UtModel { @@ -46,11 +48,11 @@ open class FallbackModelProvider( it.parameters.isEmpty() && it.isPublic } return when { - classId.isPrimitive -> + classId.isPrimitive || classId.isEnum || classId.isClassType -> classId.defaultValueModel() classId.isArray -> UtArrayModel( - id = idGenerator.asInt, + id = idGenerator.createId(), classId, length = 0, classId.elementClassId!!.defaultValueModel(), @@ -81,26 +83,36 @@ open class FallbackModelProvider( val defaultConstructor = kclass.java.constructors.firstOrNull { it.parameters.isEmpty() && it.isPublic // check constructor is public } - return if (kclass.isAbstract) { // sealed class is abstract by itself - UtNullModel(kclass.java.id) - } else if (defaultConstructor != null) { - val chain = mutableListOf() - val model = UtAssembleModel( - id = idGenerator.asInt, - kclass.id, - kclass.id.toString(), - chain - ) - chain.add( - UtExecutableCallModel(model, defaultConstructor.executableId, listOf(), model) - ) - model - } else { - UtCompositeModel( - id = idGenerator.asInt, - kclass.id, - isMock = false - ) + return when { + kclass.isAbstract -> { + // sealed class is abstract by itself + UtNullModel(kclass.java.id) + + } + kclass.java.isEnum || kclass == java.lang.Class::class -> { + // No sensible fallback solution for these classes except returning default `null` value + UtNullModel(kclass.java.id) + } + defaultConstructor != null -> { + val chain = mutableListOf() + val model = UtAssembleModel( + id = idGenerator.createId(), + kclass.id, + kclass.id.toString(), + chain + ) + chain.add( + UtExecutableCallModel(model, defaultConstructor.executableId, listOf(), model) + ) + model + } + else -> { + UtCompositeModel( + id = idGenerator.createId(), + kclass.id, + isMock = false + ) + } } } } diff --git a/utbot-framework/src/test/java/org/utbot/examples/manual/UtBotJavaApiTest.java b/utbot-framework/src/test/java/org/utbot/examples/manual/UtBotJavaApiTest.java index d21fac1a7a..b55375531d 100644 --- a/utbot-framework/src/test/java/org/utbot/examples/manual/UtBotJavaApiTest.java +++ b/utbot-framework/src/test/java/org/utbot/examples/manual/UtBotJavaApiTest.java @@ -204,9 +204,7 @@ public void testCustomPackage() { fields ); - UtClassRefModel classRefModel = new UtClassRefModel( - classIdForType(Class.class), Class.class - ); + UtClassRefModel classRefModel = modelFactory.produceClassRefModel(Class.class); EnvironmentModels initialState = new EnvironmentModels( classUnderTestModel, @@ -389,9 +387,7 @@ public void testClassRef() { fields ); - UtClassRefModel classRefModel = new UtClassRefModel( - classIdForType(Class.class), Class.class - ); + UtClassRefModel classRefModel = modelFactory.produceClassRefModel(Class.class); EnvironmentModels initialState = new EnvironmentModels( classUnderTestModel, diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/enums/ComplexEnumExamplesTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/enums/ComplexEnumExamplesTest.kt new file mode 100644 index 0000000000..d67c6d83d0 --- /dev/null +++ b/utbot-framework/src/test/kotlin/org/utbot/examples/enums/ComplexEnumExamplesTest.kt @@ -0,0 +1,109 @@ +package org.utbot.examples.enums + +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.utbot.examples.UtValueTestCaseChecker +import org.utbot.examples.enums.ComplexEnumExamples.Color +import org.utbot.examples.enums.ComplexEnumExamples.Color.BLUE +import org.utbot.examples.enums.ComplexEnumExamples.Color.GREEN +import org.utbot.examples.enums.ComplexEnumExamples.Color.RED +import org.utbot.examples.eq +import org.utbot.examples.ignoreExecutionsNumber +import org.utbot.framework.codegen.CodeGeneration +import org.utbot.framework.plugin.api.CodegenLanguage + +class ComplexEnumExamplesTest : UtValueTestCaseChecker( + testClass = ComplexEnumExamples::class, + testCodeGeneration = true, + languagePipelines = listOf( + CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), + CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) + ) +) { + @Test + fun testEnumToEnumMapCountValues() { + check( + ComplexEnumExamples::enumToEnumMapCountValues, + ignoreExecutionsNumber, + { m, r -> m.isEmpty() && r == 0 }, + { m, r -> m.isNotEmpty() && !m.values.contains(RED) && r == 0 }, + { m, r -> m.isNotEmpty() && m.values.contains(RED) && m.values.count { it == RED } == r } + ) + } + + @Test + fun testEnumToEnumMapCountKeys() { + check( + ComplexEnumExamples::enumToEnumMapCountKeys, + ignoreExecutionsNumber, + { m, r -> m.isEmpty() && r == 0 }, + { m, r -> m.isNotEmpty() && !m.keys.contains(GREEN) && !m.keys.contains(BLUE) && r == 0 }, + { m, r -> m.isNotEmpty() && m.keys.intersect(setOf(BLUE, GREEN)).isNotEmpty() && m.keys.count { it == BLUE || it == GREEN } == r } + ) + } + + @Test + fun testEnumToEnumMapCountMatches() { + check( + ComplexEnumExamples::enumToEnumMapCountMatches, + ignoreExecutionsNumber, + { m, r -> m.isEmpty() && r == 0 }, + { m, r -> m.entries.count { it.key == it.value } == r } + ) + } + + @Test + fun testCountEqualColors() { + check( + ComplexEnumExamples::countEqualColors, + ignoreExecutionsNumber, + { a, b, c, r -> a == b && a == c && r == 3 }, + { a, b, c, r -> setOf(a, b, c).size == 2 && r == 2 }, + { a, b, c, r -> a != b && b != c && a != c && r == 1 } + ) + } + + @Test + fun testCountNullColors() { + check( + ComplexEnumExamples::countNullColors, + eq(3), + { a, b, r -> a == null && b == null && r == 2 }, + { a, b, r -> (a == null) != (b == null) && r == 1 }, + { a, b, r -> a != null && b != null && r == 0 }, + ) + } + + @Test + @Disabled("TODO: nested anonymous classes are not supported: https://github.com/UnitTestBot/UTBotJava/issues/617") + fun testFindState() { + check( + ComplexEnumExamples::findState, + ignoreExecutionsNumber, + { c, r -> c in setOf(0, 127, 255) && r != null && r.code == c } + ) + } + + @Test + fun testCountValuesInArray() { + fun Color.isCorrectlyCounted(inputs: Array, counts: Map): Boolean = + inputs.count { it == this } == (counts[this] ?: 0) + + check( + ComplexEnumExamples::countValuesInArray, + ignoreExecutionsNumber, + { cs, r -> cs.isEmpty() && r != null && r.isEmpty() }, + { cs, r -> cs.toList().isEmpty() && r != null && r.isEmpty() }, + { cs, r -> cs.toList().isNotEmpty() && r != null && Color.values().all { it.isCorrectlyCounted(cs, r) } } + ) + } + + @Test + fun testCountRedInArray() { + check( + ComplexEnumExamples::countRedInArray, + eq(3), + { colors, result -> colors.count { it == RED } == result } + ) + } +} diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt index 33f0179095..c0dde7d1c5 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt @@ -12,12 +12,89 @@ import org.utbot.fuzzer.providers.PrimitiveDefaultsModelProvider import org.utbot.fuzzer.providers.EnumModelProvider import org.utbot.fuzzer.providers.PrimitiveWrapperModelProvider import java.lang.IllegalArgumentException +import java.util.IdentityHashMap import java.util.concurrent.atomic.AtomicInteger -import java.util.function.IntSupplier import kotlin.random.Random private val logger by lazy { KotlinLogging.logger {} } +/** + * Identifier generator interface for fuzzer model providers. + * + * Provides fresh identifiers for generated models. + * + * Warning: specific generators are not guaranteed to be thread-safe. + * + * @param Id the identifier type (e.g., [Int] for [UtReferenceModel] providers) + */ +interface IdGenerator { + /** + * Create a fresh identifier. Each subsequent call should return a different value. + * + * The method is not guaranteed to be thread-safe, unless an implementation makes such a guarantee. + */ + fun createId(): Id +} + +/** + * Identity preserving identifier generator interface. + * + * It allows to optionally save identifiers assigned to specific objects and later get the same identifiers + * for these objects instead of fresh identifiers. This feature is necessary, for example, to implement reference + * equality for enum models. + * + * Warning: specific generators are not guaranteed to be thread-safe. + * + * @param Id the identifier type (e.g., [Int] for [UtReferenceModel] providers) + */ +interface IdentityPreservingIdGenerator : IdGenerator { + /** + * Return an identifier for a specified non-null object. If an identifier has already been assigned + * to an object, subsequent calls should return the same identifier for this object. + * + * Note: the interface does not specify whether reference equality or regular `equals`/`compareTo` equality + * will be used to compare objects. Each implementation may provide these guarantees by itself. + * + * The method is not guaranteed to be thread-safe, unless an implementation makes such a guarantee. + */ + fun getOrCreateIdForValue(value: Any): Id +} + +/** + * An identity preserving id generator for fuzzer value providers that returns identifiers of type [Int]. + * + * When identity-preserving identifier is requested, objects are compared by reference. + * The generator is not thread-safe. + * + * @param lowerBound an integer value so that any generated identifier is strictly greater than it. + * + * Warning: when generating [UtReferenceModel] identifiers, no identifier should be equal to zero, + * as this value is reserved for [UtNullModel]. To guarantee it, [lowerBound] should never be negative. + * Avoid using custom lower bounds (maybe except fuzzer unit tests), use the predefined default value instead. + */ +class ReferencePreservingIntIdGenerator(lowerBound: Int = DEFAULT_LOWER_BOUND) : IdentityPreservingIdGenerator { + private val lastId: AtomicInteger = AtomicInteger(lowerBound) + private val cache: IdentityHashMap = IdentityHashMap() + + override fun getOrCreateIdForValue(value: Any): Int { + return cache.getOrPut(value) { createId() } + } + + override fun createId(): Int { + return lastId.incrementAndGet() + } + + companion object { + /** + * The default lower bound (all generated integer identifiers will be greater than it). + * + * It is defined as a large value because all synthetic [UtModel] instances + * must have greater identifiers than the real models. + */ + const val DEFAULT_LOWER_BOUND: Int = 1500_000_000 + } +} + fun fuzz(description: FuzzedMethodDescription, vararg modelProviders: ModelProvider): Sequence> { if (modelProviders.isEmpty()) { throw IllegalArgumentException("At least one model provider is required") @@ -42,12 +119,12 @@ fun fuzz(description: FuzzedMethodDescription, vararg modelProviders: ModelProvi /** * Creates a model provider from a list of default providers. */ -fun defaultModelProviders(idGenerator: IntSupplier = SimpleIdGenerator()): ModelProvider { +fun defaultModelProviders(idGenerator: IdentityPreservingIdGenerator): ModelProvider { return ModelProvider.of( ObjectModelProvider(idGenerator), CollectionModelProvider(idGenerator), ArrayModelProvider(idGenerator), - EnumModelProvider, + EnumModelProvider(idGenerator), ConstantsModelProvider, StringConstantModelProvider, CharToStringModelProvider, @@ -59,11 +136,11 @@ fun defaultModelProviders(idGenerator: IntSupplier = SimpleIdGenerator()): Model /** * Creates a model provider for [ObjectModelProvider] that generates values for object constructor. */ -fun objectModelProviders(idGenerator: IntSupplier = SimpleIdGenerator()): ModelProvider { +fun objectModelProviders(idGenerator: IdentityPreservingIdGenerator): ModelProvider { return ModelProvider.of( CollectionModelProvider(idGenerator), ArrayModelProvider(idGenerator), - EnumModelProvider, + EnumModelProvider(idGenerator), StringConstantModelProvider, CharToStringModelProvider, ConstantsModelProvider, @@ -71,8 +148,3 @@ fun objectModelProviders(idGenerator: IntSupplier = SimpleIdGenerator()): ModelP PrimitiveWrapperModelProvider, ) } - -private class SimpleIdGenerator : IntSupplier { - private val id = AtomicInteger() - override fun getAsInt() = id.incrementAndGet() -} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ArrayModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ArrayModelProvider.kt index 622853d505..02767a832b 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ArrayModelProvider.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ArrayModelProvider.kt @@ -5,12 +5,13 @@ import org.utbot.framework.plugin.api.util.defaultValueModel import org.utbot.framework.plugin.api.util.isArray import org.utbot.fuzzer.FuzzedMethodDescription import org.utbot.fuzzer.FuzzedParameter +import org.utbot.fuzzer.IdGenerator import org.utbot.fuzzer.ModelProvider import org.utbot.fuzzer.ModelProvider.Companion.yieldAllValues import java.util.function.IntSupplier class ArrayModelProvider( - private val idGenerator: IntSupplier + private val idGenerator: IdGenerator ) : ModelProvider { override fun generate(description: FuzzedMethodDescription): Sequence = sequence { description.parametersMap @@ -19,7 +20,7 @@ class ArrayModelProvider( .forEach { (arrayClassId, indices) -> yieldAllValues(indices, listOf(0, 10).map { arraySize -> UtArrayModel( - id = idGenerator.asInt, + id = idGenerator.createId(), arrayClassId, length = arraySize, arrayClassId.elementClassId!!.defaultValueModel(), diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/CollectionModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/CollectionModelProvider.kt index 828822687a..8d71c0bca5 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/CollectionModelProvider.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/CollectionModelProvider.kt @@ -11,6 +11,7 @@ import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.jClass import org.utbot.fuzzer.FuzzedMethodDescription import org.utbot.fuzzer.FuzzedParameter +import org.utbot.fuzzer.IdGenerator import org.utbot.fuzzer.ModelProvider import org.utbot.fuzzer.ModelProvider.Companion.yieldAllValues import java.util.function.IntSupplier @@ -23,7 +24,7 @@ import java.util.function.IntSupplier * a non-modifiable collection and tries to add values. */ class CollectionModelProvider( - private val idGenerator: IntSupplier + private val idGenerator: IdGenerator ) : ModelProvider { private val generators = mapOf( @@ -92,7 +93,7 @@ class CollectionModelProvider( private fun Class<*>.createdBy(init: ExecutableId, params: List = emptyList()): UtAssembleModel { val instantiationChain = mutableListOf() - val genId = idGenerator.asInt + val genId = idGenerator.createId() return UtAssembleModel( genId, id, diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/EnumModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/EnumModelProvider.kt index 42540ee9b5..57aa0dcf13 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/EnumModelProvider.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/EnumModelProvider.kt @@ -1,23 +1,23 @@ package org.utbot.fuzzer.providers import org.utbot.framework.plugin.api.UtEnumConstantModel -import org.utbot.framework.plugin.api.util.id -import org.utbot.framework.plugin.api.util.isSubtypeOf import org.utbot.framework.plugin.api.util.jClass +import org.utbot.fuzzer.IdentityPreservingIdGenerator import org.utbot.fuzzer.FuzzedMethodDescription import org.utbot.fuzzer.FuzzedParameter import org.utbot.fuzzer.ModelProvider import org.utbot.fuzzer.ModelProvider.Companion.yieldAllValues -object EnumModelProvider : ModelProvider { +class EnumModelProvider(private val idGenerator: IdentityPreservingIdGenerator) : ModelProvider { override fun generate(description: FuzzedMethodDescription): Sequence = sequence { description.parametersMap .asSequence() .filter { (classId, _) -> classId.jClass.isEnum } .forEach { (classId, indices) -> yieldAllValues(indices, classId.jClass.enumConstants.filterIsInstance>().map { - UtEnumConstantModel(classId, it).fuzzed { summary = "%var% = ${it.name}" } + val id = idGenerator.getOrCreateIdForValue(it) + UtEnumConstantModel(id, classId, it).fuzzed { summary = "%var% = ${it.name}" } }) } } -} \ No newline at end of file +} diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ObjectModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ObjectModelProvider.kt index 7dadefe0cb..4a45f34257 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ObjectModelProvider.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ObjectModelProvider.kt @@ -15,6 +15,7 @@ import org.utbot.framework.plugin.api.util.isPrimitiveWrapper import org.utbot.framework.plugin.api.util.jClass import org.utbot.framework.plugin.api.util.stringClassId import org.utbot.framework.plugin.api.util.voidClassId +import org.utbot.fuzzer.IdentityPreservingIdGenerator import org.utbot.fuzzer.FuzzedConcreteValue import org.utbot.fuzzer.FuzzedMethodDescription import org.utbot.fuzzer.FuzzedParameter @@ -31,25 +32,24 @@ import java.lang.reflect.Field import java.lang.reflect.Member import java.lang.reflect.Method import java.lang.reflect.Modifier.* -import java.util.function.IntSupplier private val logger by lazy { KotlinLogging.logger {} } /** * Creates [UtAssembleModel] for objects which have public constructors with primitives types and String as parameters. */ -class ObjectModelProvider : ModelProvider { +class ObjectModelProvider( + private val idGenerator: IdentityPreservingIdGenerator, + private val limit: Int = Int.MAX_VALUE, + private val recursion: Int = 1 +) : ModelProvider { - var modelProvider: ModelProvider + var modelProvider: ModelProvider = objectModelProviders(idGenerator) var limitValuesCreatedByFieldAccessors: Int = 100 set(value) { field = maxOf(0, value) } - private val idGenerator: IntSupplier - private val recursion: Int - private val limit: Int - private val nonRecursiveModelProvider: ModelProvider get() { val modelProviderWithoutRecursion = modelProvider.exceptIsInstance() @@ -60,17 +60,6 @@ class ObjectModelProvider : ModelProvider { } } - constructor(idGenerator: IntSupplier) : this(idGenerator, Int.MAX_VALUE) - - constructor(idGenerator: IntSupplier, limit: Int) : this(idGenerator, limit, 1) - - private constructor(idGenerator: IntSupplier, limit: Int, recursion: Int) { - this.idGenerator = idGenerator - this.recursion = recursion - this.limit = limit - this.modelProvider = objectModelProviders(idGenerator) - } - override fun generate(description: FuzzedMethodDescription): Sequence = sequence { val fuzzedValues = with(description) { parameters.asSequence() @@ -90,12 +79,12 @@ class ObjectModelProvider : ModelProvider { } .flatMap { (constructorId, fuzzedParameters) -> if (constructorId.parameters.isEmpty()) { - sequenceOf(assembleModel(idGenerator.asInt, constructorId, emptyList())) + + sequenceOf(assembleModel(idGenerator.createId(), constructorId, emptyList())) + generateModelsWithFieldsInitialization(constructorId, description, concreteValues) } else { fuzzedParameters.map { params -> - assembleModel(idGenerator.asInt, constructorId, params) + assembleModel(idGenerator.createId(), constructorId, params) } } } @@ -123,7 +112,7 @@ class ObjectModelProvider : ModelProvider { return fuzz(syntheticClassFieldsSetterMethodDescription, nonRecursiveModelProvider) .take(limitValuesCreatedByFieldAccessors) // limit the number of fuzzed values in this particular case .map { fieldValues -> - val fuzzedModel = assembleModel(idGenerator.asInt, constructorId, emptyList()) + val fuzzedModel = assembleModel(idGenerator.createId(), constructorId, emptyList()) val assembleModel = fuzzedModel.model as? UtAssembleModel ?: error("Expected UtAssembleModel but ${fuzzedModel.model::class.java} found") val modificationChain = assembleModel.modificationsChain as? MutableList ?: error("Modification chain must be mutable") fieldValues.asSequence().mapIndexedNotNull { index, value -> diff --git a/utbot-fuzzers/src/test/java/org/utbot/framework/plugin/api/samples/InnerClassWithEnums.java b/utbot-fuzzers/src/test/java/org/utbot/framework/plugin/api/samples/InnerClassWithEnums.java new file mode 100644 index 0000000000..3f3dcd8be0 --- /dev/null +++ b/utbot-fuzzers/src/test/java/org/utbot/framework/plugin/api/samples/InnerClassWithEnums.java @@ -0,0 +1,29 @@ +package org.utbot.framework.plugin.api.samples; + +import org.jetbrains.annotations.NotNull; + +public class InnerClassWithEnums { + private SampleEnum a; + private SampleEnum b; + + public InnerClassWithEnums(SampleEnum a, SampleEnum b) { + this.a = a; + this.b = b; + } + + public SampleEnum getA() { + return a; + } + + public void setA(SampleEnum a) { + this.a = a; + } + + public SampleEnum getB() { + return b; + } + + public void setB(SampleEnum b) { + this.b = b; + } +} diff --git a/utbot-fuzzers/src/test/java/org/utbot/framework/plugin/api/samples/OuterClassWithEnums.java b/utbot-fuzzers/src/test/java/org/utbot/framework/plugin/api/samples/OuterClassWithEnums.java new file mode 100644 index 0000000000..61354b2c52 --- /dev/null +++ b/utbot-fuzzers/src/test/java/org/utbot/framework/plugin/api/samples/OuterClassWithEnums.java @@ -0,0 +1,37 @@ +package org.utbot.framework.plugin.api.samples; + +public class OuterClassWithEnums { + private SampleEnum value; + private final InnerClassWithEnums left; + private final InnerClassWithEnums right; + + public OuterClassWithEnums(SampleEnum value, InnerClassWithEnums left, InnerClassWithEnums right) { + this.value = value; + this.left = left; + this.right = right; + } + + public void setValue(SampleEnum value) { + this.value = value; + } + + public SampleEnum getA() { + if (value == SampleEnum.LEFT && left != null) { + return left.getA(); + } else if (value == SampleEnum.RIGHT && right != null) { + return right.getA(); + } else { + return null; + } + } + + public SampleEnum getB() { + if (value == SampleEnum.LEFT && left != null) { + return left.getB(); + } else if (value == SampleEnum.RIGHT && right != null) { + return right.getB(); + } else { + return null; + } + } +} diff --git a/utbot-fuzzers/src/test/java/org/utbot/framework/plugin/api/samples/SampleEnum.java b/utbot-fuzzers/src/test/java/org/utbot/framework/plugin/api/samples/SampleEnum.java new file mode 100644 index 0000000000..1571254a70 --- /dev/null +++ b/utbot-fuzzers/src/test/java/org/utbot/framework/plugin/api/samples/SampleEnum.java @@ -0,0 +1,6 @@ +package org.utbot.framework.plugin.api.samples; + +public enum SampleEnum { + LEFT, + RIGHT +} diff --git a/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/IdGeneratorTest.kt b/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/IdGeneratorTest.kt new file mode 100644 index 0000000000..fbad13f551 --- /dev/null +++ b/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/IdGeneratorTest.kt @@ -0,0 +1,83 @@ +package org.utbot.framework.plugin.api + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.utbot.fuzzer.ReferencePreservingIntIdGenerator + +class IdGeneratorTest { + private enum class Size { S, M, L, XL } + private enum class Letter { K, L, M, N } + + @Test + fun `default id generator returns sequential values`() { + val idGenerator = ReferencePreservingIntIdGenerator() + val a = idGenerator.createId() + val b = idGenerator.createId() + val c = idGenerator.createId() + assertTrue(a == ReferencePreservingIntIdGenerator.DEFAULT_LOWER_BOUND + 1) + assertTrue(b == a + 1) + assertTrue(c == b + 1) + } + + @Test + fun `caching generator returns different ids for different values`() { + val idGenerator = ReferencePreservingIntIdGenerator() + val a = idGenerator.getOrCreateIdForValue("a") + val b = idGenerator.getOrCreateIdForValue("b") + assertNotEquals(a, b) + } + + @Test + fun `caching generator returns same ids for same values`() { + val idGenerator = ReferencePreservingIntIdGenerator() + val a = idGenerator.getOrCreateIdForValue("a") + idGenerator.getOrCreateIdForValue("b") + val c = idGenerator.getOrCreateIdForValue("a") + assertEquals(a, c) + } + + @Test + fun `caching generator returns consistent ids for enum values`() { + val idGenerator = ReferencePreservingIntIdGenerator(0) + val sizeIds = Size.values().map { it to idGenerator.getOrCreateIdForValue(it) }.toMap() + + assertEquals(idGenerator.getOrCreateIdForValue(Size.S), sizeIds[Size.S]) + assertEquals(idGenerator.getOrCreateIdForValue(Size.M), sizeIds[Size.M]) + assertEquals(idGenerator.getOrCreateIdForValue(Size.L), sizeIds[Size.L]) + assertEquals(idGenerator.getOrCreateIdForValue(Size.XL), sizeIds[Size.XL]) + + idGenerator.getOrCreateIdForValue(Letter.N) + idGenerator.getOrCreateIdForValue(Letter.M) + idGenerator.getOrCreateIdForValue(Letter.L) + idGenerator.getOrCreateIdForValue(Letter.K) + + assertEquals(1, idGenerator.getOrCreateIdForValue(Size.S)) + assertEquals(2, idGenerator.getOrCreateIdForValue(Size.M)) + assertEquals(3, idGenerator.getOrCreateIdForValue(Size.L)) + assertEquals(4, idGenerator.getOrCreateIdForValue(Size.XL)) + + assertEquals(8, idGenerator.getOrCreateIdForValue(Letter.K)) + assertEquals(7, idGenerator.getOrCreateIdForValue(Letter.L)) + assertEquals(6, idGenerator.getOrCreateIdForValue(Letter.M)) + assertEquals(5, idGenerator.getOrCreateIdForValue(Letter.N)) + } + + @Test + fun `caching generator respects reference equality`() { + val idGenerator = ReferencePreservingIntIdGenerator() + + val objA = listOf(1, 2, 3) + val objB = listOf(1, 2, 3) + val objC = objA + + val idA = idGenerator.getOrCreateIdForValue(objA) + val idB = idGenerator.getOrCreateIdForValue(objB) + val idC = idGenerator.getOrCreateIdForValue(objC) + + assertNotEquals(idA, idB) + assertEquals(idA, idC) + } + +} diff --git a/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt b/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt index 0216bf6942..782ce6ee38 100644 --- a/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt +++ b/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt @@ -24,14 +24,17 @@ import org.utbot.fuzzer.providers.StringConstantModelProvider import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test import org.utbot.framework.plugin.api.samples.FieldSetterClass +import org.utbot.framework.plugin.api.samples.OuterClassWithEnums import org.utbot.framework.plugin.api.samples.PackagePrivateFieldAndClass +import org.utbot.framework.plugin.api.samples.SampleEnum import org.utbot.framework.plugin.api.util.primitiveByWrapper import org.utbot.framework.plugin.api.util.primitiveWrappers import org.utbot.framework.plugin.api.util.voidWrapperClassId +import org.utbot.fuzzer.ReferencePreservingIntIdGenerator import org.utbot.fuzzer.ModelProvider.Companion.yieldValue import org.utbot.fuzzer.defaultModelProviders +import org.utbot.fuzzer.providers.CharToStringModelProvider.fuzzed import org.utbot.fuzzer.providers.EnumModelProvider -import org.utbot.fuzzer.providers.EnumModelProvider.fuzzed import org.utbot.fuzzer.providers.PrimitiveDefaultsModelProvider import java.util.Date @@ -190,7 +193,7 @@ class ModelProviderTest { val classId = A::class.java.id val models = collect( - ObjectModelProvider { 0 }.apply { + ObjectModelProvider(ReferencePreservingIntIdGenerator(0)).apply { modelProvider = ModelProvider.of(PrimitiveDefaultsModelProvider) }, parameters = listOf(classId) @@ -218,7 +221,7 @@ class ModelProviderTest { val classId = A::class.java.id val models = collect( - ObjectModelProvider { 0 }, + ObjectModelProvider(ReferencePreservingIntIdGenerator(0)), parameters = listOf(classId) ) @@ -238,7 +241,7 @@ class ModelProviderTest { val classId = A::class.java.id val models = collect( - ObjectModelProvider { 0 }, + ObjectModelProvider(ReferencePreservingIntIdGenerator(0)), parameters = listOf(classId) ) @@ -276,7 +279,7 @@ class ModelProviderTest { fun `test collection model can produce basic values with assembled model`() { withUtContext(UtContext(this::class.java.classLoader)) { val result = collect( - defaultModelProviders { 0 }, + defaultModelProviders(ReferencePreservingIntIdGenerator(0)), parameters = listOf(java.util.List::class.java.id) ) @@ -287,11 +290,11 @@ class ModelProviderTest { @Test fun `test enum model provider`() { withUtContext(UtContext(this::class.java.classLoader)) { - val result = collect(EnumModelProvider, parameters = listOf(OneTwoThree::class.java.id)) + val result = collect(EnumModelProvider(ReferencePreservingIntIdGenerator(0)), parameters = listOf(OneTwoThree::class.java.id)) assertEquals(1, result.size) assertEquals(3, result[0]!!.size) OneTwoThree.values().forEachIndexed { index: Int, value -> - assertEquals(UtEnumConstantModel(OneTwoThree::class.java.id, value), result[0]!![index]) + assertEquals(UtEnumConstantModel(index + 1, OneTwoThree::class.java.id, value), result[0]!![index]) } } } @@ -299,7 +302,10 @@ class ModelProviderTest { @Test fun `test string value generates only primitive models`() { withUtContext(UtContext(this::class.java.classLoader)) { - val result = collect(defaultModelProviders { 0 }, parameters = listOf(stringClassId)) + val result = collect( + defaultModelProviders(ReferencePreservingIntIdGenerator(0)), + parameters = listOf(stringClassId) + ) assertEquals(1, result.size) result[0]!!.forEach { assertInstanceOf(UtPrimitiveModel::class.java, it) @@ -312,7 +318,10 @@ class ModelProviderTest { fun `test wrapper primitives generate only primitive models`() { withUtContext(UtContext(this::class.java.classLoader)) { primitiveWrappers.asSequence().filterNot { it == voidWrapperClassId }.forEach { classId -> - val result = collect(defaultModelProviders { 0 }, parameters = listOf(classId)) + val result = collect( + defaultModelProviders(ReferencePreservingIntIdGenerator(0)), + parameters = listOf(classId) + ) assertEquals(1, result.size) result[0]!!.forEach { assertInstanceOf(UtPrimitiveModel::class.java, it) @@ -327,7 +336,7 @@ class ModelProviderTest { fun `test at least one string is created if characters exist as constants`() { withUtContext(UtContext(this::class.java.classLoader)) { val result = collect( - defaultModelProviders { 0 }, + defaultModelProviders(ReferencePreservingIntIdGenerator(0)), parameters = listOf(stringClassId), constants = listOf( FuzzedConcreteValue(charClassId, 'a'), @@ -350,7 +359,10 @@ class ModelProviderTest { } withUtContext(UtContext(this::class.java.classLoader)) { - val result = collect(ObjectModelProvider { 0 }, parameters = listOf(A::class.java.id)) + val result = collect( + ObjectModelProvider(ReferencePreservingIntIdGenerator(0)), + parameters = listOf(A::class.java.id) + ) assertEquals(1, result.size) assertEquals(1, result[0]!!.size) assertInstanceOf(UtAssembleModel::class.java, result[0]!![0]) @@ -378,7 +390,10 @@ class ModelProviderTest { } withUtContext(UtContext(this::class.java.classLoader)) { - val result = collect(ObjectModelProvider { 0 }, parameters = listOf(MyA::class.java.id)) + val result = collect( + ObjectModelProvider(ReferencePreservingIntIdGenerator(0)), + parameters = listOf(MyA::class.java.id) + ) assertEquals(1, result.size) assertEquals(1, result[0]!!.size) val outerModel = result[0]!![0] as UtAssembleModel @@ -417,7 +432,10 @@ class ModelProviderTest { } withUtContext(UtContext(this::class.java.classLoader)) { - val result = collect(ObjectModelProvider { 0 }, parameters = listOf(Outer::class.java.id)) + val result = collect( + ObjectModelProvider(ReferencePreservingIntIdGenerator(0)), + parameters = listOf(Outer::class.java.id) + ) assertEquals(1, result.size) assertEquals(1, result[0]!!.size) val outerModel = result[0]!![0] as UtAssembleModel @@ -461,7 +479,7 @@ class ModelProviderTest { ) withUtContext(UtContext(this::class.java.classLoader)) { - val result = collect(ObjectModelProvider { 0 }.apply { + val result = collect(ObjectModelProvider(ReferencePreservingIntIdGenerator(0)).apply { modelProvider = PrimitiveDefaultsModelProvider }, parameters = listOf(FieldSetterClass::class.java.id)) assertEquals(1, result.size) @@ -489,7 +507,7 @@ class ModelProviderTest { ) withUtContext(UtContext(this::class.java.classLoader)) { - val result = collect(ObjectModelProvider { 0 }.apply { + val result = collect(ObjectModelProvider(ReferencePreservingIntIdGenerator(0)).apply { modelProvider = PrimitiveDefaultsModelProvider }, parameters = listOf(PackagePrivateFieldAndClass::class.java.id)) { packageName = PackagePrivateFieldAndClass::class.java.`package`.name @@ -507,6 +525,44 @@ class ModelProviderTest { } } + @Test + fun `test that enum models in a recursive object model are consistent`() { + withUtContext(UtContext(this::class.java.classLoader)) { + val idGenerator = ReferencePreservingIntIdGenerator() + val expectedIds = SampleEnum.values().associateWith { idGenerator.getOrCreateIdForValue(it) } + + val result = collect( + ObjectModelProvider(idGenerator), + parameters = listOf(OuterClassWithEnums::class.java.id) + ) + + assertEquals(1, result.size) + val models = result[0] ?: fail("Inconsistent result") + + for (model in models) { + val outerModel = (model as? UtAssembleModel) + ?.finalInstantiationModel as? UtExecutableCallModel + ?: fail("No final instantiation model found for the outer class") + for (param in outerModel.params) { + when (param) { + is UtEnumConstantModel -> { + assertEquals(expectedIds[param.value], param.id) + } + is UtAssembleModel -> { + for (enumParam in (param.finalInstantiationModel as UtExecutableCallModel).params) { + enumParam as UtEnumConstantModel + assertEquals(expectedIds[enumParam.value], enumParam.id) + } + } + else -> { + fail("Unexpected parameter model: $param") + } + } + } + } + } + } + private fun collect( modelProvider: ModelProvider, name: String = "testMethod", diff --git a/utbot-sample/src/main/java/org/utbot/examples/enums/ComplexEnumExamples.java b/utbot-sample/src/main/java/org/utbot/examples/enums/ComplexEnumExamples.java new file mode 100644 index 0000000000..42ecf5d810 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/enums/ComplexEnumExamples.java @@ -0,0 +1,113 @@ +package org.utbot.examples.enums; + +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +public class ComplexEnumExamples { + + public enum Color { + RED, + GREEN, + BLUE + } + + public int countEqualColors(@NotNull Color a, @NotNull Color b, @NotNull Color c) { + int equalToA = 1; + if (b == a) { + equalToA++; + } + + if (c == a) { + equalToA++; + } + + int equalToB = 1; + if (a == b) { + equalToB++; + } + + if (c == b) { + equalToB++; + } + + if (equalToA > equalToB) { + return equalToA; + } else { + return equalToB; + } + } + + public int countNullColors(Color a, Color b) { + int nullCount = 0; + if (a == null) { + nullCount++; + } + + if (b == null) { + nullCount++; + } + + return nullCount; + } + + public int enumToEnumMapCountValues(@NotNull Map map) { + int count = 0; + for (Color color: map.values()) { + if (color == Color.RED) { + count++; + } + } + return count; + } + + public int enumToEnumMapCountKeys(@NotNull Map map) { + int count = 0; + for (Color key: map.keySet()) { + if (key == Color.GREEN || Color.BLUE.equals(key)) { + count++; + } else { + // Do nothing + } + } + return count; + } + + public int enumToEnumMapCountMatches(@NotNull Map map) { + int count = 0; + for (Map.Entry entry: map.entrySet()) { + if (entry.getKey() == entry.getValue() && entry.getKey() != null) { + count++; + } + } + return count; + } + + public State findState(int code) { + return State.findStateByCode(code); + } + + public Map countValuesInArray(Color @NotNull [] colors) { + HashMap counters = new HashMap<>(); + for (Color c : colors) { + if (c != null) { + Integer value = counters.getOrDefault(c, 0); + counters.put(c, value + 1); + } + } + return counters; + } + + public int countRedInArray(@NotNull Color @NotNull [] colors) { + int count = 0; + for (Color c : colors) { + if (c == Color.RED) { + count++; + } + } + return count; + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/enums/State.java b/utbot-sample/src/main/java/org/utbot/examples/enums/State.java new file mode 100644 index 0000000000..c8d0af1e3f --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/enums/State.java @@ -0,0 +1,41 @@ +package org.utbot.examples.enums; + +public enum State { + OPEN(255) { + @Override + public String toString() { + return ""; + } + }, + CLOSED(127) { + @Override + public String toString() { + return ""; + } + }, + UNKNOWN(0) { + @Override + public String toString() { + return ""; + } + }; + + private final int code; + + State(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public static State findStateByCode(int code) { + for (State state: values()) { + if (state.getCode() == code) { + return state; + } + } + return UNKNOWN; + } +}