From e76aaff78fdb0bccf5c3a85eb742a3636807a8a6 Mon Sep 17 00:00:00 2001 From: IlyaMuravjov Date: Thu, 17 Aug 2023 11:05:41 +0300 Subject: [PATCH 01/14] Extract `JavaFuzzingContext` into separate interface --- .../org/utbot/engine/UtBotSymbolicEngine.kt | 13 ++- .../context/ConcreteExecutionContext.kt | 18 +-- .../framework/context/JavaFuzzingContext.kt | 25 ++++ .../simple/SimpleConcreteExecutionContext.kt | 26 +---- .../simple/SimpleJavaFuzzingContext.kt | 36 ++++++ ...IntegrationTestConcreteExecutionContext.kt | 108 ++--------------- ...SpringIntegrationTestJavaFuzzingContext.kt | 110 ++++++++++++++++++ 7 files changed, 190 insertions(+), 146 deletions(-) create mode 100644 utbot-framework/src/main/kotlin/org/utbot/framework/context/JavaFuzzingContext.kt create mode 100644 utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleJavaFuzzingContext.kt create mode 100644 utbot-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringIntegrationTestJavaFuzzingContext.kt 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 9ac105a6f0..d420a46601 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt @@ -439,12 +439,12 @@ class UtBotSymbolicEngine( val names = graph.body.method.tags.filterIsInstance().firstOrNull()?.names ?: emptyList() var testEmittedByFuzzer = 0 - val valueProviders = try { - concreteExecutionContext.tryCreateValueProvider(concreteExecutor, classUnderTest, defaultIdGenerator) + val fuzzingContext = try { + concreteExecutionContext.tryCreateFuzzingContext(concreteExecutor, classUnderTest, defaultIdGenerator) } catch (e: Exception) { emit(UtError(e.message ?: "Failed to create ValueProvider", e)) return@flow - }.let(transform) + } val coverageToMinStateBeforeSize = mutableMapOf, Int>() @@ -453,7 +453,7 @@ class UtBotSymbolicEngine( methodUnderTest, constants = collectConstantsForFuzzer(graph), names = names, - providers = listOf(valueProviders), + providers = listOf(transform(fuzzingContext.valueProvider)), ) { thisInstance, descr, values -> val diff = until - System.currentTimeMillis() val thresholdMillisForFuzzingOperation = 0 // may be better use 10-20 millis as it might not be possible @@ -474,12 +474,11 @@ class UtBotSymbolicEngine( return@runJavaFuzzing BaseFeedback(Trie.emptyNode(), Control.PASS) } - val stateBefore = concreteExecutionContext.createStateBefore( + val stateBefore = fuzzingContext.createStateBefore( thisInstance = thisInstance?.model, parameters = values.map { it.model }, statics = emptyMap(), executableToCall = methodUnderTest, - idGenerator = defaultIdGenerator ) val concreteExecutionResult: UtConcreteExecutionResult? = try { @@ -496,6 +495,8 @@ class UtBotSymbolicEngine( // in case an exception occurred from the concrete execution concreteExecutionResult ?: return@runJavaFuzzing BaseFeedback(result = Trie.emptyNode(), control = Control.PASS) + fuzzingContext.handleFuzzedConcreteExecutionResult(concreteExecutionResult) + // in case of processed failure in the concrete execution concreteExecutionResult.processedFailure()?.let { failure -> logger.debug { "Instrumented process failed with exception ${failure.exception} before concrete execution started" } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/context/ConcreteExecutionContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/context/ConcreteExecutionContext.kt index d998474899..04ca919530 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/context/ConcreteExecutionContext.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/context/ConcreteExecutionContext.kt @@ -2,14 +2,8 @@ package org.utbot.framework.context import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.ConcreteContextLoadingResult -import org.utbot.framework.plugin.api.EnvironmentModels -import org.utbot.framework.plugin.api.ExecutableId -import org.utbot.framework.plugin.api.FieldId import org.utbot.framework.plugin.api.UtExecution -import org.utbot.framework.plugin.api.UtModel -import org.utbot.fuzzer.IdGenerator import org.utbot.fuzzer.IdentityPreservingIdGenerator -import org.utbot.fuzzing.JavaValueProvider import org.utbot.instrumentation.ConcreteExecutor import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult import org.utbot.instrumentation.instrumentation.execution.UtExecutionInstrumentation @@ -26,17 +20,9 @@ interface ConcreteExecutionContext { classUnderTestId: ClassId ): List - fun tryCreateValueProvider( + fun tryCreateFuzzingContext( concreteExecutor: ConcreteExecutor, classUnderTest: ClassId, idGenerator: IdentityPreservingIdGenerator, - ): JavaValueProvider - - fun createStateBefore( - thisInstance: UtModel?, - parameters: List, - statics: Map, - executableToCall: ExecutableId, - idGenerator: IdGenerator - ): EnvironmentModels + ): JavaFuzzingContext } \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/context/JavaFuzzingContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/context/JavaFuzzingContext.kt new file mode 100644 index 0000000000..64219d260e --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/context/JavaFuzzingContext.kt @@ -0,0 +1,25 @@ +package org.utbot.framework.context + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.EnvironmentModels +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.UtModel +import org.utbot.fuzzer.IdentityPreservingIdGenerator +import org.utbot.fuzzing.JavaValueProvider +import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult + +interface JavaFuzzingContext { + val classUnderTest: ClassId + val idGenerator: IdentityPreservingIdGenerator + val valueProvider: JavaValueProvider + + fun createStateBefore( + thisInstance: UtModel?, + parameters: List, + statics: Map, + executableToCall: ExecutableId, + ): EnvironmentModels + + fun handleFuzzedConcreteExecutionResult(concreteExecutionResult: UtConcreteExecutionResult) +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleConcreteExecutionContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleConcreteExecutionContext.kt index 5d3b5260ec..503b6fee93 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleConcreteExecutionContext.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleConcreteExecutionContext.kt @@ -1,18 +1,11 @@ package org.utbot.framework.context.simple import org.utbot.framework.context.ConcreteExecutionContext +import org.utbot.framework.context.JavaFuzzingContext import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.ConcreteContextLoadingResult -import org.utbot.framework.plugin.api.EnvironmentModels -import org.utbot.framework.plugin.api.ExecutableId -import org.utbot.framework.plugin.api.FieldId import org.utbot.framework.plugin.api.UtExecution -import org.utbot.framework.plugin.api.UtModel -import org.utbot.fuzzer.IdGenerator import org.utbot.fuzzer.IdentityPreservingIdGenerator -import org.utbot.fuzzing.JavaValueProvider -import org.utbot.fuzzing.ValueProvider -import org.utbot.fuzzing.defaultValueProviders import org.utbot.instrumentation.ConcreteExecutor import org.utbot.instrumentation.instrumentation.execution.SimpleUtExecutionInstrumentation import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult @@ -32,22 +25,9 @@ class SimpleConcreteExecutionContext(fullClassPath: String) : ConcreteExecutionC classUnderTestId: ClassId ): List = executions - override fun tryCreateValueProvider( + override fun tryCreateFuzzingContext( concreteExecutor: ConcreteExecutor, classUnderTest: ClassId, idGenerator: IdentityPreservingIdGenerator - ): JavaValueProvider = ValueProvider.of(defaultValueProviders(idGenerator)) - - override fun createStateBefore( - thisInstance: UtModel?, - parameters: List, - statics: Map, - executableToCall: ExecutableId, - idGenerator: IdGenerator - ): EnvironmentModels = EnvironmentModels( - thisInstance = thisInstance, - parameters = parameters, - statics = statics, - executableToCall = executableToCall - ) + ): JavaFuzzingContext = SimpleJavaFuzzingContext(classUnderTest, idGenerator) } \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleJavaFuzzingContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleJavaFuzzingContext.kt new file mode 100644 index 0000000000..4c972f309c --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleJavaFuzzingContext.kt @@ -0,0 +1,36 @@ +package org.utbot.framework.context.simple + +import org.utbot.framework.context.JavaFuzzingContext +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.EnvironmentModels +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.UtModel +import org.utbot.fuzzer.IdentityPreservingIdGenerator +import org.utbot.fuzzing.JavaValueProvider +import org.utbot.fuzzing.ValueProvider +import org.utbot.fuzzing.defaultValueProviders +import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult + +class SimpleJavaFuzzingContext( + override val classUnderTest: ClassId, + override val idGenerator: IdentityPreservingIdGenerator, +) : JavaFuzzingContext { + override val valueProvider: JavaValueProvider = + ValueProvider.of(defaultValueProviders(idGenerator)) + + override fun createStateBefore( + thisInstance: UtModel?, + parameters: List, + statics: Map, + executableToCall: ExecutableId, + ): EnvironmentModels = EnvironmentModels( + thisInstance = thisInstance, + parameters = parameters, + statics = statics, + executableToCall = executableToCall + ) + + override fun handleFuzzedConcreteExecutionResult(concreteExecutionResult: UtConcreteExecutionResult) = + Unit +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringIntegrationTestConcreteExecutionContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringIntegrationTestConcreteExecutionContext.kt index e0569bd5ac..fdfd8ba2bd 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringIntegrationTestConcreteExecutionContext.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringIntegrationTestConcreteExecutionContext.kt @@ -1,38 +1,13 @@ package org.utbot.framework.context.spring import mu.KotlinLogging -import org.utbot.common.tryLoadClass import org.utbot.framework.context.ConcreteExecutionContext +import org.utbot.framework.context.JavaFuzzingContext import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.ConcreteContextLoadingResult -import org.utbot.framework.plugin.api.ConstructorId -import org.utbot.framework.plugin.api.EnvironmentModels -import org.utbot.framework.plugin.api.ExecutableId -import org.utbot.framework.plugin.api.FieldId -import org.utbot.framework.plugin.api.MethodId -import org.utbot.framework.plugin.api.SpringRepositoryId import org.utbot.framework.plugin.api.SpringSettings import org.utbot.framework.plugin.api.UtExecution -import org.utbot.framework.plugin.api.UtModel -import org.utbot.framework.plugin.api.util.SpringModelUtils -import org.utbot.framework.plugin.api.util.SpringModelUtils.createMockMvcModel -import org.utbot.framework.plugin.api.util.SpringModelUtils.createRequestBuilderModelOrNull -import org.utbot.framework.plugin.api.util.SpringModelUtils.mockMvcPerformMethodId -import org.utbot.framework.plugin.api.util.allDeclaredFieldIds -import org.utbot.framework.plugin.api.util.jField -import org.utbot.framework.plugin.api.util.utContext -import org.utbot.fuzzer.IdGenerator import org.utbot.fuzzer.IdentityPreservingIdGenerator -import org.utbot.fuzzing.JavaValueProvider -import org.utbot.fuzzing.ValueProvider -import org.utbot.fuzzing.providers.AnyDepthNullValueProvider -import org.utbot.fuzzing.spring.GeneratedFieldValueProvider -import org.utbot.fuzzing.spring.SpringBeanValueProvider -import org.utbot.fuzzing.spring.preserveProperties -import org.utbot.fuzzing.spring.valid.EmailValueProvider -import org.utbot.fuzzing.spring.valid.NotBlankStringValueProvider -import org.utbot.fuzzing.spring.valid.NotEmptyStringValueProvider -import org.utbot.fuzzing.spring.valid.ValidEntityValueProvider import org.utbot.instrumentation.ConcreteExecutor import org.utbot.instrumentation.getRelevantSpringRepositories import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult @@ -40,8 +15,6 @@ import org.utbot.instrumentation.instrumentation.execution.UtExecutionInstrument import org.utbot.instrumentation.instrumentation.spring.SpringUtExecutionInstrumentation import org.utbot.instrumentation.tryLoadingSpringContext import java.io.File -import org.utbot.fuzzing.providers.ObjectValueProvider -import org.utbot.fuzzing.providers.anyObjectValueProvider class SpringIntegrationTestConcreteExecutionContext( private val delegateContext: ConcreteExecutionContext, @@ -79,11 +52,11 @@ class SpringIntegrationTestConcreteExecutionContext( classUnderTestId: ClassId ): List = delegateContext.transformExecutionsBeforeMinimization(executions, classUnderTestId) - override fun tryCreateValueProvider( + override fun tryCreateFuzzingContext( concreteExecutor: ConcreteExecutor, classUnderTest: ClassId, idGenerator: IdentityPreservingIdGenerator - ): JavaValueProvider { + ): JavaFuzzingContext { if (springApplicationContext.getBeansAssignableTo(classUnderTest).isEmpty()) error( "No beans of type ${classUnderTest.name} are found. " + @@ -94,77 +67,10 @@ class SpringIntegrationTestConcreteExecutionContext( val relevantRepositories = concreteExecutor.getRelevantSpringRepositories(classUnderTest) logger.info { "Detected relevant repositories for class $classUnderTest: $relevantRepositories" } - val springBeanValueProvider = SpringBeanValueProvider( - idGenerator, - beanNameProvider = { classId -> - springApplicationContext.getBeansAssignableTo(classId).map { it.beanName } - }, - relevantRepositories = relevantRepositories + return SpringIntegrationTestJavaFuzzingContext( + delegateContext = delegateContext.tryCreateFuzzingContext(concreteExecutor, classUnderTest, idGenerator), + relevantRepositories = relevantRepositories, + springApplicationContext = springApplicationContext, ) - - return springBeanValueProvider - .withFallback(ValidEntityValueProvider(idGenerator, onlyAcceptWhenValidIsRequired = true)) - .withFallback(EmailValueProvider()) - .withFallback(NotBlankStringValueProvider()) - .withFallback(NotEmptyStringValueProvider()) - .withFallback( - delegateContext.tryCreateValueProvider(concreteExecutor, classUnderTest, idGenerator) - .except { p -> p is ObjectValueProvider } - .with(anyObjectValueProvider(idGenerator, shouldMutateWithMethods = true)) - .with(ValidEntityValueProvider(idGenerator, onlyAcceptWhenValidIsRequired = false)) - .with(createGeneratedFieldValueProviders(relevantRepositories, idGenerator)) - .withFallback(AnyDepthNullValueProvider) - ) - .preserveProperties() - } - - private fun createGeneratedFieldValueProviders( - relevantRepositories: Set, - idGenerator: IdentityPreservingIdGenerator - ): JavaValueProvider { - val generatedValueAnnotationClasses = SpringModelUtils.generatedValueClassIds.mapNotNull { - @Suppress("UNCHECKED_CAST") // type system fails to understand that @GeneratedValue is indeed an annotation - utContext.classLoader.tryLoadClass(it.name) as Class? - } - - val generatedValueFields = - relevantRepositories - .flatMap { springRepositoryId -> - val entityClassId = springRepositoryId.entityClassId - entityClassId.allDeclaredFieldIds - .filter { fieldId -> generatedValueAnnotationClasses.any { fieldId.jField.isAnnotationPresent(it) } } - .map { entityClassId to it } - } - - logger.info { "Detected @GeneratedValue fields: $generatedValueFields" } - - return ValueProvider.of(generatedValueFields.map { (entityClassId, fieldId) -> - GeneratedFieldValueProvider(idGenerator, entityClassId, fieldId) - }) - } - - override fun createStateBefore( - thisInstance: UtModel?, - parameters: List, - statics: Map, - executableToCall: ExecutableId, - idGenerator: IdGenerator - ): EnvironmentModels { - val delegateStateBefore = delegateContext.createStateBefore(thisInstance, parameters, statics, executableToCall, idGenerator) - return when (executableToCall) { - is ConstructorId -> delegateStateBefore - is MethodId -> { - val requestBuilderModel = createRequestBuilderModelOrNull( - methodId = executableToCall, - arguments = parameters, - idGenerator = { idGenerator.createId() } - ) ?: return delegateStateBefore - delegateStateBefore.copy( - thisInstance = createMockMvcModel { idGenerator.createId() }, - parameters = listOf(requestBuilderModel), - executableToCall = mockMvcPerformMethodId, - ) - } - } } } \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringIntegrationTestJavaFuzzingContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringIntegrationTestJavaFuzzingContext.kt new file mode 100644 index 0000000000..f94972f680 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringIntegrationTestJavaFuzzingContext.kt @@ -0,0 +1,110 @@ +package org.utbot.framework.context.spring + +import mu.KotlinLogging +import org.utbot.common.tryLoadClass +import org.utbot.framework.context.JavaFuzzingContext +import org.utbot.framework.plugin.api.ConstructorId +import org.utbot.framework.plugin.api.EnvironmentModels +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.SpringRepositoryId +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.SpringModelUtils +import org.utbot.framework.plugin.api.util.allDeclaredFieldIds +import org.utbot.framework.plugin.api.util.jField +import org.utbot.framework.plugin.api.util.utContext +import org.utbot.fuzzer.IdentityPreservingIdGenerator +import org.utbot.fuzzing.JavaValueProvider +import org.utbot.fuzzing.ValueProvider +import org.utbot.fuzzing.providers.AnyDepthNullValueProvider +import org.utbot.fuzzing.providers.ObjectValueProvider +import org.utbot.fuzzing.providers.anyObjectValueProvider +import org.utbot.fuzzing.spring.GeneratedFieldValueProvider +import org.utbot.fuzzing.spring.SpringBeanValueProvider +import org.utbot.fuzzing.spring.preserveProperties +import org.utbot.fuzzing.spring.valid.EmailValueProvider +import org.utbot.fuzzing.spring.valid.NotBlankStringValueProvider +import org.utbot.fuzzing.spring.valid.NotEmptyStringValueProvider +import org.utbot.fuzzing.spring.valid.ValidEntityValueProvider + +class SpringIntegrationTestJavaFuzzingContext( + val delegateContext: JavaFuzzingContext, + relevantRepositories: Set, + springApplicationContext: SpringApplicationContext, +) : JavaFuzzingContext by delegateContext { + companion object { + private val logger = KotlinLogging.logger {} + } + + override val valueProvider: JavaValueProvider = + SpringBeanValueProvider( + idGenerator, + beanNameProvider = { classId -> + springApplicationContext.getBeansAssignableTo(classId).map { it.beanName } + }, + relevantRepositories = relevantRepositories + ) + .withFallback(ValidEntityValueProvider(idGenerator, onlyAcceptWhenValidIsRequired = true)) + .withFallback(EmailValueProvider()) + .withFallback(NotBlankStringValueProvider()) + .withFallback(NotEmptyStringValueProvider()) + .withFallback( + delegateContext.valueProvider + .except { p -> p is ObjectValueProvider } + .with(anyObjectValueProvider(idGenerator, shouldMutateWithMethods = true)) + .with(ValidEntityValueProvider(idGenerator, onlyAcceptWhenValidIsRequired = false)) + .with(createGeneratedFieldValueProviders(relevantRepositories, idGenerator)) + .withFallback(AnyDepthNullValueProvider) + ) + .preserveProperties() + + private fun createGeneratedFieldValueProviders( + relevantRepositories: Set, + idGenerator: IdentityPreservingIdGenerator + ): JavaValueProvider { + val generatedValueAnnotationClasses = SpringModelUtils.generatedValueClassIds.mapNotNull { + @Suppress("UNCHECKED_CAST") // type system fails to understand that @GeneratedValue is indeed an annotation + utContext.classLoader.tryLoadClass(it.name) as Class? + } + + val generatedValueFields = + relevantRepositories + .flatMap { springRepositoryId -> + val entityClassId = springRepositoryId.entityClassId + entityClassId.allDeclaredFieldIds + .filter { fieldId -> generatedValueAnnotationClasses.any { fieldId.jField.isAnnotationPresent(it) } } + .map { entityClassId to it } + } + + logger.info { "Detected @GeneratedValue fields: $generatedValueFields" } + + return ValueProvider.of(generatedValueFields.map { (entityClassId, fieldId) -> + GeneratedFieldValueProvider(idGenerator, entityClassId, fieldId) + }) + } + + override fun createStateBefore( + thisInstance: UtModel?, + parameters: List, + statics: Map, + executableToCall: ExecutableId, + ): EnvironmentModels { + val delegateStateBefore = delegateContext.createStateBefore(thisInstance, parameters, statics, executableToCall) + return when (executableToCall) { + is ConstructorId -> delegateStateBefore + is MethodId -> { + val requestBuilderModel = SpringModelUtils.createRequestBuilderModelOrNull( + methodId = executableToCall, + arguments = parameters, + idGenerator = { idGenerator.createId() } + ) ?: return delegateStateBefore + delegateStateBefore.copy( + thisInstance = SpringModelUtils.createMockMvcModel { idGenerator.createId() }, + parameters = listOf(requestBuilderModel), + executableToCall = SpringModelUtils.mockMvcPerformMethodId, + ) + } + } + } +} \ No newline at end of file From 9b9a0b3fb550cabc62f4e97eeb02f83efad29fd3 Mon Sep 17 00:00:00 2001 From: IlyaMuravjov Date: Thu, 17 Aug 2023 11:18:01 +0300 Subject: [PATCH 02/14] Add mock and inject mocks value providers --- .../org/utbot/framework/plugin/api/Api.kt | 4 ++ .../utbot/fuzzing/spring/unit/InjectMocks.kt | 48 +++++++++++++ .../org/utbot/fuzzing/spring/unit/Mocks.kt | 70 +++++++++++++++++++ 3 files changed, 122 insertions(+) create mode 100644 utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/unit/InjectMocks.kt create mode 100644 utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/unit/Mocks.kt 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 2bd0ea57c7..2b82021ab1 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 @@ -498,6 +498,8 @@ data class UtClassRefModel( * - isMock flag * - calculated field values (models) * - mocks for methods with return values + * - [canHaveRedundantOrMissingMocks] flag, which is set to `true` for mocks + * created by fuzzer without knowing which methods will actually be called * * [fields] contains non-static fields */ @@ -507,6 +509,8 @@ data class UtCompositeModel( val isMock: Boolean, val fields: MutableMap = mutableMapOf(), val mocks: MutableMap> = mutableMapOf(), + // TODO handle it in instrumentation & codegen + val canHaveRedundantOrMissingMocks: Boolean = false, ) : UtReferenceModel(id, classId) { //TODO: SAT-891 - rewrite toString() method override fun toString() = withToStringThreadLocalReentrancyGuard { diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/unit/InjectMocks.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/unit/InjectMocks.kt new file mode 100644 index 0000000000..d6bb17f8b8 --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/unit/InjectMocks.kt @@ -0,0 +1,48 @@ +package org.utbot.fuzzing.spring.unit + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtCompositeModel +import org.utbot.framework.plugin.api.util.allDeclaredFieldIds +import org.utbot.framework.plugin.api.util.isFinal +import org.utbot.framework.plugin.api.util.isStatic +import org.utbot.framework.plugin.api.util.jField +import org.utbot.fuzzer.FuzzedType +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzer.IdGenerator +import org.utbot.fuzzer.fuzzed +import org.utbot.fuzzing.FuzzedDescription +import org.utbot.fuzzing.JavaValueProvider +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.toFuzzerType + +/** + * Models created by this class can be used with `@InjectMock` annotation, because + * they are [UtCompositeModel]s similar to the ones created by the symbolic engine. + */ +class InjectMockValueProvider( + private val idGenerator: IdGenerator, + private val classToUseCompositeModelFor: ClassId +) : JavaValueProvider { + override fun accept(type: FuzzedType): Boolean = type.classId == classToUseCompositeModelFor + + override fun generate(description: FuzzedDescription, type: FuzzedType): Sequence> { + val fields = type.classId.allDeclaredFieldIds.filterNot { it.isStatic && it.isFinal }.toList() + return sequenceOf(Seed.Recursive( + construct = Routine.Create(types = fields.map { toFuzzerType(it.jField.genericType, description.typeCache) }) { values -> + emptyFuzzedValue(type.classId).also { + (it.model as UtCompositeModel).fields.putAll( + fields.zip(values).associate { (field, value) -> field to value.model } + ) + } + }, + empty = Routine.Empty { emptyFuzzedValue(type.classId) } + )) + } + + private fun emptyFuzzedValue(classId: ClassId) = UtCompositeModel( + id = idGenerator.createId(), + classId = classId, + isMock = false, + ).fuzzed { summary = "%var% = ${classId.simpleName}()" } +} \ 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 new file mode 100644 index 0000000000..53902ba38b --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/unit/Mocks.kt @@ -0,0 +1,70 @@ +package org.utbot.fuzzing.spring.unit + +import mu.KotlinLogging +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.UtCompositeModel +import org.utbot.framework.plugin.api.util.executableId +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.method +import org.utbot.fuzzer.FuzzedType +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzer.IdGenerator +import org.utbot.fuzzer.fuzzed +import org.utbot.fuzzing.FuzzedDescription +import org.utbot.fuzzing.JavaValueProvider +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Scope +import org.utbot.fuzzing.ScopeProperty +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.toFuzzerType + +val methodsToMockProperty = ScopeProperty>( + description = "Method ids that can be mocked by `MockValueProvider`" +) + +// TODO shouldn't be used for primitives and other "easy" to create types +class MockValueProvider(private val idGenerator: IdGenerator) : JavaValueProvider { + companion object { + private val logger = KotlinLogging.logger {} + private val loggedMockedMethods = mutableSetOf() + } + + private val methodsToMock = mutableSetOf() + + override fun enrich(description: FuzzedDescription, type: FuzzedType, scope: Scope) { + val publicMethods = type.classId.jClass.methods.map { it.executableId } + publicMethods.intersect(methodsToMock).takeIf { it.isNotEmpty() }?.let { + scope.putProperty(methodsToMockProperty, it) + } + } + + override fun generate(description: FuzzedDescription, type: FuzzedType): Sequence> = + sequenceOf(Seed.Recursive( + 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) -> + (instance.model as UtCompositeModel).mocks[methodId] = listOf(value.model) + } + } + )) + + private fun emptyMockFuzzedValue(classId: ClassId) = UtCompositeModel( + id = idGenerator.createId(), + classId = classId, + isMock = true, + canHaveRedundantOrMissingMocks = true, + ).fuzzed { summary = "%var% = mock()" } + + fun addMockingCandidates(detectedMockingCandidates: Set) = + detectedMockingCandidates.forEach { methodId -> + if (methodsToMock.add(methodId)) + logger.info { "Detected that $methodId may need mocking" } + } +} \ No newline at end of file From 66470e05f8afdeeb23b9452c651eb61252ff5546 Mon Sep 17 00:00:00 2001 From: IlyaMuravjov Date: Thu, 17 Aug 2023 17:16:49 +0300 Subject: [PATCH 03/14] Add `StateBeforeAwareIdGenerator` --- .../StateBeforeAwareIdGenerator.kt | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/StateBeforeAwareIdGenerator.kt diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/StateBeforeAwareIdGenerator.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/StateBeforeAwareIdGenerator.kt new file mode 100644 index 0000000000..cc54b1d4c6 --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/StateBeforeAwareIdGenerator.kt @@ -0,0 +1,103 @@ +package org.utbot.instrumentation.instrumentation.execution.constructors + +import org.utbot.framework.plugin.api.UtArrayModel +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtClassRefModel +import org.utbot.framework.plugin.api.UtCompositeModel +import org.utbot.framework.plugin.api.UtCustomModel +import org.utbot.framework.plugin.api.UtDirectSetFieldModel +import org.utbot.framework.plugin.api.UtEnumConstantModel +import org.utbot.framework.plugin.api.UtLambdaModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNewInstanceInstrumentation +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.plugin.api.UtReferenceModel +import org.utbot.framework.plugin.api.UtStatementCallModel +import org.utbot.framework.plugin.api.UtStatementModel +import org.utbot.framework.plugin.api.UtStaticMethodInstrumentation +import org.utbot.framework.plugin.api.UtVoidModel +import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionData +import java.util.* + +class StateBeforeAwareIdGenerator(preExistingModels: Collection) { + private val seenIds = mutableSetOf() + + // there's no `IdentityHashSet`, so we use `IdentityHashMap` with dummy values + private val seenModels = IdentityHashMap() + + private var nextId = 0 + + init { + collectIds(preExistingModels) + } + + private fun collectIds(models: Collection) = + models.forEach { collectIds(it) } + + private fun collectIds(model: UtModel) { + if (model !in seenModels) { + seenModels[model] = Unit + (model as? UtReferenceModel)?.id?.let { seenIds.add(it) } + when (model) { + is UtNullModel, + is UtPrimitiveModel, + is UtEnumConstantModel, + is UtClassRefModel, + is UtVoidModel -> {} + is UtCompositeModel -> { + collectIds(model.fields.values) + model.mocks.values.forEach { collectIds(it) } + } + is UtArrayModel -> { + collectIds(model.constModel) + collectIds(model.stores.values) + } + is UtAssembleModel -> { + model.origin?.let { collectIds(it) } + collectIds(model.instantiationCall) + model.modificationsChain.forEach { collectIds(it) } + } + is UtCustomModel -> { + model.origin?.let { collectIds(it) } + collectIds(model.dependencies) + } + is UtLambdaModel -> { + collectIds(model.capturedValues) + } + else -> error("Can't collect ids from $model") + } + } + } + + private fun collectIds(call: UtStatementModel): Unit = when (call) { + is UtStatementCallModel -> { + call.instance?.let { collectIds(it) } + collectIds(call.params) + } + is UtDirectSetFieldModel -> { + collectIds(call.instance) + collectIds(call.fieldModel) + } + } + + fun createId(): Int { + while (nextId in seenIds) nextId++ + return nextId++ + } + + companion object { + fun fromUtConcreteExecutionData(data: UtConcreteExecutionData): StateBeforeAwareIdGenerator = + StateBeforeAwareIdGenerator( + listOfNotNull(data.stateBefore.thisInstance) + + data.stateBefore.parameters + + data.stateBefore.statics.values + + data.instrumentation.flatMap { + when (it) { + is UtNewInstanceInstrumentation -> it.instances + is UtStaticMethodInstrumentation -> it.values + } + } + ) + } +} \ No newline at end of file From 63a7edacfcf3ed183b7893bfcc50072349d89e14 Mon Sep 17 00:00:00 2001 From: IlyaMuravjov Date: Thu, 17 Aug 2023 16:55:54 +0300 Subject: [PATCH 04/14] Properly handle `canHaveRedundantOrMissingMocks` in instrumentation --- .../org/utbot/framework/plugin/api/Api.kt | 13 ++- .../util/UtConcreteExecutionResultUtils.kt | 9 +- .../SimpleUtExecutionInstrumentation.kt | 14 ++- .../execution/UtExecutionInstrumentation.kt | 21 ++-- ...rumentationContextAwareValueConstructor.kt | 107 +++++++++++++++++- .../constructors/UtModelConstructor.kt | 21 ++-- .../phases/ModelConstructionPhase.kt | 3 + .../execution/phases/PhasesController.kt | 12 +- .../phases/ValueConstructionPhase.kt | 10 +- 9 files changed, 167 insertions(+), 43 deletions(-) diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt index 2b82021ab1..c8e6346fac 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 @@ -509,7 +509,6 @@ data class UtCompositeModel( val isMock: Boolean, val fields: MutableMap = mutableMapOf(), val mocks: MutableMap> = mutableMapOf(), - // TODO handle it in instrumentation & codegen val canHaveRedundantOrMissingMocks: Boolean = false, ) : UtReferenceModel(id, classId) { //TODO: SAT-891 - rewrite toString() method @@ -767,13 +766,17 @@ abstract class UtCustomModel( classId: ClassId, modelName: String = id.toString(), override val origin: UtCompositeModel? = null, -) : UtModelWithCompositeOrigin(id, classId, modelName, origin) +) : UtModelWithCompositeOrigin(id, classId, modelName, origin) { + abstract val dependencies: Collection +} object UtSpringContextModel : UtCustomModel( id = null, classId = SpringModelUtils.applicationContextClassId, modelName = "applicationContext" ) { + override val dependencies: Collection get() = emptySet() + // NOTE that overriding equals is required just because without it // we will lose equality for objects after deserialization override fun equals(other: Any?): Boolean = other is UtSpringContextModel @@ -786,6 +789,8 @@ class UtSpringEntityManagerModel : UtCustomModel( classId = SpringModelUtils.entityManagerClassIds.first(), modelName = "entityManager" ) { + override val dependencies: Collection get() = emptySet() + // NOTE that overriding equals is required just because without it // we will lose equality for objects after deserialization override fun equals(other: Any?): Boolean = other is UtSpringEntityManagerModel @@ -814,7 +819,9 @@ data class UtSpringMockMvcResultActionsModel( classId = SpringModelUtils.resultActionsClassId, id = id, modelName = "mockMvcResultActions@$id" -) +) { + override val dependencies: Collection get() = emptySet() +} /** * Model for a step to obtain [UtAssembleModel]. diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/util/UtConcreteExecutionResultUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/util/UtConcreteExecutionResultUtils.kt index 94e4b5deb0..952512ca6e 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/util/UtConcreteExecutionResultUtils.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/util/UtConcreteExecutionResultUtils.kt @@ -21,12 +21,9 @@ private fun UtConcreteExecutionResult.updateWithAssembleModels( val resolvedResult = (result as? UtExecutionSuccess)?.model?.let { UtExecutionSuccess(toAssemble(it)) } ?: result - return UtConcreteExecutionResult( - stateBefore, - resolvedStateAfter, - resolvedResult, - coverage, - newInstrumentation + return copy( + stateAfter = resolvedStateAfter, + result = resolvedResult, ) } diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/SimpleUtExecutionInstrumentation.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/SimpleUtExecutionInstrumentation.kt index c3f8bcce7f..bfbdabb159 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/SimpleUtExecutionInstrumentation.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/SimpleUtExecutionInstrumentation.kt @@ -2,6 +2,7 @@ package org.utbot.instrumentation.instrumentation.execution import org.utbot.framework.plugin.api.EnvironmentModels import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.MethodId import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.util.executable import org.utbot.framework.plugin.api.util.signature @@ -11,6 +12,7 @@ import org.utbot.instrumentation.instrumentation.ArgumentList import org.utbot.instrumentation.instrumentation.InvokeInstrumentation import org.utbot.instrumentation.instrumentation.et.TraceHandler import org.utbot.instrumentation.instrumentation.execution.constructors.ConstructOnlyUserClassesOrCachedObjectsStrategy +import org.utbot.instrumentation.instrumentation.execution.constructors.StateBeforeAwareIdGenerator import org.utbot.instrumentation.instrumentation.execution.constructors.UtModelConstructor import org.utbot.instrumentation.instrumentation.execution.context.InstrumentationContext import org.utbot.instrumentation.instrumentation.execution.context.SimpleInstrumentationContext @@ -53,12 +55,17 @@ class SimpleUtExecutionInstrumentation( } val (stateBefore, instrumentations, timeout) = parameters // smart cast to UtConcreteExecutionData + lateinit var detectedMockingCandidates: Set + return PhasesController( instrumentationContext, traceHandler, delegateInstrumentation, - timeout + timeout, + idGenerator = StateBeforeAwareIdGenerator.fromUtConcreteExecutionData(parameters) ).computeConcreteExecutionResult { + detectedMockingCandidates = valueConstructionPhase.detectedMockingCandidates + phasesWrapper { try { // some preparation actions for concrete execution @@ -126,7 +133,10 @@ class SimpleUtExecutionInstrumentation( applyPostprocessing() } } - }.toCompleteUtConcreteExecutionResult(stateBefore = stateBefore) + }.toCompleteUtConcreteExecutionResult( + stateBefore = stateBefore, + detectedMockingCandidates = detectedMockingCandidates, + ) } override fun getStaticField(fieldId: FieldId): Result = diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/UtExecutionInstrumentation.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/UtExecutionInstrumentation.kt index 3242a9198a..3fe6d66360 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/UtExecutionInstrumentation.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/UtExecutionInstrumentation.kt @@ -24,8 +24,8 @@ data class UtConcreteExecutionData( /** * [UtConcreteExecutionResult] that has not yet been populated with extra data, e.g.: - * - updated `stateBefore: EnvironmentModels` - * - `detectedMockingCandidates: Set` (not yet implemented, see #2321) + * - updated [UtConcreteExecutionResult.stateBefore] + * - [UtConcreteExecutionResult.detectedMockingCandidates] */ data class PreliminaryUtConcreteExecutionResult( val stateAfter: EnvironmentModels, @@ -33,12 +33,16 @@ data class PreliminaryUtConcreteExecutionResult( val coverage: Coverage, val newInstrumentation: List? = null, ) { - fun toCompleteUtConcreteExecutionResult(stateBefore: EnvironmentModels) = UtConcreteExecutionResult( - stateBefore, - stateAfter, - result, - coverage, - newInstrumentation, + fun toCompleteUtConcreteExecutionResult( + stateBefore: EnvironmentModels, + detectedMockingCandidates: Set + ) = UtConcreteExecutionResult( + stateBefore = stateBefore, + stateAfter = stateAfter, + result = result, + coverage = coverage, + newInstrumentation = newInstrumentation, + detectedMockingCandidates = detectedMockingCandidates, ) } @@ -48,6 +52,7 @@ data class UtConcreteExecutionResult( val result: UtExecutionResult, val coverage: Coverage, val newInstrumentation: List? = null, + val detectedMockingCandidates: Set, ) { override fun toString(): String = buildString { appendLine("UtConcreteExecutionResult(") 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 a96f2efe95..aea8f55c2d 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 @@ -30,25 +30,32 @@ import org.utbot.framework.plugin.api.UtReferenceModel import org.utbot.framework.plugin.api.UtStatementCallModel import org.utbot.framework.plugin.api.UtStaticMethodInstrumentation import org.utbot.framework.plugin.api.UtVoidModel +import org.utbot.framework.plugin.api.isNull import org.utbot.framework.plugin.api.util.anyInstance +import org.utbot.framework.plugin.api.util.classClassId import org.utbot.framework.plugin.api.util.constructor import org.utbot.framework.plugin.api.util.constructor.CapturedArgument import org.utbot.framework.plugin.api.util.constructor.constructLambda import org.utbot.framework.plugin.api.util.constructor.constructStaticLambda +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.isStatic import org.utbot.framework.plugin.api.util.jClass import org.utbot.framework.plugin.api.util.jField import org.utbot.framework.plugin.api.util.method +import org.utbot.framework.plugin.api.util.stringClassId import org.utbot.framework.plugin.api.util.utContext -import org.utbot.instrumentation.instrumentation.execution.mock.InstanceMockController import org.utbot.instrumentation.instrumentation.execution.context.InstrumentationContext +import org.utbot.instrumentation.instrumentation.execution.mock.InstanceMockController import org.utbot.instrumentation.instrumentation.execution.mock.MethodMockController import org.utbot.instrumentation.instrumentation.execution.mock.MockController import org.utbot.instrumentation.process.runSandbox +import java.lang.reflect.Method import java.lang.reflect.Modifier +import java.security.AccessController +import java.security.PrivilegedAction import java.util.* -import org.utbot.framework.plugin.api.util.id import kotlin.reflect.KClass /** @@ -63,7 +70,8 @@ import kotlin.reflect.KClass */ // TODO: JIRA:1379 -- Refactor ValueConstructor and InstrumentationContextAwareValueConstructor class InstrumentationContextAwareValueConstructor( - private val instrumentationContext: InstrumentationContext + private val instrumentationContext: InstrumentationContext, + private val idGenerator: StateBeforeAwareIdGenerator, ) { private val classLoader: ClassLoader get() = utContext.classLoader @@ -77,6 +85,8 @@ class InstrumentationContextAwareValueConstructor( return objectToModel } + val detectedMockingCandidates: MutableSet = mutableSetOf() + // TODO: JIRA:1379 -- replace UtReferenceModel with Int private val constructedObjects = HashMap() @@ -146,7 +156,7 @@ class InstrumentationContextAwareValueConstructor( notMockInstance } else { val concreteValues = model.mocks.mapValues { mutableListOf() } - val mockInstance = generateMockitoMock(javaClass, concreteValues) + val mockInstance = generateMockitoMock(javaClass, concreteValues, model) constructedObjects[model] = mockInstance @@ -157,6 +167,12 @@ class InstrumentationContextAwareValueConstructor( valuesList.addAll(constructedValues) } + if (model.canHaveRedundantOrMissingMocks) { + // we clear `mocks` to avoid redundant mocks, + // actually useful mocks should be later added back as they are used + model.mocks.clear() + } + mockInstance } @@ -194,8 +210,87 @@ class InstrumentationContextAwareValueConstructor( } } - private fun generateMockitoMock(clazz: Class<*>, concreteValues: Map>): Any { - val answer = generateMockitoAnswer(concreteValues) + private fun generateMockitoAnswerHandlingRedundantAndMissingMocks( + concreteValues: Map>, + mockModel: UtCompositeModel + ): Answer<*> { + class MockedExecutable( + val executableId: ExecutableId, + val answerValues: List, + val answerModels: List, + ) { + private var pointer: Int = 0 + + fun nextAnswer(): Any? { + val answerValue = answerValues[pointer] + val answerModel = answerModels[pointer] + pointer = (pointer + 1) % answerValues.size + (mockModel.mocks.getOrPut(executableId) { mutableListOf() } as MutableList).add(answerModel) + return answerValue + } + } + + val mockedExecutables = concreteValues.mapValues { (executableId, values) -> + MockedExecutable( + executableId = executableId, + answerValues = values, + answerModels = mockModel.mocks.getValue(executableId), + ) + }.toMutableMap() + + return Answer { invocation -> + with(invocation.method) { + mockedExecutables.getOrPut(executableId) { + detectedMockingCandidates.add(executableId) + val answerModel = generateNewAnswerModel() + + MockedExecutable( + executableId = executableId, + answerValues = listOf( + // `construct()` heavily uses reflection and Mockito, + // so it can't run in sandbox and we need to elevate permissions + AccessController.doPrivileged(PrivilegedAction { construct(answerModel) }) + .value.takeUnless { it == Unit } + ), + answerModels = listOf(answerModel) + ) + }.nextAnswer() + } + } + } + + private fun Method.generateNewAnswerModel() = + executableId.returnType.defaultValueModel().takeUnless { it.isNull() } ?: when (executableId.returnType) { + // mockito can't mock `String` and `Class` + stringClassId -> UtNullModel(stringClassId) + classClassId -> UtClassRefModel( + id = idGenerator.createId(), + classId = classClassId, + value = classClassId, + ) + + else -> UtCompositeModel( + id = idGenerator.createId(), + // TODO mockito can't mock sealed interfaces, + // we have to mock their implementations or use null + classId = executableId.returnType, + isMock = true, + canHaveRedundantOrMissingMocks = true, + ) + } + + private fun generateMockitoMock( + clazz: Class<*>, + concreteValues: Map>, + mockModel: UtCompositeModel + ): Any { + val answer = + if (mockModel.canHaveRedundantOrMissingMocks) { + generateMockitoAnswerHandlingRedundantAndMissingMocks(concreteValues, mockModel) + } else { + generateMockitoAnswer(concreteValues) + } + return Mockito.mock(clazz, answer) } diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/UtModelConstructor.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/UtModelConstructor.kt index 5a33211130..e739432f5b 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/UtModelConstructor.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/UtModelConstructor.kt @@ -33,18 +33,13 @@ interface UtModelConstructorInterface { */ class UtModelConstructor( private val objectToModelCache: IdentityHashMap, + private val idGenerator: StateBeforeAwareIdGenerator, private val utModelWithCompositeOriginConstructorFinder: (ClassId) -> UtModelWithCompositeOriginConstructor?, private val compositeModelStrategy: UtCompositeModelStrategy = AlwaysConstructStrategy, private val maxDepth: Long = DEFAULT_MAX_DEPTH ) : UtModelConstructorInterface { private val constructedObjects = IdentityHashMap() - private var unusedId = 0 - private val usedIds = objectToModelCache.values - .filterIsInstance() - .mapNotNull { it.id } - .toMutableSet() - companion object { private const val DEFAULT_MAX_DEPTH = 7L @@ -56,16 +51,16 @@ class UtModelConstructor( val strategy = ConstructOnlyUserClassesOrCachedObjectsStrategy( pathsToUserClasses, cache ) - return UtModelConstructor(cache, utModelWithCompositeOriginConstructorFinder, strategy) + return UtModelConstructor( + objectToModelCache = cache, + idGenerator = StateBeforeAwareIdGenerator(preExistingModels = emptySet()), + utModelWithCompositeOriginConstructorFinder = utModelWithCompositeOriginConstructorFinder, + compositeModelStrategy = strategy + ) } } - private fun computeUnusedIdAndUpdate(): Int { - while (unusedId in usedIds) { - unusedId++ - } - return unusedId.also { usedIds += it } - } + private fun computeUnusedIdAndUpdate(): Int = idGenerator.createId() private fun handleId(value: Any): Int { return objectToModelCache[value]?.let { (it as? UtReferenceModel)?.id } ?: computeUnusedIdAndUpdate() diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/ModelConstructionPhase.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/ModelConstructionPhase.kt index 44c16bd09f..188915ff3b 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/ModelConstructionPhase.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/ModelConstructionPhase.kt @@ -8,6 +8,7 @@ import org.utbot.framework.plugin.api.visible.UtStreamConsumingException import org.utbot.instrumentation.instrumentation.et.ExplicitThrowInstruction import org.utbot.instrumentation.instrumentation.et.TraceHandler import org.utbot.instrumentation.instrumentation.execution.PreliminaryUtConcreteExecutionResult +import org.utbot.instrumentation.instrumentation.execution.constructors.StateBeforeAwareIdGenerator import org.utbot.instrumentation.instrumentation.execution.constructors.UtCompositeModelStrategy import org.utbot.instrumentation.instrumentation.execution.constructors.UtModelWithCompositeOriginConstructor import org.utbot.instrumentation.instrumentation.execution.constructors.UtModelConstructor @@ -20,6 +21,7 @@ import java.util.* class ModelConstructionPhase( private val traceHandler: TraceHandler, private val utModelWithCompositeOriginConstructorFinder: (ClassId) -> UtModelWithCompositeOriginConstructor?, + private val idGenerator: StateBeforeAwareIdGenerator, ) : ExecutionPhase { override fun wrapError(e: Throwable): ExecutionPhaseException { @@ -52,6 +54,7 @@ class ModelConstructionPhase( objectToModelCache = cache, utModelWithCompositeOriginConstructorFinder = utModelWithCompositeOriginConstructorFinder, compositeModelStrategy = strategy, + idGenerator = idGenerator, ) } } diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/PhasesController.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/PhasesController.kt index a571ebcc98..c23ed675da 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/PhasesController.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/PhasesController.kt @@ -14,6 +14,7 @@ import org.utbot.instrumentation.instrumentation.Instrumentation import org.utbot.instrumentation.instrumentation.et.TraceHandler import org.utbot.instrumentation.instrumentation.execution.PreliminaryUtConcreteExecutionResult import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionData +import org.utbot.instrumentation.instrumentation.execution.constructors.StateBeforeAwareIdGenerator import org.utbot.instrumentation.instrumentation.execution.context.InstrumentationContext import java.security.AccessControlException @@ -21,10 +22,14 @@ class PhasesController( private val instrumentationContext: InstrumentationContext, traceHandler: TraceHandler, delegateInstrumentation: Instrumentation>, - private val timeout: Long + private val timeout: Long, + idGenerator: StateBeforeAwareIdGenerator, ) { private var currentlyElapsed = 0L - val valueConstructionPhase = ValueConstructionPhase(instrumentationContext) + val valueConstructionPhase = ValueConstructionPhase( + instrumentationContext, + idGenerator, + ) val preparationPhase = PreparationPhase(traceHandler) @@ -34,7 +39,8 @@ class PhasesController( val modelConstructionPhase = ModelConstructionPhase( traceHandler = traceHandler, - utModelWithCompositeOriginConstructorFinder = instrumentationContext::findUtModelWithCompositeOriginConstructor + utModelWithCompositeOriginConstructorFinder = instrumentationContext::findUtModelWithCompositeOriginConstructor, + idGenerator = idGenerator, ) val postprocessingPhase = PostprocessingPhase() diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/ValueConstructionPhase.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/ValueConstructionPhase.kt index 87f1431c48..55e322bfad 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/ValueConstructionPhase.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/ValueConstructionPhase.kt @@ -6,6 +6,7 @@ import org.utbot.instrumentation.instrumentation.execution.constructors.Instrume import org.utbot.instrumentation.instrumentation.execution.context.InstrumentationContext import org.utbot.framework.plugin.api.util.isInaccessibleViaReflection import org.utbot.instrumentation.instrumentation.execution.PreliminaryUtConcreteExecutionResult +import org.utbot.instrumentation.instrumentation.execution.constructors.StateBeforeAwareIdGenerator typealias ConstructedParameters = List> typealias ConstructedStatics = Map> @@ -21,7 +22,8 @@ data class ConstructedData( * This phase of values instantiation from given models. */ class ValueConstructionPhase( - instrumentationContext: InstrumentationContext + instrumentationContext: InstrumentationContext, + idGenerator: StateBeforeAwareIdGenerator, ) : ExecutionPhase { override fun wrapError(e: Throwable): ExecutionPhaseException = ExecutionPhaseStop( @@ -36,7 +38,11 @@ class ValueConstructionPhase( ) ) - private val constructor = InstrumentationContextAwareValueConstructor(instrumentationContext) + private val constructor = InstrumentationContextAwareValueConstructor( + instrumentationContext, + idGenerator, + ) + val detectedMockingCandidates: Set get() = constructor.detectedMockingCandidates fun getCache(): ConstructedCache { return constructor.objectToModelCache From 00cca37f0e6fb1c40e6d717057f34d9b7c402fe0 Mon Sep 17 00:00:00 2001 From: IlyaMuravjov Date: Thu, 17 Aug 2023 17:01:34 +0300 Subject: [PATCH 05/14] Properly create `void` models (for mocking `void` methods) --- .../kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt | 2 ++ .../src/main/kotlin/org/utbot/fuzzing/JavaLanguage.kt | 1 + .../main/kotlin/org/utbot/fuzzing/providers/Primitives.kt | 6 ++++++ 3 files changed, 9 insertions(+) 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 fb4527445c..45382fc1ab 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 @@ -11,6 +11,7 @@ import org.utbot.framework.plugin.api.MethodId 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.UtVoidModel import org.utbot.framework.plugin.api.id import soot.SootField import java.lang.reflect.Constructor @@ -449,6 +450,7 @@ fun ClassId.defaultValueModel(): UtModel = when (this) { doubleClassId -> UtPrimitiveModel(0.0) booleanClassId -> UtPrimitiveModel(false) charClassId -> UtPrimitiveModel('\u0000') + voidClassId -> UtVoidModel else -> UtNullModel(this) } diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/JavaLanguage.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/JavaLanguage.kt index 8f4904816a..db340a501f 100644 --- a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/JavaLanguage.kt +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/JavaLanguage.kt @@ -50,6 +50,7 @@ fun defaultValueProviders(idGenerator: IdentityPreservingIdGenerator) = lis IteratorValueProvider(idGenerator), EmptyCollectionValueProvider(idGenerator), DateValueProvider(idGenerator), + VoidValueProvider, NullValueProvider, ) diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Primitives.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Primitives.kt index 6f251ea0c3..3995a32b84 100644 --- a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Primitives.kt +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Primitives.kt @@ -2,6 +2,7 @@ package org.utbot.fuzzing.providers import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.plugin.api.UtVoidModel import org.utbot.framework.plugin.api.util.* import org.utbot.fuzzer.FuzzedContext import org.utbot.fuzzer.FuzzedContext.Comparison.* @@ -236,4 +237,9 @@ object StringValueProvider : PrimitiveValueProvider(stringClassId, java.lang.Cha else -> false } } +} + +object VoidValueProvider : PrimitiveValueProvider(voidClassId) { + override fun generate(description: FuzzedDescription, type: FuzzedType): Sequence> = + sequenceOf(Seed.Simple(UtVoidModel.fuzzed { summary = "%var% = void" })) } \ No newline at end of file From 91c604b19aa17e2943ec7a28bac255bef15d1598 Mon Sep 17 00:00:00 2001 From: IlyaMuravjov Date: Thu, 17 Aug 2023 17:01:52 +0300 Subject: [PATCH 06/14] Add utils to ease configuring `ApplicationContext` --- .../context/utils/ApplicationContextUtils.kt | 22 ++++++++++++++++ .../utils/ConcreteExecutionContextUtils.kt | 26 +++++++++++++++++++ .../context/utils/JavaFuzzingContextUtils.kt | 15 +++++++++++ 3 files changed, 63 insertions(+) create mode 100644 utbot-framework/src/main/kotlin/org/utbot/framework/context/utils/ApplicationContextUtils.kt create mode 100644 utbot-framework/src/main/kotlin/org/utbot/framework/context/utils/ConcreteExecutionContextUtils.kt create mode 100644 utbot-framework/src/main/kotlin/org/utbot/framework/context/utils/JavaFuzzingContextUtils.kt diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/context/utils/ApplicationContextUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/context/utils/ApplicationContextUtils.kt new file mode 100644 index 0000000000..bcb734457a --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/context/utils/ApplicationContextUtils.kt @@ -0,0 +1,22 @@ +package org.utbot.framework.context.utils + +import org.utbot.framework.context.ApplicationContext +import org.utbot.framework.context.ConcreteExecutionContext +import org.utbot.fuzzing.JavaValueProvider + +fun ApplicationContext.transformConcreteExecutionContext( + transformer: (ConcreteExecutionContext) -> ConcreteExecutionContext +) = object : ApplicationContext by this { + override fun createConcreteExecutionContext( + fullClasspath: String, + classpathWithoutDependencies: String + ): ConcreteExecutionContext = transformer( + this@transformConcreteExecutionContext.createConcreteExecutionContext( + fullClasspath, classpathWithoutDependencies + ) + ) +} + +fun ApplicationContext.transformValueProvider( + transformer: (JavaValueProvider) -> JavaValueProvider +) = transformConcreteExecutionContext { it.transformValueProvider(transformer) } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/context/utils/ConcreteExecutionContextUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/context/utils/ConcreteExecutionContextUtils.kt new file mode 100644 index 0000000000..7ae25f10a9 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/context/utils/ConcreteExecutionContextUtils.kt @@ -0,0 +1,26 @@ +package org.utbot.framework.context.utils + +import org.utbot.framework.context.ConcreteExecutionContext +import org.utbot.framework.context.JavaFuzzingContext +import org.utbot.framework.plugin.api.ClassId +import org.utbot.fuzzer.IdentityPreservingIdGenerator +import org.utbot.fuzzing.JavaValueProvider +import org.utbot.instrumentation.ConcreteExecutor +import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult +import org.utbot.instrumentation.instrumentation.execution.UtExecutionInstrumentation + +fun ConcreteExecutionContext.transformJavaFuzzingContext( + transformer: (JavaFuzzingContext) -> JavaFuzzingContext +) = object : ConcreteExecutionContext by this { + override fun tryCreateFuzzingContext( + concreteExecutor: ConcreteExecutor, + classUnderTest: ClassId, + idGenerator: IdentityPreservingIdGenerator + ): JavaFuzzingContext = transformer( + this@transformJavaFuzzingContext.tryCreateFuzzingContext(concreteExecutor, classUnderTest, idGenerator) + ) +} + +fun ConcreteExecutionContext.transformValueProvider( + transformer: (JavaValueProvider) -> JavaValueProvider +) = transformJavaFuzzingContext { it.transformValueProvider(transformer) } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/context/utils/JavaFuzzingContextUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/context/utils/JavaFuzzingContextUtils.kt new file mode 100644 index 0000000000..4f4cf0714d --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/context/utils/JavaFuzzingContextUtils.kt @@ -0,0 +1,15 @@ +package org.utbot.framework.context.utils + +import org.utbot.framework.context.JavaFuzzingContext +import org.utbot.fuzzing.JavaValueProvider + +fun JavaFuzzingContext.transformValueProvider( + transformer: (JavaValueProvider) -> JavaValueProvider +) = object : JavaFuzzingContext by this { + override val valueProvider: JavaValueProvider = + transformer(this@transformValueProvider.valueProvider) +} + +fun JavaFuzzingContext.withValueProvider( + valueProvider: JavaValueProvider +) = transformValueProvider { it.with(valueProvider) } \ No newline at end of file From 74050505e37d33795066c46f1721a568b98b4779 Mon Sep 17 00:00:00 2001 From: IlyaMuravjov Date: Thu, 17 Aug 2023 17:02:23 +0300 Subject: [PATCH 07/14] Configure Spring unit tests to use fuzzer with mocks --- .../custom/MockingJavaFuzzingContext.kt | 42 +++++++++++++++++++ .../spring/SpringApplicationContextImpl.kt | 15 ++++++- .../generator/UtTestsDialogProcessor.kt | 2 +- 3 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 utbot-framework/src/main/kotlin/org/utbot/framework/context/custom/MockingJavaFuzzingContext.kt diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/context/custom/MockingJavaFuzzingContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/context/custom/MockingJavaFuzzingContext.kt new file mode 100644 index 0000000000..cb50b7a861 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/context/custom/MockingJavaFuzzingContext.kt @@ -0,0 +1,42 @@ +package org.utbot.framework.context.custom + +import org.utbot.framework.context.JavaFuzzingContext +import org.utbot.fuzzing.JavaValueProvider +import org.utbot.fuzzing.providers.MapValueProvider +import org.utbot.fuzzing.spring.unit.MockValueProvider +import org.utbot.fuzzing.providers.NullValueProvider +import org.utbot.fuzzing.providers.ObjectValueProvider +import org.utbot.fuzzing.providers.StringValueProvider +import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult + +/** + * Makes fuzzer mock all types that don't have *specific* [JavaValueProvider], + * like [MapValueProvider] or [StringValueProvider]. + * + * NOTE: the caller is responsible for providing some *specific* [JavaValueProvider] + * that can create values for class under test (otherwise it will be mocked), + * [ObjectValueProvider] and [NullValueProvider] do not count as *specific*. + */ +fun JavaFuzzingContext.mockAllTypesWithoutSpecificValueProvider() = + MockingJavaFuzzingContext(delegateContext = this) + +class MockingJavaFuzzingContext( + val delegateContext: JavaFuzzingContext +) : JavaFuzzingContext by delegateContext { + private val mockValueProvider = MockValueProvider(delegateContext.idGenerator) + + override val valueProvider: JavaValueProvider = + // NOTE: we first remove `NullValueProvider` from `delegateContext.valueProvider` and then + // add it back as a part of our `withFallback` so it has the same priority as + // `mockValueProvider`, otherwise mocks will never be used where `null` can be used. + delegateContext.valueProvider + .except { it is NullValueProvider } + .except { it is ObjectValueProvider } + .withFallback( + mockValueProvider + .with(NullValueProvider) + ) + + override fun handleFuzzedConcreteExecutionResult(concreteExecutionResult: UtConcreteExecutionResult) = + mockValueProvider.addMockingCandidates(concreteExecutionResult.detectedMockingCandidates) +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringApplicationContextImpl.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringApplicationContextImpl.kt index 4e9bf11553..f17bf1086a 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringApplicationContextImpl.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringApplicationContextImpl.kt @@ -11,6 +11,9 @@ import org.utbot.framework.context.ConcreteExecutionContext import org.utbot.framework.context.NonNullSpeculator import org.utbot.framework.context.TypeReplacer import org.utbot.framework.context.custom.CoverageFilteringConcreteExecutionContext +import org.utbot.framework.context.custom.mockAllTypesWithoutSpecificValueProvider +import org.utbot.framework.context.utils.transformJavaFuzzingContext +import org.utbot.framework.context.utils.withValueProvider import org.utbot.framework.plugin.api.BeanDefinitionData import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.ConcreteContextLoadingResult @@ -21,6 +24,7 @@ import org.utbot.framework.plugin.api.util.allSuperTypes import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.jClass import org.utbot.framework.plugin.api.util.utContext +import org.utbot.fuzzing.spring.unit.InjectMockValueProvider class SpringApplicationContextImpl( private val delegateContext: ApplicationContext, @@ -58,7 +62,16 @@ class SpringApplicationContextImpl( ) return when (springTestType) { - SpringTestType.UNIT_TEST -> delegateConcreteExecutionContext + SpringTestType.UNIT_TEST -> delegateConcreteExecutionContext.transformJavaFuzzingContext { fuzzingContext -> + fuzzingContext + .withValueProvider( + InjectMockValueProvider( + idGenerator = fuzzingContext.idGenerator, + classToUseCompositeModelFor = fuzzingContext.classUnderTest + ) + ) + .mockAllTypesWithoutSpecificValueProvider() + } SpringTestType.INTEGRATION_TEST -> SpringIntegrationTestConcreteExecutionContext( delegateConcreteExecutionContext, classpathWithoutDependencies, diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/UtTestsDialogProcessor.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/UtTestsDialogProcessor.kt index 99ead38d0b..ffd59fe240 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/UtTestsDialogProcessor.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/UtTestsDialogProcessor.kt @@ -387,7 +387,7 @@ object UtTestsDialogProcessor { } val useFuzzing = when (model.projectType) { Spring -> when (model.springTestType) { - UNIT_TEST -> false + UNIT_TEST -> UtSettings.useFuzzing INTEGRATION_TEST -> true } From 399a8b8ba94b5fb37f6e14bffe817d733aeef967 Mon Sep 17 00:00:00 2001 From: IlyaMuravjov Date: Thu, 17 Aug 2023 17:13:22 +0300 Subject: [PATCH 08/14] Refactor to only transform `JavaValueProvider` via `applicationContext` (avoid redundant lambdas) --- .../org/utbot/engine/UtBotSymbolicEngine.kt | 4 ++-- .../org/utbot/external/api/UtBotJavaApi.kt | 18 ++++++++++++------ .../context/simple/SimpleApplicationContext.kt | 5 ++++- 3 files changed, 18 insertions(+), 9 deletions(-) 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 d420a46601..6ceb5ca7ba 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt @@ -425,7 +425,7 @@ class UtBotSymbolicEngine( * @param until is used by fuzzer to cancel all tasks if the current time is over this value * @param transform provides model values for a method */ - fun fuzzing(until: Long = Long.MAX_VALUE, transform: (JavaValueProvider) -> JavaValueProvider = { it }) = flow { + fun fuzzing(until: Long = Long.MAX_VALUE) = flow { val isFuzzable = methodUnderTest.parameters.all { classId -> classId != Method::class.java.id && // causes the instrumented process crash at invocation classId != Class::class.java.id // causes java.lang.IllegalAccessException: java.lang.Class at sun.misc.Unsafe.allocateInstance(Native Method) @@ -453,7 +453,7 @@ class UtBotSymbolicEngine( methodUnderTest, constants = collectConstantsForFuzzer(graph), names = names, - providers = listOf(transform(fuzzingContext.valueProvider)), + providers = listOf(fuzzingContext.valueProvider), ) { thisInstance, descr, values -> val diff = until - System.currentTimeMillis() val thresholdMillisForFuzzingOperation = 0 // may be better use 10-20 millis as it might not be possible diff --git a/utbot-framework/src/main/kotlin/org/utbot/external/api/UtBotJavaApi.kt b/utbot-framework/src/main/kotlin/org/utbot/external/api/UtBotJavaApi.kt index 2168d67471..0050395965 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/external/api/UtBotJavaApi.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/external/api/UtBotJavaApi.kt @@ -12,6 +12,8 @@ import org.utbot.framework.codegen.domain.TestFramework import org.utbot.framework.codegen.generator.CodeGenerator import org.utbot.framework.codegen.generator.CodeGeneratorParams import org.utbot.framework.codegen.services.language.CgLanguageAssistant +import org.utbot.framework.context.simple.SimpleApplicationContext +import org.utbot.framework.context.utils.transformValueProvider import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionData import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult import org.utbot.instrumentation.instrumentation.execution.UtExecutionInstrumentation @@ -181,7 +183,15 @@ object UtBotJavaApi { return withUtContext(UtContext(classUnderTest.classLoader)) { val buildPath = FileUtil.isolateClassFiles(classUnderTest).toPath() - TestCaseGenerator(listOf(buildPath), classpath, dependencyClassPath, jdkInfo = JdkInfoDefaultProvider().info) + TestCaseGenerator( + listOf(buildPath), + classpath, + dependencyClassPath, + jdkInfo = JdkInfoDefaultProvider().info, + applicationContext = SimpleApplicationContext().transformValueProvider { defaultModelProvider -> + customModelProvider.withFallback(defaultModelProvider) + } + ) .generate( methodsForAutomaticGeneration.map { it.methodToBeTestedFromUserInput.executableId @@ -189,11 +199,7 @@ object UtBotJavaApi { mockStrategyApi, chosenClassesToMockAlways = emptySet(), generationTimeoutInMillis, - generate = { symbolicEngine -> - symbolicEngine.fuzzing { defaultModelProvider -> - customModelProvider.withFallback(defaultModelProvider) - } - } + generate = { symbolicEngine -> symbolicEngine.fuzzing() } ) }.toMutableList() } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleApplicationContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleApplicationContext.kt index e3f18b5cb7..3f54edd968 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleApplicationContext.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleApplicationContext.kt @@ -13,7 +13,10 @@ import org.utbot.framework.context.TypeReplacer * A context to use when no specific data is required. */ class SimpleApplicationContext( - override val mockerContext: MockerContext, + override val mockerContext: MockerContext = SimpleMockerContext( + mockFrameworkInstalled = true, + staticsMockingIsConfigured = true + ), override val typeReplacer: TypeReplacer = SimpleTypeReplacer(), override val nonNullSpeculator: NonNullSpeculator = SimpleNonNullSpeculator() ) : ApplicationContext { From 6c224c5dae5f881c0e05993b393bd57f79ee5d28 Mon Sep 17 00:00:00 2001 From: IlyaMuravjov Date: Thu, 17 Aug 2023 17:18:16 +0300 Subject: [PATCH 09/14] Fix test compilation --- .../execution/constructors/BaseConstructorTest.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/utbot-instrumentation/src/test/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/BaseConstructorTest.kt b/utbot-instrumentation/src/test/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/BaseConstructorTest.kt index afc147be72..f4eb63966b 100644 --- a/utbot-instrumentation/src/test/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/BaseConstructorTest.kt +++ b/utbot-instrumentation/src/test/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/BaseConstructorTest.kt @@ -27,6 +27,7 @@ abstract class BaseConstructorTest { protected fun computeReconstructed(value: T): T { val model = UtModelConstructor( objectToModelCache = IdentityHashMap(), + idGenerator = StateBeforeAwareIdGenerator(preExistingModels = emptySet()), utModelWithCompositeOriginConstructorFinder = ::findUtCustomModelConstructor ).construct(value, value::class.java.id) From fa774da4e6759e3ec139facfcf7ba51f5620a8b8 Mon Sep 17 00:00:00 2001 From: IlyaMuravjov Date: Fri, 18 Aug 2023 11:22:11 +0300 Subject: [PATCH 10/14] Improve default mock answers of unmockable types (arrays and sealed interfaces) --- ...rumentationContextAwareValueConstructor.kt | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) 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 aea8f55c2d..930b89d052 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 @@ -51,7 +51,6 @@ import org.utbot.instrumentation.instrumentation.execution.mock.InstanceMockCont import org.utbot.instrumentation.instrumentation.execution.mock.MethodMockController import org.utbot.instrumentation.instrumentation.execution.mock.MockController import org.utbot.instrumentation.process.runSandbox -import java.lang.reflect.Method import java.lang.reflect.Modifier import java.security.AccessController import java.security.PrivilegedAction @@ -130,6 +129,14 @@ class InstrumentationContextAwareValueConstructor( } } + /** + * Use this method if you need to use [construct], while being in a sandbox. + * + * Permission elevation is required, because [construct] heavily uses reflection and Mockito. + */ + private fun constructPrivileged(model: UtModel): UtConcreteValue<*> = + AccessController.doPrivileged(PrivilegedAction { construct(model) }) + /** * Constructs an Enum<*> instance by model, uses reference-equality cache. */ @@ -242,16 +249,18 @@ class InstrumentationContextAwareValueConstructor( with(invocation.method) { mockedExecutables.getOrPut(executableId) { detectedMockingCandidates.add(executableId) - val answerModel = generateNewAnswerModel() + var answerModel = generateNewAnswerModel(executableId) + val answerValue = runCatching { constructPrivileged(answerModel) }.getOrElse { + // fallback is used, so we still get some value (null) for types + // that can't be mocked, e.g. arrays and sealed interfaces + answerModel = executableId.returnType.defaultValueModel() + constructPrivileged(answerModel) + } MockedExecutable( executableId = executableId, - answerValues = listOf( - // `construct()` heavily uses reflection and Mockito, - // so it can't run in sandbox and we need to elevate permissions - AccessController.doPrivileged(PrivilegedAction { construct(answerModel) }) - .value.takeUnless { it == Unit } - ), + // `Unit` is replaced with `null`, because in Java `void` methods actually return `null` + answerValues = listOf(answerValue.value.takeUnless { it == Unit }), answerModels = listOf(answerModel) ) }.nextAnswer() @@ -259,7 +268,7 @@ class InstrumentationContextAwareValueConstructor( } } - private fun Method.generateNewAnswerModel() = + private fun generateNewAnswerModel(executableId: ExecutableId) = executableId.returnType.defaultValueModel().takeUnless { it.isNull() } ?: when (executableId.returnType) { // mockito can't mock `String` and `Class` stringClassId -> UtNullModel(stringClassId) From 8bf81d562e32721a282971bdaa10d576c577f5aa Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Fri, 18 Aug 2023 16:54:18 +0300 Subject: [PATCH 11/14] Set default fuzzing value to 0.3 for Spring --- .../intellij/plugin/ui/GenerateTestsDialogWindow.kt | 12 ++++++++++++ .../utbot/intellij/plugin/settings/CommonSettings.kt | 11 +++++++++++ 2 files changed, 23 insertions(+) diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt index e49034b660..0af1bc1217 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt @@ -860,6 +860,18 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m val settings = model.project.service() + when (model.projectType) { + ProjectType.Spring -> { + if (!settings.isSpringHandled) { + settings.isSpringHandled = true + settings.fuzzingValue = + if (settings.fuzzingValue == 0.0) 0.0 + else settings.fuzzingValue.coerceAtLeast(0.3) + } + } + else -> {} + } + mockStrategies.item = when (model.projectType) { ProjectType.Spring -> MockStrategyApi.springDefaultItem else -> settings.mockStrategy diff --git a/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/settings/CommonSettings.kt b/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/settings/CommonSettings.kt index 4f8d9573bc..bca83c97c3 100644 --- a/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/settings/CommonSettings.kt +++ b/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/settings/CommonSettings.kt @@ -70,6 +70,7 @@ class Settings(val project: Project) : PersistentStateComponent var summariesGenerationType: SummariesGenerationType = UtSettings.summaryGenerationType, var generationTimeoutInMillis: Long = UtSettings.utBotGenerationTimeoutInMillis, var enableExperimentalLanguagesSupport: Boolean = false, + var isSpringHandled: Boolean = false, ) { override fun equals(other: Any?): Boolean { @@ -178,6 +179,16 @@ class Settings(val project: Project) : PersistentStateComponent var enableSummariesGeneration = state.summariesGenerationType + /** + * Defaults in Spring are slightly different, so for every Spring project we update settings, but only + * do it once so user is not stuck with defaults, hence this flag is needed to avoid repeated updates. + */ + var isSpringHandled: Boolean + get() = state.isSpringHandled + set(value) { + state.isSpringHandled = value + } + fun setClassesToMockAlways(classesToMockAlways: List) { state.classesToMockAlways = classesToMockAlways.distinct().toTypedArray() } From 8cb41d3d46f34d0c2a8e079f63081fd025e7c653 Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Fri, 18 Aug 2023 16:54:51 +0300 Subject: [PATCH 12/14] Avoid creating too deep dynamic mocks --- ...rumentationContextAwareValueConstructor.kt | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) 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 930b89d052..3a258af753 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 @@ -72,6 +72,10 @@ class InstrumentationContextAwareValueConstructor( private val instrumentationContext: InstrumentationContext, private val idGenerator: StateBeforeAwareIdGenerator, ) { + companion object { + private const val MAX_DYNAMIC_MOCK_DEPTH = 5 + } + private val classLoader: ClassLoader get() = utContext.classLoader @@ -249,7 +253,7 @@ class InstrumentationContextAwareValueConstructor( with(invocation.method) { mockedExecutables.getOrPut(executableId) { detectedMockingCandidates.add(executableId) - var answerModel = generateNewAnswerModel(executableId) + var answerModel = generateNewAnswerModel(executableId, dynamicMockModelToDepth[mockModel] ?: 0) val answerValue = runCatching { constructPrivileged(answerModel) }.getOrElse { // fallback is used, so we still get some value (null) for types // that can't be mocked, e.g. arrays and sealed interfaces @@ -268,15 +272,18 @@ class InstrumentationContextAwareValueConstructor( } } - private fun generateNewAnswerModel(executableId: ExecutableId) = - executableId.returnType.defaultValueModel().takeUnless { it.isNull() } ?: when (executableId.returnType) { + private val dynamicMockModelToDepth = mutableMapOf() + + private fun generateNewAnswerModel(executableId: ExecutableId, depth: Int) = + executableId.returnType.defaultValueModel().takeUnless { it.isNull() } ?: when { // mockito can't mock `String` and `Class` - stringClassId -> UtNullModel(stringClassId) - classClassId -> UtClassRefModel( + executableId.returnType == stringClassId -> UtNullModel(stringClassId) + executableId.returnType == classClassId -> UtClassRefModel( id = idGenerator.createId(), classId = classClassId, value = classClassId, ) + depth > MAX_DYNAMIC_MOCK_DEPTH -> UtNullModel(executableId.classId) else -> UtCompositeModel( id = idGenerator.createId(), @@ -285,7 +292,7 @@ class InstrumentationContextAwareValueConstructor( classId = executableId.returnType, isMock = true, canHaveRedundantOrMissingMocks = true, - ) + ).also { dynamicMockModelToDepth[it] = depth + 1 } } private fun generateMockitoMock( From 87e0e22d8f379881b584d3d5ce51015e54625b92 Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Fri, 18 Aug 2023 16:55:31 +0300 Subject: [PATCH 13/14] Avoid recording multiple mock answers if same answer is reused over and over again --- .../InstrumentationContextAwareValueConstructor.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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 3a258af753..c3b90bb681 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 @@ -236,7 +236,13 @@ class InstrumentationContextAwareValueConstructor( val answerValue = answerValues[pointer] val answerModel = answerModels[pointer] pointer = (pointer + 1) % answerValues.size - (mockModel.mocks.getOrPut(executableId) { mutableListOf() } as MutableList).add(answerModel) + + // Record mock answers into `mockModel.mocks` as these answers are used. + // Avoid recording multiple answers if same answer is reused over and over again. + if (answerValues.size > 1 || executableId !in mockModel.mocks) { + (mockModel.mocks.getOrPut(executableId) { mutableListOf() } as MutableList).add(answerModel) + } + return answerValue } } From 96b7faf4005985b2f96d430a941ce7ba2f56955a Mon Sep 17 00:00:00 2001 From: IlyaMuravjov Date: Fri, 18 Aug 2023 18:14:07 +0300 Subject: [PATCH 14/14] Fix JS compilation --- .../model/constructor/tree/JsCgStatementConstructor.kt | 4 ++-- .../codegen/model/constructor/tree/JsCgVariableConstructor.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/utbot-js/src/main/kotlin/framework/codegen/model/constructor/tree/JsCgStatementConstructor.kt b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/tree/JsCgStatementConstructor.kt index 54f9eda4d2..daa267d0ed 100644 --- a/utbot-js/src/main/kotlin/framework/codegen/model/constructor/tree/JsCgStatementConstructor.kt +++ b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/tree/JsCgStatementConstructor.kt @@ -95,7 +95,7 @@ class JsCgStatementConstructor(context: CgContext) : isMutable = isMutableVar } - updateVariableScope(declaration.variable, model) + rememberVariableForModel(declaration.variable, model) return Either.left(declaration) } @@ -275,7 +275,7 @@ class JsCgStatementConstructor(context: CgContext) : override fun declareVariable(type: ClassId, name: String): CgVariable = CgVariable(name, type).also { - updateVariableScope(it) + rememberVariableForModel(it) } // TODO SEVERE: think about these 2 functions diff --git a/utbot-js/src/main/kotlin/framework/codegen/model/constructor/tree/JsCgVariableConstructor.kt b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/tree/JsCgVariableConstructor.kt index 328ff26f0f..ef6f845f32 100644 --- a/utbot-js/src/main/kotlin/framework/codegen/model/constructor/tree/JsCgVariableConstructor.kt +++ b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/tree/JsCgVariableConstructor.kt @@ -102,7 +102,7 @@ class JsCgVariableConstructor(ctx: CgContext) : CgVariableConstructor(ctx) { ): Pair { val declaration = CgDeclaration(variableType, baseVariableName.toVarName(), initializer.resolve()) val variable = declaration.variable - updateVariableScope(variable) + rememberVariableForModel(variable) return variable to declaration }