diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/InstrumentationContextAwareValueConstructor.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/InstrumentationContextAwareValueConstructor.kt index c3b90bb681..d9b6176fe9 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/InstrumentationContextAwareValueConstructor.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/InstrumentationContextAwareValueConstructor.kt @@ -52,6 +52,7 @@ import org.utbot.instrumentation.instrumentation.execution.mock.MethodMockContro import org.utbot.instrumentation.instrumentation.execution.mock.MockController import org.utbot.instrumentation.process.runSandbox import java.lang.reflect.Modifier +import java.lang.reflect.TypeVariable import java.security.AccessController import java.security.PrivilegedAction import java.util.* @@ -280,22 +281,25 @@ class InstrumentationContextAwareValueConstructor( private val dynamicMockModelToDepth = mutableMapOf() - private fun generateNewAnswerModel(executableId: ExecutableId, depth: Int) = - executableId.returnType.defaultValueModel().takeUnless { it.isNull() } ?: when { + private fun generateNewAnswerModel(methodId: MethodId, depth: Int) = + methodId.returnType.defaultValueModel().takeUnless { it.isNull() } ?: when { + // use `null` to avoid false positive `ClassCastException` + methodId.method.genericReturnType is TypeVariable<*> -> UtNullModel(methodId.returnType) + // mockito can't mock `String` and `Class` - executableId.returnType == stringClassId -> UtNullModel(stringClassId) - executableId.returnType == classClassId -> UtClassRefModel( + methodId.returnType == stringClassId -> UtNullModel(stringClassId) + methodId.returnType == classClassId -> UtClassRefModel( id = idGenerator.createId(), classId = classClassId, value = classClassId, ) - depth > MAX_DYNAMIC_MOCK_DEPTH -> UtNullModel(executableId.classId) + depth > MAX_DYNAMIC_MOCK_DEPTH -> UtNullModel(methodId.classId) else -> UtCompositeModel( id = idGenerator.createId(), // TODO mockito can't mock sealed interfaces, // we have to mock their implementations or use null - classId = executableId.returnType, + classId = methodId.returnType, isMock = true, canHaveRedundantOrMissingMocks = true, ).also { dynamicMockModelToDepth[it] = depth + 1 } diff --git a/utbot-java-fuzzing/build.gradle.kts b/utbot-java-fuzzing/build.gradle.kts index 120fd58e16..eadcf81b91 100644 --- a/utbot-java-fuzzing/build.gradle.kts +++ b/utbot-java-fuzzing/build.gradle.kts @@ -1,6 +1,7 @@ val sootVersion: String by rootProject val kotlinLoggingVersion: String by rootProject val rgxgenVersion: String by rootProject +val guavaVersion: String by rootProject dependencies { implementation(project(":utbot-framework-api")) @@ -12,4 +13,5 @@ dependencies { } implementation(group = "io.github.microutils", name = "kotlin-logging", version = kotlinLoggingVersion) implementation(group = "com.github.curious-odd-man", name = "rgxgen", version = rgxgenVersion) + implementation(group = "com.google.guava", name = "guava", version = guavaVersion) } \ No newline at end of file diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/unit/Mocks.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/unit/Mocks.kt index 53902ba38b..85417456bf 100644 --- a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/unit/Mocks.kt +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/unit/Mocks.kt @@ -1,5 +1,6 @@ package org.utbot.fuzzing.spring.unit +import com.google.common.reflect.TypeResolver import mu.KotlinLogging import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.MethodId @@ -17,6 +18,9 @@ import org.utbot.fuzzing.Routine import org.utbot.fuzzing.Scope import org.utbot.fuzzing.ScopeProperty import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.spring.utils.jType +import org.utbot.fuzzing.spring.utils.toTypeParametrizedByTypeVariables +import org.utbot.fuzzing.spring.utils.typeToken import org.utbot.fuzzing.toFuzzerType val methodsToMockProperty = ScopeProperty>( @@ -28,6 +32,7 @@ class MockValueProvider(private val idGenerator: IdGenerator) : JavaValuePr companion object { private val logger = KotlinLogging.logger {} private val loggedMockedMethods = mutableSetOf() + private val loggedUnresolvedMethods = mutableSetOf() } private val methodsToMock = mutableSetOf() @@ -44,12 +49,26 @@ class MockValueProvider(private val idGenerator: IdGenerator) : JavaValuePr construct = Routine.Create(types = emptyList()) { emptyMockFuzzedValue(type.classId) }, empty = Routine.Empty { emptyMockFuzzedValue(type.classId) }, modify = (description.scope?.getProperty(methodsToMockProperty)?.asSequence() ?: emptySequence()).map { methodId -> - if (loggedMockedMethods.add(methodId)) - logger.info { "Actually mocked $methodId for the first time" } - // TODO accept `List` instead of singular `returnType` - Routine.Call(types = listOf( - toFuzzerType(methodId.method.genericReturnType, description.typeCache) - )) { instance, (value) -> + val methodDeclaringClass = methodId.classId.jClass + + val returnType = try { + TypeResolver().where( + methodDeclaringClass.toTypeParametrizedByTypeVariables(), + @Suppress("UNCHECKED_CAST") + type.jType.typeToken.getSupertype(methodDeclaringClass as Class).type + ).resolveType(methodId.method.genericReturnType) + } catch (e: Exception) { + if (loggedUnresolvedMethods.add(methodId)) + logger.error(e) { "Failed to resolve return type for $methodId, using unresolved generic type" } + + methodId.method.genericReturnType + } + + // TODO accept `List` instead of singular `resolvedReturnType` + Routine.Call(types = listOf(toFuzzerType(returnType, description.typeCache))) { instance, (value) -> + if (loggedMockedMethods.add(methodId)) + logger.info { "Actually mocked $methodId for the first time" } + (instance.model as UtCompositeModel).mocks[methodId] = listOf(value.model) } } diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/utils/TypeUtils.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/utils/TypeUtils.kt new file mode 100644 index 0000000000..56d9113c8b --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/utils/TypeUtils.kt @@ -0,0 +1,46 @@ +package org.utbot.fuzzing.spring.utils + +import com.google.common.reflect.TypeToken +import org.utbot.framework.plugin.api.util.isArray +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.fuzzer.FuzzedType +import java.lang.reflect.GenericArrayType +import java.lang.reflect.ParameterizedType +import java.lang.reflect.Type + +val Type.typeToken: TypeToken<*> get() = TypeToken.of(this) + +val FuzzedType.jType: Type get() = toType(mutableMapOf()) + +private fun FuzzedType.toType(cache: MutableMap): Type = cache.getOrPut(this) { + when { + generics.isEmpty() -> classId.jClass + classId.isArray && generics.size == 1 -> GenericArrayType { generics.single().toType(cache) } + else -> object : ParameterizedType { + override fun getActualTypeArguments(): Array = + generics.map { it.toType(cache) }.toTypedArray() + + override fun getRawType(): Type = + classId.jClass + + override fun getOwnerType(): Type? = null + } + } +} + +/** + * Returns fully parameterized type, e.g. for `Map` class + * `Map` type is returned, where `K` and `V` are type variables. + */ +fun Class<*>.toTypeParametrizedByTypeVariables(): Type = + if (typeParameters.isEmpty()) this + else object : ParameterizedType { + override fun getActualTypeArguments(): Array = + typeParameters.toList().toTypedArray() + + override fun getRawType(): Type = + this@toTypeParametrizedByTypeVariables + + override fun getOwnerType(): Type? = + declaringClass?.toTypeParametrizedByTypeVariables() + }