diff --git a/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FuzzerFunctions.kt b/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FuzzerFunctions.kt index 8bae40fd8a..96fbd4d0d7 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FuzzerFunctions.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FuzzerFunctions.kt @@ -74,7 +74,10 @@ fun collectConstantsForFuzzer(graph: ExceptionalUnitGraph): Set diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedType.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedType.kt index b70c714b40..a11cc0dd0a 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedType.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedType.kt @@ -5,16 +5,16 @@ import org.utbot.framework.plugin.api.ClassId /** * Contains information about classId and generic types, that should be applied. * - * Currently, there's some limitation for generics that are supported: - * 1. Only concrete types and collections are supported - * 2. No relative types like: `Map` - * - * Note, that this class can be replaced by API mechanism for collecting parametrized types, + * Note that this class can be replaced by the API mechanism for collecting parametrized types, * but at the moment it doesn't fully support all necessary operations. * * @see ClassId.typeParameters */ -data class FuzzedType( +class FuzzedType( val classId: ClassId, val generics: List = emptyList(), -) \ No newline at end of file +) { + override fun toString(): String { + return "FuzzedType(classId=$classId, generics=${generics.map { it.classId }})" + } +} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/package-info.java b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/package-info.java new file mode 100644 index 0000000000..e41294c025 --- /dev/null +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/package-info.java @@ -0,0 +1,7 @@ +/** + * @deprecated Code is migrating to the new fuzzing platform. + * @see org.utbot.fuzzing.Fuzzing + * @see org.utbot.fuzzing.JavaLanguageKt#runJavaFuzzing + */ +@Deprecated +package org.utbot.fuzzer; \ 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 745c055b69..6dc9c0a213 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 @@ -24,7 +24,7 @@ import org.utbot.fuzzer.IdentityPreservingIdGenerator import org.utbot.fuzzer.objects.FuzzerMockableMethodId import org.utbot.fuzzer.objects.assembleModel import org.utbot.fuzzing.providers.FieldDescription -import org.utbot.fuzzing.providers.findAccessibleModifableFields +import org.utbot.fuzzing.providers.findAccessibleModifiableFields import org.utbot.fuzzing.providers.isAccessible /** @@ -64,10 +64,10 @@ class ObjectModelProvider( // and mutate some fields. Only if there's no option next block // with empty constructor should be used. if (constructorId.parameters.isEmpty()) { - val fields = findAccessibleModifableFields(constructorId.classId, description.packageName) + val fields = findAccessibleModifiableFields(null, constructorId.classId, description.packageName) if (fields.isNotEmpty()) { yield( - ModelConstructor(fields.map { FuzzedType(it.classId) }) { + ModelConstructor(fields.map { it.type }) { generateModelsWithFieldsInitialization(constructorId, fields, it) } ) @@ -103,7 +103,7 @@ class ObjectModelProvider( constructorId.classId, field.setter.name, field.setter.returnType.id, - listOf(field.classId), + listOf(field.type.classId), mock = { field.getter?.let { g -> val getterMethodID = MethodId( diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzing/JavaLanguage.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzing/JavaLanguage.kt index ab0b9cbf1f..6eca308d5f 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzing/JavaLanguage.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzing/JavaLanguage.kt @@ -11,6 +11,7 @@ import org.utbot.fuzzing.utils.Trie import java.lang.reflect.* import java.util.concurrent.TimeUnit import kotlin.system.measureNanoTime +import kotlin.random.Random private val logger = KotlinLogging.logger {} @@ -19,6 +20,8 @@ typealias JavaValueProvider = ValueProvider, + val typeCache: MutableMap, + val random: Random, ) : Description( description.parameters.mapIndexed { index, classId -> description.fuzzerType(index) ?: FuzzedType(classId) @@ -39,6 +42,7 @@ fun defaultValueProviders(idGenerator: IdentityPreservingIdGenerator) = lis EnumValueProvider(idGenerator), ListSetValueProvider(idGenerator), MapValueProvider(idGenerator), + IteratorValueProvider(idGenerator), EmptyCollectionValueProvider(idGenerator), DateValueProvider(idGenerator), // NullValueProvider, @@ -52,13 +56,18 @@ suspend fun runJavaFuzzing( providers: List> = defaultValueProviders(idGenerator), exec: suspend (thisInstance: FuzzedValue?, description: FuzzedDescription, values: List) -> BaseFeedback, FuzzedType, FuzzedValue> ) { + val random = Random(0) val classUnderTest = methodUnderTest.classId val name = methodUnderTest.classId.simpleName + "." + methodUnderTest.name val returnType = methodUnderTest.returnType val parameters = methodUnderTest.parameters + // For a concrete fuzzing run we need to track types we create. + // Because of generics can be declared as recursive structures like `>`, + // we should track them by reference and do not call `equals` and `hashCode` recursively. + val typeCache = hashMapOf() /** - * To fuzz this instance the class of it is added into head of parameters list. + * To fuzz this instance, the class of it is added into head of parameters list. * Done for compatibility with old fuzzer logic and should be reworked more robust way. */ fun createFuzzedMethodDescription(self: ClassId?) = FuzzedMethodDescription( @@ -79,9 +88,9 @@ suspend fun runJavaFuzzing( fuzzerType = { try { when { - self != null && it == 0 -> toFuzzerType(methodUnderTest.executable.declaringClass) - self != null -> toFuzzerType(methodUnderTest.executable.genericParameterTypes[it - 1]) - else -> toFuzzerType(methodUnderTest.executable.genericParameterTypes[it]) + self != null && it == 0 -> toFuzzerType(methodUnderTest.executable.declaringClass, typeCache) + self != null -> toFuzzerType(methodUnderTest.executable.genericParameterTypes[it - 1], typeCache) + else -> toFuzzerType(methodUnderTest.executable.genericParameterTypes[it], typeCache) } } catch (_: Throwable) { null @@ -94,15 +103,15 @@ suspend fun runJavaFuzzing( if (!isStatic && !isConstructor) { classUnderTest } else { null } } val tracer = Trie(Instruction::id) - val descriptionWithOptionalThisInstance = FuzzedDescription(createFuzzedMethodDescription(thisInstance), tracer) - val descriptionWithOnlyParameters = FuzzedDescription(createFuzzedMethodDescription(null), tracer) + val descriptionWithOptionalThisInstance = FuzzedDescription(createFuzzedMethodDescription(thisInstance), tracer, typeCache, random) + val descriptionWithOnlyParameters = FuzzedDescription(createFuzzedMethodDescription(null), tracer, typeCache, random) try { logger.info { "Starting fuzzing for method: $methodUnderTest" } logger.info { "\tuse thisInstance = ${thisInstance != null}" } logger.info { "\tparameters = $parameters" } var totalExecutionCalled = 0 val totalFuzzingTime = measureNanoTime { - runFuzzing(ValueProvider.of(providers), descriptionWithOptionalThisInstance) { _, t -> + runFuzzing(ValueProvider.of(providers), descriptionWithOptionalThisInstance, random) { _, t -> totalExecutionCalled++ if (thisInstance == null) { exec(null, descriptionWithOnlyParameters, t) @@ -118,22 +127,86 @@ suspend fun runJavaFuzzing( } } -private fun toFuzzerType(type: Type): FuzzedType { +/** + * Resolve a fuzzer type that has class info and some generics. + * + * @param type to be resolved + * @param cache is used to store same [FuzzedType] for same java types + */ +internal fun toFuzzerType(type: Type, cache: MutableMap): FuzzedType { + return toFuzzerType( + type = type, + classId = { t -> toClassId(t, cache) }, + generics = { t -> toGenerics(t) }, + cache = cache, + ) +} + +/** + * Resolve a fuzzer type that has class info and some generics. + * + * Cache is used to stop recursive call in case of some recursive class definition like: + * + * ``` + * public > call(T type) { ... } + * ``` + * + * @param type to be resolved into a fuzzed type. + * @param classId is a function that produces classId by general type. + * @param generics is a function that produced a list of generics for this concrete type. + * @param cache is used to store all generated types. + */ +private fun toFuzzerType( + type: Type, + classId: (type: Type) -> ClassId, + generics: (parent: Type) -> Array, + cache: MutableMap +): FuzzedType { + val g = mutableListOf() + val t = type.replaceWithUpperBoundUntilNotTypeVariable() + var target = cache[t] + if (target == null) { + target = FuzzedType(classId(t), g) + cache[t] = target + g += generics(t).map { + toFuzzerType(it, classId, generics, cache) + } + } + return target +} + +internal fun Type.replaceWithUpperBoundUntilNotTypeVariable() : Type { + var type: Type = this + while (type is TypeVariable<*>) { + type = type.bounds.firstOrNull() ?: java.lang.Object::class.java + } + return type +} + +private fun toClassId(type: Type, cache: MutableMap): ClassId { return when (type) { - is WildcardType -> type.upperBounds.firstOrNull()?.let(::toFuzzerType) ?: FuzzedType(objectClassId) - is TypeVariable<*> -> type.bounds.firstOrNull()?.let(::toFuzzerType) ?: FuzzedType(objectClassId) - is ParameterizedType -> FuzzedType((type.rawType as Class<*>).id, type.actualTypeArguments.map { toFuzzerType(it) }) + is WildcardType -> type.upperBounds.firstOrNull()?.let { toClassId(it, cache) } ?: objectClassId is GenericArrayType -> { val genericComponentType = type.genericComponentType - val fuzzerType = toFuzzerType(genericComponentType) - val classId = if (genericComponentType !is GenericArrayType) { - ClassId("[L${fuzzerType.classId.name};", fuzzerType.classId) + val classId = toFuzzerType(genericComponentType, cache).classId + if (genericComponentType !is GenericArrayType) { + ClassId("[L${classId.name};", classId) } else { - ClassId("[" + fuzzerType.classId.name, fuzzerType.classId) + ClassId("[" + classId.name, classId) } - FuzzedType(classId) } - is Class<*> -> FuzzedType(type.id, type.typeParameters.map { toFuzzerType(it) }) - else -> error("Unknown type: $type") + is ParameterizedType -> (type.rawType as Class<*>).id + is Class<*> -> type.id + else -> error("unknown type: $type") + } +} + +private fun toGenerics(t: Type) : Array { + return when (t) { + is WildcardType -> t.upperBounds.firstOrNull()?.let { toGenerics(it) } ?: emptyArray() + is GenericArrayType -> arrayOf(t.genericComponentType) + is ParameterizedType -> t.actualTypeArguments + is Class<*> -> t.typeParameters + else -> emptyArray() } } \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzing/providers/Arrays.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzing/providers/Arrays.kt index 8af39a4ce8..1d64e2443e 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzing/providers/Arrays.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzing/providers/Arrays.kt @@ -7,10 +7,7 @@ import org.utbot.fuzzer.FuzzedType import org.utbot.fuzzer.FuzzedValue import org.utbot.fuzzer.IdGenerator import org.utbot.fuzzer.providers.ConstantsModelProvider.fuzzed -import org.utbot.fuzzing.FuzzedDescription -import org.utbot.fuzzing.Routine -import org.utbot.fuzzing.Seed -import org.utbot.fuzzing.ValueProvider +import org.utbot.fuzzing.* class ArrayValueProvider( val idGenerator: IdGenerator, @@ -35,9 +32,24 @@ class ArrayValueProvider( summary = "%var% = ${type.classId.elementClassId!!.simpleName}[$it]" } }, - modify = Routine.ForEach(listOf(FuzzedType(type.classId.elementClassId!!))) { self, i, values -> + modify = Routine.ForEach(listOf(resolveArrayElementType(type))) { self, i, values -> (self.model as UtArrayModel).stores[i] = values.first().model } )) } + + /** + * Resolves array's element type. In case a generic type is used, like `T[]` the type should pass generics. + * + * For example, List[] returns List. + */ + private fun resolveArrayElementType(arrayType: FuzzedType): FuzzedType = when { + !arrayType.classId.isArray -> error("$arrayType is not array") + arrayType.generics.size == 1 -> arrayType.generics.first() + arrayType.generics.isEmpty() -> FuzzedType( + arrayType.classId.elementClassId ?: error("Element classId of $arrayType is not found") + ) + + else -> error("Array has ${arrayType.generics.size} generic type for ($arrayType), that should not happen") + } } \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzing/providers/Collections.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzing/providers/Collections.kt index b77d04850f..87fc8a61c3 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzing/providers/Collections.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzing/providers/Collections.kt @@ -6,10 +6,8 @@ import org.utbot.fuzzer.FuzzedType import org.utbot.fuzzer.FuzzedValue import org.utbot.fuzzer.IdGenerator import org.utbot.fuzzer.providers.PrimitivesModelProvider.fuzzed -import org.utbot.fuzzing.FuzzedDescription -import org.utbot.fuzzing.Routine -import org.utbot.fuzzing.Seed -import org.utbot.fuzzing.ValueProvider +import org.utbot.fuzzing.* +import org.utbot.fuzzing.utils.hex import kotlin.reflect.KClass class EmptyCollectionValueProvider( @@ -56,10 +54,25 @@ class EmptyCollectionValueProvider( classId = classId, modelName = "", instantiationCall = UtExecutableCallModel(null, executableId, value.map { it.model }) + ).fuzzed { + summary = "%var% = ${executableId.classId.simpleName}#${executableId.name}" + } + }, + empty = Routine.Empty { + if (executableId.parameters.isEmpty()) { + UtAssembleModel( + id = idGenerator.createId(), + classId = classId, + modelName = "", + instantiationCall = UtExecutableCallModel(null, executableId, emptyList()) - ).fuzzed() + ).fuzzed{ + summary = "%var% = ${executableId.classId.simpleName}#${executableId.name}" + } + } else { + UtNullModel(classId).fuzzed { summary = "%var% = null" } + } }, - empty = Routine.Empty { UtNullModel(classId).fuzzed { summary = "%var% = null" } } )) } } @@ -205,4 +218,51 @@ abstract class CollectionValueProvider( // return isSubtypeOf(another) return klass.java.isAssignableFrom(this.jClass) } +} + +class IteratorValueProvider(val idGenerator: IdGenerator) : ValueProvider { + override fun accept(type: FuzzedType): Boolean { + return type.classId == Iterator::class.id + } + + override fun generate(description: FuzzedDescription, type: FuzzedType): Sequence> { + val generic = type.generics.firstOrNull() ?: FuzzedType(objectClassId) + return sequenceOf(Seed.Recursive( + construct = Routine.Create(listOf(FuzzedType(iterableClassId, listOf(generic)))) { v -> + val id = idGenerator.createId() + val iterable = when (val model = v.first().model) { + is UtAssembleModel -> model + is UtNullModel -> return@Create v.first() + else -> error("Model can be only UtNullModel or UtAssembleModel, but $model is met") + } + UtAssembleModel( + id = id, + classId = type.classId, + modelName = "iterator#${id.hex()}", + instantiationCall = UtExecutableCallModel( + instance = iterable, + executable = MethodId(iterableClassId, "iterator", type.classId, emptyList()), + params = emptyList() + ) + ).fuzzed { + summary = "%var% = ${iterable.classId.simpleName}#iterator()" + } + }, + empty = Routine.Empty { + val id = idGenerator.createId() + UtAssembleModel( + id = id, + classId = type.classId, + modelName = "emptyIterator#${id.hex()}", + instantiationCall = UtExecutableCallModel( + instance = null, + executable = MethodId(java.util.Collections::class.id, "emptyIterator", type.classId, emptyList()), + params = emptyList() + ) + ).fuzzed { + summary = "%var% = empty iterator" + } + } + )) + } } \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt index 0e1ca84ab7..1322452a01 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt @@ -12,6 +12,7 @@ import java.lang.reflect.Field import java.lang.reflect.Member import java.lang.reflect.Method import java.lang.reflect.Modifier +import java.util.* class ObjectValueProvider( val idGenerator: IdGenerator, @@ -43,7 +44,9 @@ class ObjectValueProvider( private fun createValue(classId: ClassId, constructorId: ConstructorId, description: FuzzedDescription): Seed.Recursive { return Seed.Recursive( - construct = Routine.Create(constructorId.parameters.map { FuzzedType(it) }) { values -> + construct = Routine.Create(constructorId.executable.genericParameterTypes.map { + toFuzzerType(it, description.typeCache) + }) { values -> val id = idGenerator.createId() UtAssembleModel( id = id, @@ -59,10 +62,10 @@ class ObjectValueProvider( } }, modify = sequence { - findAccessibleModifableFields(classId, description.description.packageName).forEach { fd -> + findAccessibleModifiableFields(description, classId, description.description.packageName).forEach { fd -> when { fd.canBeSetDirectly -> { - yield(Routine.Call(listOf(FuzzedType(fd.classId))) { self, values -> + yield(Routine.Call(listOf(fd.type)) { self, values -> val model = self.model as UtAssembleModel model.modificationsChain as MutableList += UtDirectSetFieldModel( model, @@ -73,7 +76,7 @@ class ObjectValueProvider( } fd.setter != null && fd.getter != null -> { - yield(Routine.Call(listOf(FuzzedType(fd.classId))) { self, values -> + yield(Routine.Call(listOf(fd.type)) { self, values -> val model = self.model as UtAssembleModel model.modificationsChain as MutableList += UtExecutableCallModel( model, @@ -143,19 +146,19 @@ internal class PublicSetterGetter( internal class FieldDescription( val name: String, - val classId: ClassId, + val type: FuzzedType, val canBeSetDirectly: Boolean, val setter: Method?, val getter: Method? ) -internal fun findAccessibleModifableFields(classId: ClassId, packageName: String?): List { +internal fun findAccessibleModifiableFields(description: FuzzedDescription?, classId: ClassId, packageName: String?): List { val jClass = classId.jClass return jClass.declaredFields.map { field -> val setterAndGetter = jClass.findPublicSetterGetterIfHasPublicGetter(field, packageName) FieldDescription( name = field.name, - classId = field.type.id, + type = if (description != null) toFuzzerType(field.type, description.typeCache) else FuzzedType(field.type.id), canBeSetDirectly = isAccessible( field, packageName diff --git a/utbot-fuzzers/src/test/java/org/utbot/fuzzing/samples/Stubs.java b/utbot-fuzzers/src/test/java/org/utbot/fuzzing/samples/Stubs.java index 16c2748681..8423b68cbf 100644 --- a/utbot-fuzzers/src/test/java/org/utbot/fuzzing/samples/Stubs.java +++ b/utbot-fuzzers/src/test/java/org/utbot/fuzzing/samples/Stubs.java @@ -1,8 +1,35 @@ package org.utbot.fuzzing.samples; +import java.util.List; + @SuppressWarnings("unused") public class Stubs { public static void name(String value) {} + public static > int resolve(T recursive) { + int result = 0; + for (T t : recursive) { + result++; + } + return result; + } + + public static > int types(T[] t1, T[] t2, T[] t3) { + return t1.length + t2.length + t3.length; + } + + public static int arrayLength(java.util.List[][] test) { + int length = 0; + for (int i = 0; i < test.length; i++) { + for (int j = 0; j < test[i].length; j++) { + length += test[i][j].size(); + } + } + return length; + } + + public static , B extends List, C extends List>> A example(A c1, B c2, C c) { + return c2.iterator().next(); + } } diff --git a/utbot-fuzzers/src/test/kotlin/org/utbot/fuzzing/JavaFuzzingTest.kt b/utbot-fuzzers/src/test/kotlin/org/utbot/fuzzing/JavaFuzzingTest.kt index 53b8ade416..65afde2f20 100644 --- a/utbot-fuzzers/src/test/kotlin/org/utbot/fuzzing/JavaFuzzingTest.kt +++ b/utbot-fuzzers/src/test/kotlin/org/utbot/fuzzing/JavaFuzzingTest.kt @@ -1,7 +1,7 @@ package org.utbot.fuzzing import kotlinx.coroutines.runBlocking -import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertDoesNotThrow import org.utbot.framework.plugin.api.MethodId @@ -10,8 +10,13 @@ import org.utbot.framework.plugin.api.UtPrimitiveModel import org.utbot.framework.plugin.api.util.* import org.utbot.fuzzer.FuzzedConcreteValue import org.utbot.fuzzing.samples.DeepNested +import org.utbot.fuzzer.FuzzedType import org.utbot.fuzzing.samples.Stubs import org.utbot.fuzzing.utils.Trie +import java.lang.reflect.GenericArrayType +import java.lang.reflect.ParameterizedType +import java.lang.reflect.Type +import java.util.IdentityHashMap class JavaFuzzingTest { @@ -30,7 +35,7 @@ class JavaFuzzingTest { constants = emptyList(), names = emptyList(), ) { _, _, _ -> - Assertions.fail("This method is never called") + fail("This method is never called") } } } @@ -56,14 +61,138 @@ class JavaFuzzingTest { results += (values.first().model as UtPrimitiveModel).value as String BaseFeedback(Trie.emptyNode(), if (++count < probes) Control.CONTINUE else Control.STOP) } - Assertions.assertEquals(count, results.size) + assertEquals(count, results.size) results } } val probe1 = collect() val probe2 = collect() - Assertions.assertEquals(probe1, probe2) + assertEquals(probe1, probe2) + } + + @Test + fun `recursive generic types are recognized correctly`() { + runBlockingWithContext { + val methods = Stubs::class.java.methods + val method = methods.first { it.name == "resolve" && it.returnType == Int::class.javaPrimitiveType } + val typeCache = IdentityHashMap() + val type = toFuzzerType(method.genericParameterTypes.first(), typeCache) + assertEquals(1, typeCache.size) + assertTrue(typeCache.values.all { type === it }) + assertEquals(1, type.generics.size) + assertTrue(typeCache.values.all { type.generics[0] === it }) + + try { + // If FuzzerType has implemented `equals` and `hashCode` or is data class, + // that implements those methods implicitly, + // then adding it to hash table throws [StackOverflowError] + val set = HashSet() + set += type + } catch (soe: StackOverflowError) { + fail("Looks like FuzzerType implements equals and hashCode, " + + "which leads unstable behaviour in recursive generics ", soe) + } + } + } + + @Test + fun `can pass types through`() { + runBlockingWithContext { + val cache = HashMap() + val methods = Stubs::class.java.methods + val method = methods.first { it.name == "types" } + val types = method.genericParameterTypes.map { + toFuzzerType(it, cache) + } + assertEquals(3, cache.size) { "Cache should contain following types: List, Number and T[] for $method" } + assertTrue(cache.keys.any { t -> + t is Class<*> && t == java.lang.Number::class.java + }) + assertTrue(cache.keys.any { t -> + t is ParameterizedType + && t.rawType == java.util.List::class.java + && t.actualTypeArguments.size == 1 + && t.actualTypeArguments.first() == java.lang.Number::class.java + }) + assertTrue(cache.keys.any { t -> + t is GenericArrayType + && t.typeName == "T[]" + }) + } + } + + @Test + fun `arrays with generics can be resolved`() { + runBlockingWithContext { + val cache = HashMap() + val methods = Stubs::class.java.methods + val method = methods.first { it.name == "arrayLength" } + method.genericParameterTypes.map { + toFuzzerType(it, cache) + } + assertEquals(4, cache.size) { "Cache should contain following types: List, Number and T[] for $method" } + assertTrue(cache.keys.any { t -> + t is Class<*> && t == java.lang.Number::class.java + }) + assertTrue(cache.keys.any { t -> + t is ParameterizedType + && t.rawType == java.util.List::class.java + && t.actualTypeArguments.size == 1 + && t.actualTypeArguments.first().typeName == "T" + }) + assertTrue(cache.keys.any { t -> + t is GenericArrayType + && t.typeName == "java.util.List[]" + }) + assertTrue(cache.keys.any { t -> + t is GenericArrayType + && t.typeName == "java.util.List[][]" + }) + } + } + + @Test + fun `run complex type dependency call`() { + runBlockingWithContext { + val cache = HashMap() + val methods = Stubs::class.java.methods + val method = methods.first { it.name == "example" } + val types = method.genericParameterTypes + assertTrue(types.size == 3 && types[0].typeName == "A" && types[1].typeName == "B" && types[2].typeName == "C") { "bad input parameters" } + method.genericParameterTypes.map { + toFuzzerType(it, cache) + } + assertEquals(4, cache.size) + val typeIterableB = cache[types[0].replaceWithUpperBoundUntilNotTypeVariable()]!! + val genericOfIterableB = with(typeIterableB) { + assertEquals(iterableClassId, classId) + assertEquals(1, generics.size) + generics[0] + } + val typeListA = cache[types[1].replaceWithUpperBoundUntilNotTypeVariable()]!! + val genericOfListA = with(typeListA) { + assertEquals(java.util.List::class.id, classId) + assertEquals(1, generics.size) + generics[0] + } + assertEquals(1, genericOfIterableB.generics.size) + assertEquals(1, genericOfListA.generics.size) + assertTrue(genericOfIterableB.generics[0] === typeIterableB) { "Because of recursive types generic of B must depend on B itself" } + assertTrue(genericOfListA.generics[0] === typeListA) { "Because of recursive types generic of A must depend on A itself" } + + val typeListC = cache[types[2].replaceWithUpperBoundUntilNotTypeVariable()]!! + val genericOfListC = with(typeListC) { + assertEquals(java.util.List::class.id, classId) + assertEquals(1, generics.size) + generics[0] + } + + assertEquals(1, genericOfListC.generics.size) + assertEquals(iterableClassId, genericOfListC.generics[0].classId) + assertTrue(genericOfListC.generics[0].generics[0] === typeListA) { "Generic of C must lead to type A" } + + } } private fun runBlockingWithContext(block: suspend () -> T) : T { diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Api.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Api.kt index e89eeedc67..cfa84ed253 100644 --- a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Api.kt +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Api.kt @@ -112,7 +112,7 @@ sealed class Routine(val types: List) : Iterable by types { class Create( types: List, val builder: (arguments: List) -> R, - ) : Routine(types), Map by hashMapOf() { + ) : Routine(types) { operator fun invoke(arguments: List): R = builder(arguments) } @@ -347,7 +347,7 @@ private fun , FEEDBACK : Feedback< } } if (candidates.isEmpty()) { - error("Unknown type: $type") + error("No seed candidates generated for type: $type") } return candidates.random(random) } diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Configuration.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Configuration.kt index 20821e836f..77cd6650b0 100644 --- a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Configuration.kt +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Configuration.kt @@ -11,7 +11,7 @@ class Configuration( * * To stop recursion [Seed.Recursive.empty] is called to create new values. */ - var recursionTreeDepth: Int = 3, + var recursionTreeDepth: Int = 4, /** * The limit of collection size to create. diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/utils/CartesianProduct.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/utils/CartesianProduct.kt index 138076fcbc..399f514c89 100644 --- a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/utils/CartesianProduct.kt +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/utils/CartesianProduct.kt @@ -66,4 +66,36 @@ class CartesianProduct( } } } -} \ No newline at end of file +} + +inline fun List>.cartesian(block: (List) -> Unit) { + cartesian().forEach(block) +} + +fun List>.cartesian(): Sequence> = sequence { + cartesian(this@cartesian, 0, IntArray(size)) +} + +private suspend fun SequenceScope>.cartesian(lists: List>, iteration: Int, array: IntArray) { + if (iteration == lists.size) { + yield(array.mapIndexed { l, v -> lists[l][v] }) + } else { + check(iteration < lists.size) + for (j in lists[iteration].indices) { + array[iteration] = j + cartesian(lists, iteration + 1, array) + } + } +} + +//private suspend fun SequenceScope>.cartesian(lists: List>, head: List) { +// if (head.size == lists.size) { +// yield(head) +// } else { +// check(head.size < lists.size) +// lists[head.size].forEach { +// val copy = head + it +// cartesian(lists, copy) +// } +// } +//} \ No newline at end of file diff --git a/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/Collections.java b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/Collections.java index 1d9f8f19f8..e26c7b6d1a 100644 --- a/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/Collections.java +++ b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/Collections.java @@ -174,4 +174,16 @@ public boolean testNoErrorWithHashMap(HashMap map) { } return false; } + + /** + * Should generate iterators with recursions + */ + public static > int size(Iterator some) { + int r = 0; + while (some.hasNext()) { + some.next(); + r++; + } + return r; + } } diff --git a/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/Generics.java b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/Generics.java new file mode 100644 index 0000000000..381d71a70d --- /dev/null +++ b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/Generics.java @@ -0,0 +1,27 @@ +package org.utbot.fuzzing.samples; + +@SuppressWarnings("unused") +public class Generics> { + + private final T[] value; + + public Generics(T[] value) { + this.value = value; + } + + // should generate data with numbers to sum it + public double getSum() { + double sum = 0; + for (T numbers : value) { + for (Number number : numbers) { + if (number.doubleValue() > 0) { + sum += number.doubleValue(); + } + } + } + if (sum == 0.0) { + throw new IllegalStateException(); + } + return sum; + } +} diff --git a/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/utils/CombinationsTest.kt b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/utils/CombinationsTest.kt index fbd702b59e..a1ae742b0c 100644 --- a/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/utils/CombinationsTest.kt +++ b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/utils/CombinationsTest.kt @@ -267,4 +267,33 @@ class CombinationsTest { assertArrayEquals(intArrayOf(1, 1), combinations[Int.MAX_VALUE + 1L]) assertArrayEquals(intArrayOf(1, Int.MAX_VALUE - 1), combinations[Int.MAX_VALUE * 2L - 1]) } + + @Test + fun testLazyCartesian() { + val source = listOf( + (1..10).toList(), + listOf("a", "b", "c"), + listOf(true, false) + ) + val eager = CartesianProduct(source).asSequence().toList() + val lazy = source.cartesian().toList() + assertEquals(eager.size, lazy.size) + for (i in eager.indices) { + assertEquals(eager[i], lazy[i]) + } + } + + @Test + fun testLazyCartesian2() { + val source = listOf( + (1..10).toList(), + listOf("a", "b", "c"), + listOf(true, false) + ) + val eager = CartesianProduct(source).asSequence().toList() + var index = 0 + source.cartesian { + assertEquals(eager[index++], it) + } + } } \ No newline at end of file