From 0587302841ead3ef00eeabefd950ceb3297f5a4e Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Thu, 24 Aug 2023 13:29:37 +0300 Subject: [PATCH 1/6] Better method mutations in Spring fuzzing #2502 (#2541) --- .../kotlin/org/utbot/framework/UtSettings.kt | 5 + .../UtBotFieldModificatorsTest.kt | 4 +- .../org/utbot/external/api/UtBotJavaApi.kt | 3 +- .../assemble/AssembleModelGenerator.kt | 4 +- ...SpringIntegrationTestJavaFuzzingContext.kt | 22 ++- .../kotlin/org/utbot/fuzzing/JavaLanguage.kt | 4 +- .../org/utbot/fuzzing/providers/Arrays.kt | 2 +- .../utbot/fuzzing/providers/Collections.kt | 6 +- .../org/utbot/fuzzing/providers/Enums.kt | 4 +- .../ModifyingWithMethodsProviderWrapper.kt | 58 +++++++ .../org/utbot/fuzzing/providers/Objects.kt | 158 +++++++++++------- .../org/utbot/fuzzing/providers/Others.kt | 2 +- .../org/utbot/fuzzing/providers/Primitives.kt | 2 +- .../fuzzing/spring/SpringBeanValueProvider.kt | 103 +++++------- .../org/utbot/fuzzing/JavaFuzzingTest.kt | 2 +- .../modifications/ExecutablesAnalyzer.kt | 4 +- .../utbot/modifications/StatementsStorage.kt | 4 +- .../org/utbot/modifications/Transformers.kt | 8 +- .../UtBotFieldsModificatorsSearcher.kt | 4 +- 19 files changed, 250 insertions(+), 149 deletions(-) create mode 100644 utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/ModifyingWithMethodsProviderWrapper.kt diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt index 97f456ecd4..8303a67aa6 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt @@ -241,6 +241,11 @@ object UtSettings : AbstractSettings(logger, defaultKeyForSettingsPath, defaultS */ var fuzzingImplementationOfAbstractClasses: Boolean by getBooleanProperty(true) + /** + * Use methods to mutate fields of classes different from class under test or not. + */ + var tryMutateOtherClassesFieldsWithMethods: Boolean by getBooleanProperty(false) + /** * Generate tests that treat possible overflows in arithmetic operations as errors * that throw Arithmetic Exception. diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/framework/modificators/UtBotFieldModificatorsTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/framework/modificators/UtBotFieldModificatorsTest.kt index a56dab59a1..b552dc7176 100644 --- a/utbot-framework-test/src/test/kotlin/org/utbot/framework/modificators/UtBotFieldModificatorsTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/framework/modificators/UtBotFieldModificatorsTest.kt @@ -26,7 +26,7 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.utbot.framework.plugin.services.JdkInfoDefaultProvider import org.utbot.framework.util.SootUtils -import org.utbot.modifications.ModificationTransformationMode +import org.utbot.modifications.FieldInvolvementMode internal class UtBotFieldModificatorsTest { private lateinit var fieldsModificatorsSearcher: UtBotFieldsModificatorsSearcher @@ -177,7 +177,7 @@ internal class UtBotFieldModificatorsTest { jdkInfo = JdkInfoDefaultProvider().info ) fieldsModificatorsSearcher = UtBotFieldsModificatorsSearcher( - modificationTransformationMode = ModificationTransformationMode.WriteOnly + fieldInvolvementMode = FieldInvolvementMode.WriteOnly ) } 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 0050395965..ef3d6ce20d 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 @@ -39,6 +39,7 @@ import org.utbot.framework.plugin.services.JdkInfoDefaultProvider import org.utbot.fuzzer.FuzzedType import org.utbot.fuzzer.FuzzedValue import org.utbot.fuzzing.FuzzedDescription +import org.utbot.fuzzing.JavaValueProvider import org.utbot.fuzzing.Seed import org.utbot.fuzzing.ValueProvider import org.utbot.instrumentation.ConcreteExecutor @@ -173,7 +174,7 @@ object UtBotJavaApi { } ?.map { UtPrimitiveModel(it) } ?: emptySequence() - val customModelProvider = ValueProvider { _, type -> + val customModelProvider = JavaValueProvider { _, type -> sequence { createPrimitiveModels(primitiveValuesSupplier, type.classId).forEach { model -> yield(Seed.Simple(FuzzedValue(model))) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/assemble/AssembleModelGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/assemble/AssembleModelGenerator.kt index 20f4694807..bdb4fc6868 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/assemble/AssembleModelGenerator.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/assemble/AssembleModelGenerator.kt @@ -48,7 +48,7 @@ import org.utbot.framework.plugin.api.util.jClass import org.utbot.framework.util.nextModelName import java.lang.reflect.Constructor import java.util.IdentityHashMap -import org.utbot.modifications.ModificationTransformationMode +import org.utbot.modifications.FieldInvolvementMode /** * Creates [UtAssembleModel] from any [UtModel] or it's inner models if possible @@ -75,7 +75,7 @@ class AssembleModelGenerator(private val basePackageName: String) { private val modificatorsSearcher = UtBotFieldsModificatorsSearcher( - modificationTransformationMode = ModificationTransformationMode.WriteOnly + fieldInvolvementMode = FieldInvolvementMode.WriteOnly ) private val constructorAnalyzer = ConstructorAnalyzer() 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 index f94972f680..63de7a5f82 100644 --- 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 @@ -17,9 +17,10 @@ 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.AbstractsObjectValueProvider import org.utbot.fuzzing.providers.AnyDepthNullValueProvider +import org.utbot.fuzzing.providers.ModifyingWithMethodsProviderWrapper 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 @@ -37,7 +38,7 @@ class SpringIntegrationTestJavaFuzzingContext( private val logger = KotlinLogging.logger {} } - override val valueProvider: JavaValueProvider = + private val springBeanValueProvider: JavaValueProvider = SpringBeanValueProvider( idGenerator, beanNameProvider = { classId -> @@ -45,20 +46,29 @@ class SpringIntegrationTestJavaFuzzingContext( }, relevantRepositories = relevantRepositories ) - .withFallback(ValidEntityValueProvider(idGenerator, onlyAcceptWhenValidIsRequired = true)) + + override val valueProvider: JavaValueProvider = + springBeanValueProvider.withModifyingMethodsBuddy() + .withFallback(ValidEntityValueProvider(idGenerator, onlyAcceptWhenValidIsRequired = true).withModifyingMethodsBuddy()) .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(ObjectValueProvider(idGenerator).withModifyingMethodsBuddy()) + .with(ValidEntityValueProvider(idGenerator, onlyAcceptWhenValidIsRequired = false).withModifyingMethodsBuddy()) .with(createGeneratedFieldValueProviders(relevantRepositories, idGenerator)) .withFallback(AnyDepthNullValueProvider) ) .preserveProperties() + private fun JavaValueProvider.withModifyingMethodsBuddy(): JavaValueProvider = + with(modifyingMethodsBuddy(this)) + + private fun modifyingMethodsBuddy(provider: JavaValueProvider): JavaValueProvider = + ModifyingWithMethodsProviderWrapper(classUnderTest, provider) + + private fun createGeneratedFieldValueProviders( relevantRepositories: Set, idGenerator: IdentityPreservingIdGenerator 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 db340a501f..6f3e600788 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 @@ -42,7 +42,7 @@ fun defaultValueProviders(idGenerator: IdentityPreservingIdGenerator) = lis FloatValueProvider, StringValueProvider, NumberValueProvider, - anyObjectValueProvider(idGenerator, shouldMutateWithMethods = false), + anyObjectValueProvider(idGenerator), ArrayValueProvider(idGenerator), EnumValueProvider(idGenerator), ListSetValueProvider(idGenerator), @@ -59,7 +59,7 @@ suspend fun runJavaFuzzing( methodUnderTest: ExecutableId, constants: Collection, names: List, - providers: List> = defaultValueProviders(idGenerator), + providers: List = defaultValueProviders(idGenerator), exec: suspend (thisInstance: FuzzedValue?, description: FuzzedDescription, values: List) -> BaseFeedback, FuzzedType, FuzzedValue> ) { val random = Random(0) diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Arrays.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Arrays.kt index 1cf2c84331..e53d9888ba 100644 --- a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Arrays.kt +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Arrays.kt @@ -11,7 +11,7 @@ import org.utbot.fuzzing.* class ArrayValueProvider( val idGenerator: IdGenerator, -) : ValueProvider { +) : JavaValueProvider { override fun accept(type: FuzzedType) = type.classId.isArray diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Collections.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Collections.kt index 51bda79661..c860fbbd33 100644 --- a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Collections.kt +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Collections.kt @@ -12,7 +12,7 @@ import kotlin.reflect.KClass class EmptyCollectionValueProvider( val idGenerator: IdGenerator -) : ValueProvider { +) : JavaValueProvider { private class Info(val classId: ClassId, val methodName: String, val returnType: ClassId = classId) private val unmodifiableCollections = listOf( @@ -150,7 +150,7 @@ class ListSetValueProvider( abstract class CollectionValueProvider( private val idGenerator: IdGenerator, vararg acceptableCollectionTypes: ClassId -) : ValueProvider { +) : JavaValueProvider { private val acceptableCollectionTypes = acceptableCollectionTypes.toList() @@ -216,7 +216,7 @@ abstract class CollectionValueProvider( } } -class IteratorValueProvider(val idGenerator: IdGenerator) : ValueProvider { +class IteratorValueProvider(val idGenerator: IdGenerator) : JavaValueProvider { override fun accept(type: FuzzedType): Boolean { return type.classId == Iterator::class.id } diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Enums.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Enums.kt index fb1e92e3c5..d51800f002 100644 --- a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Enums.kt +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Enums.kt @@ -8,12 +8,12 @@ import org.utbot.fuzzer.FuzzedValue import org.utbot.fuzzer.IdentityPreservingIdGenerator import org.utbot.fuzzer.fuzzed import org.utbot.fuzzing.FuzzedDescription +import org.utbot.fuzzing.JavaValueProvider import org.utbot.fuzzing.Seed -import org.utbot.fuzzing.ValueProvider class EnumValueProvider( val idGenerator: IdentityPreservingIdGenerator, -) : ValueProvider { +) : JavaValueProvider { override fun accept(type: FuzzedType) = type.classId.isEnum override fun generate( diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/ModifyingWithMethodsProviderWrapper.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/ModifyingWithMethodsProviderWrapper.kt new file mode 100644 index 0000000000..fa3ab0b4cc --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/ModifyingWithMethodsProviderWrapper.kt @@ -0,0 +1,58 @@ +package org.utbot.fuzzing.providers + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.util.executableId +import org.utbot.fuzzer.FuzzedType +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzing.FuzzedDescription +import org.utbot.fuzzing.JavaValueProvider +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Scope +import org.utbot.fuzzing.Seed + +/** + * Value provider that is a buddy for another provider + * that keeps all it's functionality and also allows + * to use methods to mutate field states of an object. + * + * NOTE!!! + * Instances represented by [UtAssembleModel] only can be mutated with methods. + */ +class ModifyingWithMethodsProviderWrapper( + private val classUnderTest: ClassId, + private val delegate: JavaValueProvider +) : JavaValueProvider by delegate { + + override fun generate(description: FuzzedDescription, type: FuzzedType): Sequence> = + delegate + .generate(description, type) + .map { seed -> + if (seed is Seed.Recursive) { + Seed.Recursive( + construct = seed.construct, + modify = seed.modify + + findMethodsToModifyWith(description, type.classId, classUnderTest) + .asSequence() + .map { md -> + Routine.Call(md.parameterTypes) { self, values -> + val model = self.model as UtAssembleModel + model.modificationsChain as MutableList += + UtExecutableCallModel( + model, + md.method.executableId, + values.map { it.model } + ) + } + }, + empty = seed.empty, + ) + } else seed + } + + override fun enrich(description: FuzzedDescription, type: FuzzedType, scope: Scope) = + delegate.enrich(description, type, scope) + + override fun accept(type: FuzzedType): Boolean = delegate.accept(type) +} \ No newline at end of file diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt index a0876b75d3..bb14e0ecfd 100644 --- a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt @@ -2,11 +2,44 @@ package org.utbot.fuzzing.providers import mu.KotlinLogging import org.utbot.framework.UtSettings -import org.utbot.framework.plugin.api.* -import org.utbot.framework.plugin.api.util.* -import org.utbot.fuzzer.* -import org.utbot.fuzzing.* +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ConstructorId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtDirectSetFieldModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.id +import org.utbot.framework.plugin.api.util.classClassId +import org.utbot.framework.plugin.api.util.constructor +import org.utbot.framework.plugin.api.util.dateClassId +import org.utbot.framework.plugin.api.util.executable +import org.utbot.framework.plugin.api.util.executableId +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.isAbstract +import org.utbot.framework.plugin.api.util.isCollectionOrMap +import org.utbot.framework.plugin.api.util.isEnum +import org.utbot.framework.plugin.api.util.isPrimitiveWrapper +import org.utbot.framework.plugin.api.util.isRefType +import org.utbot.framework.plugin.api.util.isStatic +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.method +import org.utbot.framework.plugin.api.util.stringClassId +import org.utbot.fuzzer.FuzzedType +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzer.IdGenerator +import org.utbot.fuzzer.IdentityPreservingIdGenerator +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.Seed +import org.utbot.fuzzing.toFuzzerType +import org.utbot.fuzzing.traverseHierarchy import org.utbot.fuzzing.utils.hex +import org.utbot.modifications.AnalysisMode +import org.utbot.modifications.FieldInvolvementMode +import org.utbot.modifications.UtBotFieldsModificatorsSearcher import soot.Scene import soot.SootClass import java.lang.reflect.Field @@ -14,9 +47,6 @@ import java.lang.reflect.Member import java.lang.reflect.Method import java.lang.reflect.Modifier import java.lang.reflect.TypeVariable -import org.utbot.modifications.AnalysisMode -import org.utbot.modifications.ModificationTransformationMode -import org.utbot.modifications.UtBotFieldsModificatorsSearcher private val logger = KotlinLogging.logger {} @@ -35,15 +65,14 @@ private fun isIgnored(type: ClassId): Boolean { || (type.isInner && !type.isStatic) } -fun anyObjectValueProvider(idGenerator: IdentityPreservingIdGenerator, shouldMutateWithMethods: Boolean) = - ObjectValueProvider(idGenerator, shouldMutateWithMethods).letIf(UtSettings.fuzzingImplementationOfAbstractClasses) { ovp -> +fun anyObjectValueProvider(idGenerator: IdentityPreservingIdGenerator) = + ObjectValueProvider(idGenerator).letIf(UtSettings.fuzzingImplementationOfAbstractClasses) { ovp -> ovp.withFallback(AbstractsObjectValueProvider(idGenerator)) } class ObjectValueProvider( val idGenerator: IdGenerator, - private val shouldMutateWithMethods: Boolean, -) : ValueProvider { +) : JavaValueProvider { override fun accept(type: FuzzedType) = !isIgnored(type.classId) @@ -61,8 +90,8 @@ class ObjectValueProvider( } } - private fun createValue(classId: ClassId, constructorId: ConstructorId, description: FuzzedDescription): Seed.Recursive { - return Seed.Recursive( + private fun createValue(classId: ClassId, constructorId: ConstructorId, description: FuzzedDescription): Seed.Recursive = + Seed.Recursive( construct = Routine.Create(constructorId.executable.genericParameterTypes.map { toFuzzerType(it, description.typeCache) }) { values -> @@ -105,27 +134,13 @@ class ObjectValueProvider( } } } - if (shouldMutateWithMethods) { - findAllAvailableMethods(description, classId, description.description.packageName).forEach { md -> - yield(Routine.Call(md.parameterTypes) { self, values -> - val model = self.model as UtAssembleModel - model.modificationsChain as MutableList += - UtExecutableCallModel( - model, - md.method.executableId, - values.map { it.model } - ) - }) - } - } }, empty = nullRoutine(classId) ) - } } @Suppress("unused") -object NullValueProvider : ValueProvider { +object NullValueProvider : JavaValueProvider { override fun enrich(description: FuzzedDescription, type: FuzzedType, scope: Scope) { // any value in static function is ok to fuzz @@ -155,7 +170,7 @@ object NullValueProvider : ValueProvider { +object AnyDepthNullValueProvider : JavaValueProvider { override fun accept(type: FuzzedType) = type.classId.isRefType @@ -170,7 +185,7 @@ object AnyDepthNullValueProvider : ValueProvider, -) : ValueProvider { +) : JavaValueProvider { override fun accept(type: FuzzedType) = type.classId.isRefType && !isKnownTypes(type.classId) @@ -264,30 +279,35 @@ internal fun findAccessibleModifiableFields(description: FuzzedDescription?, cla }.toList() } -internal fun findAllAvailableMethods( +internal fun findMethodsToModifyWith( description: FuzzedDescription, - classId: ClassId, - packageName: String? -): List { + valueClassId: ClassId, + classUnderTest: ClassId, + ): List { + val packageName = description.description.packageName + val methodUnderTestName = description.description.name.substringAfter(description.description.className + ".") - val modifyingMethods = findModifyingMethodNames(methodUnderTestName, classId) - return classId.jClass.declaredMethods.mapNotNull { method -> - if (isAccessible(method, packageName)) { - if (method.name !in modifyingMethods) return@mapNotNull null - if (method.genericParameterTypes.any { it is TypeVariable<*> }) return@mapNotNull null - - val parameterTypes = - method - .parameterTypes - .map { toFuzzerType(it, description.typeCache) } - - MethodDescription( - name = method.name, - parameterTypes = parameterTypes, - method = method - ) - } else null - } + val modifyingMethods = findModifyingMethodNames(methodUnderTestName, valueClassId, classUnderTest) + return valueClassId.allMethods + .map { it.method } + .mapNotNull { method -> + if (isAccessible(method, packageName)) { + if (method.name !in modifyingMethods) return@mapNotNull null + if (method.genericParameterTypes.any { it is TypeVariable<*> }) return@mapNotNull null + + val parameterTypes = + method + .parameterTypes + .map { toFuzzerType(it, description.typeCache) } + + MethodDescription( + name = method.name, + parameterTypes = parameterTypes, + method = method + ) + } else null + } + .toList() } internal fun Class<*>.findPublicSetterGetterIfHasPublicGetter(field: Field, packageName: String?): PublicSetterGetter? { @@ -322,21 +342,33 @@ internal fun isAccessible(clazz: Class<*>, packageName: String?): Boolean { (packageName != null && isNotPrivateOrProtected(clazz.modifiers) && clazz.`package`?.name == packageName) } -private fun findModifyingMethodNames(methodUnderTestName: String, classId: ClassId) = - UtBotFieldsModificatorsSearcher( - modificationTransformationMode = ModificationTransformationMode.ReadAndWrite - ) +private fun findModifyingMethodNames( + methodUnderTestName: String, + valueClassId: ClassId, + classUnderTest: ClassId, + ) : Set = + UtBotFieldsModificatorsSearcher(fieldInvolvementMode = FieldInvolvementMode.ReadAndWrite) .let { searcher -> - searcher.update(setOf(classId)) + searcher.update(setOf(valueClassId, classUnderTest)) val modificatorsToFields = searcher.getModificatorToFields(analysisMode = AnalysisMode.Methods) - modificatorsToFields[methodUnderTestName] - ?.let { fieldsModifiedByMUT -> - modificatorsToFields.mapNotNull { - if (it.key == methodUnderTestName || it.value.intersect(fieldsModifiedByMUT).isEmpty()) null - else it.key + modificatorsToFields[methodUnderTestName]?.let { fieldsModifiedByMut -> + modificatorsToFields + .mapNotNull { (methodName, fieldSet) -> + val relevantFields = if (UtSettings.tryMutateOtherClassesFieldsWithMethods) { + fieldsModifiedByMut + } else { + fieldsModifiedByMut + .filter { field -> field.declaringClass == classUnderTest } + .toSet() + } + + val methodIsModifying = fieldSet.intersect(relevantFields).isNotEmpty() + && methodName != methodUnderTestName + if (methodIsModifying) methodName else null } - } + .toSet() + } ?: setOf() } diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Others.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Others.kt index e019d95904..f90bd06d6f 100644 --- a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Others.kt +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Others.kt @@ -13,7 +13,7 @@ import java.util.* abstract class ClassValueProvider( val classId: ClassId -) : ValueProvider { +) : JavaValueProvider { override fun accept(type: FuzzedType) = type.classId == classId } 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 3995a32b84..4cb1ce1879 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 @@ -17,7 +17,7 @@ import kotlin.random.Random abstract class PrimitiveValueProvider( vararg acceptableTypes: ClassId -) : ValueProvider { +) : JavaValueProvider { protected val acceptableTypes = acceptableTypes.toSet() final override fun accept(type: FuzzedType) = type.classId in acceptableTypes diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/SpringBeanValueProvider.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/SpringBeanValueProvider.kt index 99c73496d4..388ce45c7c 100644 --- a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/SpringBeanValueProvider.kt +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/SpringBeanValueProvider.kt @@ -13,7 +13,7 @@ import org.utbot.fuzzer.IdGenerator import org.utbot.fuzzer.fuzzed import org.utbot.fuzzing.* import org.utbot.fuzzing.providers.SPRING_BEAN_PROP -import org.utbot.fuzzing.providers.findAllAvailableMethods +import org.utbot.fuzzing.providers.findMethodsToModifyWith import org.utbot.fuzzing.providers.nullRoutine import org.utbot.fuzzing.spring.valid.EntityLifecycleState import org.utbot.fuzzing.spring.valid.EntityLifecycleStateProperty @@ -22,7 +22,7 @@ class SpringBeanValueProvider( private val idGenerator: IdGenerator, private val beanNameProvider: (ClassId) -> List, private val relevantRepositories: Set -) : ValueProvider { +) : JavaValueProvider { override fun enrich(description: FuzzedDescription, type: FuzzedType, scope: Scope) { if (description.description.isStatic == false @@ -38,62 +38,51 @@ class SpringBeanValueProvider( ) = sequence { val beans = description.scope?.getProperty(SPRING_BEAN_PROP) beans?.invoke(type.classId)?.forEach { beanName -> - yield( - Seed.Recursive( - construct = Routine.Create(types = emptyList()) { - SpringModelUtils.createBeanModel( - beanName = beanName, - id = idGenerator.createId(), - classId = type.classId, - ).fuzzed { summary = "@Autowired ${type.classId.simpleName} $beanName" } - }, - modify = sequence { - // TODO mutate model itself (not just repositories) - relevantRepositories.forEach { repositoryId -> - yield(Routine.Call( - listOf(toFuzzerType(repositoryId.entityClassId.jClass, description.typeCache).addProperties( - dynamicPropertiesOf( - EntityLifecycleStateProperty.withValue(EntityLifecycleState.MANAGED) - ) - )) - ) { selfValue, (entityValue) -> - val self = selfValue.model as UtAssembleModel - val modificationChain: MutableList = - self.modificationsChain as MutableList - val entity = entityValue.model - if (entity is UtReferenceModel) { - persistMethodIdOrNull?.let { persistMethodId -> - ((entity as? UtAssembleModel)?.modificationsChain as? MutableList)?.removeAll { - it is UtExecutableCallModel && it.executable == persistMethodId - } - modificationChain.add( - UtExecutableCallModel( - instance = UtSpringEntityManagerModel(), - executable = persistMethodId, - params = listOf(entity) - ) - ) - } + yield(createValue(type.classId, beanName, description)) + } + } + private fun createValue(classId: ClassId, beanName: String, description: FuzzedDescription): Seed.Recursive = + Seed.Recursive( + construct = Routine.Create(types = emptyList()) { + SpringModelUtils.createBeanModel( + beanName = beanName, + id = idGenerator.createId(), + classId = classId, + ).fuzzed { summary = "@Autowired ${classId.simpleName} $beanName" } + }, + modify = sequence { + // TODO mutate model itself (not just repositories) + relevantRepositories.forEach { repositoryId -> + yield(Routine.Call( + listOf(toFuzzerType(repositoryId.entityClassId.jClass, description.typeCache).addProperties( + dynamicPropertiesOf( + EntityLifecycleStateProperty.withValue(EntityLifecycleState.MANAGED) + ) + )) + ) { selfValue, (entityValue) -> + val self = selfValue.model as UtAssembleModel + val modificationChain: MutableList = + self.modificationsChain as MutableList + val entity = entityValue.model + if (entity is UtReferenceModel) { + persistMethodIdOrNull?.let { persistMethodId -> + ((entity as? UtAssembleModel)?.modificationsChain as? MutableList)?.removeAll { + it is UtExecutableCallModel && it.executable == persistMethodId } - }) - } - findAllAvailableMethods(description, type.classId, description.description.packageName) - .forEach { md -> - yield(Routine.Call(md.parameterTypes) { self, values -> - val model = self.model as UtAssembleModel - model.modificationsChain as MutableList += - UtExecutableCallModel( - model, - md.method.executableId, - values.map { it.model } - ) - }) + modificationChain.add( + UtExecutableCallModel( + instance = UtSpringEntityManagerModel(), + executable = persistMethodId, + params = listOf(entity) + ) + ) } - }, - empty = nullRoutine(type.classId) - ) - ) - } - } + + } + }) + } + }, + empty = nullRoutine(classId) + ) } \ No newline at end of file diff --git a/utbot-java-fuzzing/src/test/kotlin/org/utbot/fuzzing/JavaFuzzingTest.kt b/utbot-java-fuzzing/src/test/kotlin/org/utbot/fuzzing/JavaFuzzingTest.kt index 360d706356..09d853edb9 100644 --- a/utbot-java-fuzzing/src/test/kotlin/org/utbot/fuzzing/JavaFuzzingTest.kt +++ b/utbot-java-fuzzing/src/test/kotlin/org/utbot/fuzzing/JavaFuzzingTest.kt @@ -209,7 +209,7 @@ class JavaFuzzingTest { `value providers override every function of fuzzing`(provided, provider2) } - private fun `value providers override every function of fuzzing`(provided: ValueProvider, valueProvider: MarkerValueProvider) { + private fun `value providers override every function of fuzzing`(provided: JavaValueProvider, valueProvider: MarkerValueProvider) { var executions = 0 runBlockingWithContext { runJavaFuzzing( diff --git a/utbot-modificators-analyzer/src/main/kotlin/org/utbot/modifications/ExecutablesAnalyzer.kt b/utbot-modificators-analyzer/src/main/kotlin/org/utbot/modifications/ExecutablesAnalyzer.kt index fe12bce870..0e8edad6b0 100644 --- a/utbot-modificators-analyzer/src/main/kotlin/org/utbot/modifications/ExecutablesAnalyzer.kt +++ b/utbot-modificators-analyzer/src/main/kotlin/org/utbot/modifications/ExecutablesAnalyzer.kt @@ -56,14 +56,14 @@ class ExecutablesAnalyzer { */ fun findModificationsInJimple( executableId: ExecutableId, - transformationMode: ModificationTransformationMode + transformationMode: FieldInvolvementMode ): Set { val sootMethod = executablesCache[executableId] ?: error("No method ${executableId.name} in soot cache") val jimpleBody = retrieveJimpleBody(sootMethod) ?: return emptySet() return jimpleBody .units - .mapNotNullTo(mutableSetOf()) { transformationMode.transformer(it) } + .mapNotNullTo(mutableSetOf()) { transformationMode.projector(it) } } /** diff --git a/utbot-modificators-analyzer/src/main/kotlin/org/utbot/modifications/StatementsStorage.kt b/utbot-modificators-analyzer/src/main/kotlin/org/utbot/modifications/StatementsStorage.kt index f8f0990deb..9c724a8ff0 100644 --- a/utbot-modificators-analyzer/src/main/kotlin/org/utbot/modifications/StatementsStorage.kt +++ b/utbot-modificators-analyzer/src/main/kotlin/org/utbot/modifications/StatementsStorage.kt @@ -20,7 +20,7 @@ import org.utbot.framework.plugin.api.StatementId * - build invocation graph (with nested calls) and find field modificators on request */ class StatementsStorage( - private val modificationsTransformationMode: ModificationTransformationMode + private val modificationsTransformationMode: FieldInvolvementMode ) { /** Statements with their detailed information */ val items: MutableMap = mutableMapOf() @@ -99,7 +99,7 @@ class StatementsStorage( return when (analysisMode) { AllModificators -> fields - Methods -> if (statementId is MethodId && !isSetterOrDirectAccessor(statementId) && fields.size == 1) fields else emptySet() + Methods -> if (statementId is MethodId && !isSetterOrDirectAccessor(statementId)) fields else emptySet() SettersAndDirectAccessors -> if (isSetterOrDirectAccessor(statementId) && fields.size == 1) fields else emptySet() Constructors -> if (statementId is ConstructorId) fields else emptySet() } diff --git a/utbot-modificators-analyzer/src/main/kotlin/org/utbot/modifications/Transformers.kt b/utbot-modificators-analyzer/src/main/kotlin/org/utbot/modifications/Transformers.kt index 12f9636cd3..8198564668 100644 --- a/utbot-modificators-analyzer/src/main/kotlin/org/utbot/modifications/Transformers.kt +++ b/utbot-modificators-analyzer/src/main/kotlin/org/utbot/modifications/Transformers.kt @@ -6,7 +6,13 @@ import soot.Unit import soot.jimple.internal.JAssignStmt import soot.jimple.internal.JInstanceFieldRef -enum class ModificationTransformationMode(val transformer: (Unit) -> FieldId?) { +/** + * Describes the type of field involvement meaningful for the current analysis type. + * For example, we can be interested on modifications only or in read attempts too. + * + * @param projector shows the rule to collect a set of [FieldId]s from the statements + */ +enum class FieldInvolvementMode(val projector: (Unit) -> FieldId?) { WriteOnly( { when (it) { diff --git a/utbot-modificators-analyzer/src/main/kotlin/org/utbot/modifications/UtBotFieldsModificatorsSearcher.kt b/utbot-modificators-analyzer/src/main/kotlin/org/utbot/modifications/UtBotFieldsModificatorsSearcher.kt index 5eec134ef8..c1b89d92ff 100644 --- a/utbot-modificators-analyzer/src/main/kotlin/org/utbot/modifications/UtBotFieldsModificatorsSearcher.kt +++ b/utbot-modificators-analyzer/src/main/kotlin/org/utbot/modifications/UtBotFieldsModificatorsSearcher.kt @@ -5,10 +5,10 @@ import org.utbot.framework.plugin.api.FieldId import org.utbot.framework.plugin.api.StatementId class UtBotFieldsModificatorsSearcher( - modificationTransformationMode: ModificationTransformationMode + fieldInvolvementMode: FieldInvolvementMode ) { - private var statementsStorage = StatementsStorage(modificationTransformationMode) + private var statementsStorage = StatementsStorage(fieldInvolvementMode) fun update(classIds: Set) = statementsStorage.update(classIds) From 4a75abc6f283c51f3f5f98c0e78362e2df217d64 Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Thu, 24 Aug 2023 16:18:34 +0300 Subject: [PATCH 2/6] Use some time compensation for dynamic classes transformation (#2551) Use some time compensation in dynamic classes transformation --- .../main/kotlin/org/utbot/common/StopWatch.kt | 17 ++++++++++++----- .../agent/DynamicClassTransformer.kt | 4 +++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/utbot-core/src/main/kotlin/org/utbot/common/StopWatch.kt b/utbot-core/src/main/kotlin/org/utbot/common/StopWatch.kt index 1c355317f9..1ff1b149fa 100644 --- a/utbot-core/src/main/kotlin/org/utbot/common/StopWatch.kt +++ b/utbot-core/src/main/kotlin/org/utbot/common/StopWatch.kt @@ -28,12 +28,19 @@ class StopWatch { startTime = System.currentTimeMillis() } } - - fun stop() { + + /** + * @param compensationMillis the duration in millis that should be subtracted from [elapsedMillis] to compensate + * for stopping and restarting [StopWatch] taking some time, can also be used to compensate for some activities, + * that are hard to directly detect (e.g. class loading). + * + * NOTE: [compensationMillis] will never cause [elapsedMillis] become negative. + */ + fun stop(compensationMillis: Long = 0) { lock.withLockInterruptibly { - startTime?.let { - elapsedMillis += (System.currentTimeMillis() - it) - startTime = null + startTime?.let { startTime -> + elapsedMillis += ((System.currentTimeMillis() - startTime) - compensationMillis).coerceAtLeast(0) + this.startTime = null } } } diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/agent/DynamicClassTransformer.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/agent/DynamicClassTransformer.kt index c3e83d4694..e9ba0651ff 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/agent/DynamicClassTransformer.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/agent/DynamicClassTransformer.kt @@ -35,7 +35,9 @@ class DynamicClassTransformer : ClassFileTransformer { classfileBuffer: ByteArray ): ByteArray? { try { - UtContext.currentContext()?.stopWatch?.stop() + // since we got here we have loaded a new class, meaning program is not stuck and some "meaningful" + // non-repeating actions are performed, so we assume that we should not time out for then next 65 ms + UtContext.currentContext()?.stopWatch?.stop(compensationMillis = 65) val pathToClassfile = protectionDomain.codeSource?.location?.toURI()?.let(Paths::get)?.absolutePathString() return if (pathToClassfile in pathsToUserClasses || packsToAlwaysTransform.any(className::startsWith) From de1712a114b40bedb5c5b40e1f6f51a464f7faeb Mon Sep 17 00:00:00 2001 From: IlyaMuravjov <71839386+IlyaMuravjov@users.noreply.github.com> Date: Fri, 25 Aug 2023 12:34:21 +0300 Subject: [PATCH 3/6] Make `findEqualValueOfWellKnownType` catch exceptions #2554 (#2555) --- .../execution/constructors/UtModelConstructor.kt | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) 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 e739432f5b..b9bf5053f9 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 @@ -348,12 +348,14 @@ class UtModelConstructor( ?: constructCompositeModel(value, remainingDepth) } - private fun findEqualValueOfWellKnownType(value: Any): Pair? = when (value) { - is List<*> -> ArrayList(value) to listClassId - is Set<*> -> LinkedHashSet(value) to setClassId - is Map<*, *> -> LinkedHashMap(value) to mapClassId - else -> null - } + private fun findEqualValueOfWellKnownType(value: Any): Pair? = runCatching { + when (value) { + is List<*> -> ArrayList(value) to listClassId + is Set<*> -> LinkedHashSet(value) to setClassId + is Map<*, *> -> LinkedHashMap(value) to mapClassId + else -> null + } + }.getOrNull() /** * Constructs custom UtModel but does it only for predefined list of classes. From 1931f26242fdde87c46d1c82a49f2d5fa370043e Mon Sep 17 00:00:00 2001 From: IlyaMuravjov <71839386+IlyaMuravjov@users.noreply.github.com> Date: Fri, 25 Aug 2023 15:53:26 +0300 Subject: [PATCH 4/6] Make jar extraction multi-process safe (#2552) --- .../main/kotlin/org/utbot/common/JarUtils.kt | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/utbot-core/src/main/kotlin/org/utbot/common/JarUtils.kt b/utbot-core/src/main/kotlin/org/utbot/common/JarUtils.kt index ac7a9e630c..765d520514 100644 --- a/utbot-core/src/main/kotlin/org/utbot/common/JarUtils.kt +++ b/utbot-core/src/main/kotlin/org/utbot/common/JarUtils.kt @@ -4,18 +4,29 @@ import java.io.File import java.net.URL import java.nio.file.Files import java.nio.file.StandardCopyOption +import java.util.* object JarUtils { private const val UNKNOWN_MODIFICATION_TIME = 0L fun extractJarFileFromResources(jarFileName: String, jarResourcePath: String, targetDirectoryName: String): File { - val targetDirectory = - Files.createDirectories(utBotTempDirectory.toFile().resolve(targetDirectoryName).toPath()).toFile() - return targetDirectory.resolve(jarFileName).also { jarFile -> - val resource = this::class.java.classLoader.getResource(jarResourcePath) - ?: error("Unable to find \"$jarResourcePath\" in resources, make sure it's on the classpath") - updateJarIfRequired(jarFile, resource) + val resource = this::class.java.classLoader.getResource(jarResourcePath) + ?: error("Unable to find \"$jarResourcePath\" in resources, make sure it's on the classpath") + + val targetDirectory = utBotTempDirectory.toFile().resolve(targetDirectoryName).toPath() + fun extractToSubDir(subDir: String) = + Files.createDirectories(targetDirectory.resolve(subDir)).toFile().resolve(jarFileName).also { jarFile -> + updateJarIfRequired(jarFile, resource) + } + + // We attempt to always extract jars to same locations, to avoid eating up drive space with + // every UtBot launch, but we may fail to do so if multiple processes are running in parallel. + repeat(10) { i -> + runCatching { + return extractToSubDir(i.toString()) + } } + return extractToSubDir(UUID.randomUUID().toString()) } private fun updateJarIfRequired(jarFile: File, resource: URL) { From f9c95348acb59c1b4fc212ecb36e6b2968e9ac5a Mon Sep 17 00:00:00 2001 From: IlyaMuravjov <71839386+IlyaMuravjov@users.noreply.github.com> Date: Fri, 25 Aug 2023 17:34:29 +0300 Subject: [PATCH 5/6] Call `entityManager.flush()` on every `entityManager` usage and dynamically remove failing `UtStatementCallModel`s (#2550) * Introduce deep `UtModel` mapper * Add `flush` after every `entityManager` usage * Move setting up Spring specific RD responses to `SpringUtExecutionInstrumentation` * Remove unused `getBean` rd call * Dynamically remove failing `UtStatementModel`s * Comment out unsafe class loading of `javax.servlet.http.Cookie` (until #2542 is fixed) * Avoid `mockMvc.perform((RequestBuilder) null)` tests * Address comments from #2550 * Clarify that we discard `mockMvc.perform((RequestBuilder) null)` test --- .../org/utbot/framework/plugin/api/Api.kt | 14 +- .../plugin/api/mapper/UtModelDeepMapper.kt | 123 ++++++++++++++ .../plugin/api/mapper/UtModelMapper.kt | 18 ++ .../plugin/api/mapper/UtModelNoopMapper.kt | 7 + .../UtModelSafeCastingCachingShallowMapper.kt | 15 ++ .../framework/plugin/api/mapper/Utils.kt | 64 ++++++++ .../plugin/api/util/SpringModelUtils.kt | 76 +++++---- .../codegen/domain/models/CgMethodTestSet.kt | 2 +- .../codegen/domain/models/TestClassModel.kt | 13 +- ...CgSpringIntegrationTestClassConstructor.kt | 44 ++++- ...IntegrationTestConcreteExecutionContext.kt | 17 +- .../instrumentation/Instrumentation.kt | 5 + ...onstructFailsUtExecutionInstrumentation.kt | 155 ++++++++++++++++++ .../execution/UtExecutionInstrumentation.kt | 7 + ...rumentationContextAwareValueConstructor.kt | 8 +- .../StateBeforeAwareIdGenerator.kt | 93 +---------- .../constructors/UtModelConstructor.kt | 2 +- .../context/InstrumentationContext.kt | 15 ++ .../context/SimpleInstrumentationContext.kt | 2 + .../execution/phases/PhasesController.kt | 2 + .../phases/ValueConstructionPhase.kt | 1 + .../SpringUtExecutionInstrumentation.kt | 51 ++++-- .../process/InstrumentedProcessMain.kt | 20 +-- .../instrumentation/rd/InstrumentedProcess.kt | 6 - .../InstrumentedProcessModel.Generated.kt | 133 +-------------- .../constructors/BaseConstructorTest.kt | 2 +- .../rd/models/InstrumentedProcessModel.kt | 12 -- 27 files changed, 592 insertions(+), 315 deletions(-) create mode 100644 utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/UtModelDeepMapper.kt create mode 100644 utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/UtModelMapper.kt create mode 100644 utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/UtModelNoopMapper.kt create mode 100644 utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/UtModelSafeCastingCachingShallowMapper.kt create mode 100644 utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/Utils.kt create mode 100644 utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/RemovingConstructFailsUtExecutionInstrumentation.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 c8e6346fac..3fad5afca8 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 @@ -55,6 +55,9 @@ import java.io.File import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract import org.utbot.common.isAbstract +import org.utbot.framework.plugin.api.mapper.UtModelMapper +import org.utbot.framework.plugin.api.mapper.map +import org.utbot.framework.plugin.api.mapper.mapPreservingType import org.utbot.framework.plugin.api.util.SpringModelUtils import org.utbot.framework.process.OpenModulesContainer import soot.SootMethod @@ -767,7 +770,7 @@ abstract class UtCustomModel( modelName: String = id.toString(), override val origin: UtCompositeModel? = null, ) : UtModelWithCompositeOrigin(id, classId, modelName, origin) { - abstract val dependencies: Collection + abstract fun shallowMap(mapper: UtModelMapper): UtCustomModel } object UtSpringContextModel : UtCustomModel( @@ -775,7 +778,7 @@ object UtSpringContextModel : UtCustomModel( classId = SpringModelUtils.applicationContextClassId, modelName = "applicationContext" ) { - override val dependencies: Collection get() = emptySet() + override fun shallowMap(mapper: UtModelMapper) = this // NOTE that overriding equals is required just because without it // we will lose equality for objects after deserialization @@ -789,7 +792,7 @@ class UtSpringEntityManagerModel : UtCustomModel( classId = SpringModelUtils.entityManagerClassIds.first(), modelName = "entityManager" ) { - override val dependencies: Collection get() = emptySet() + override fun shallowMap(mapper: UtModelMapper) = this // NOTE that overriding equals is required just because without it // we will lose equality for objects after deserialization @@ -820,7 +823,10 @@ data class UtSpringMockMvcResultActionsModel( id = id, modelName = "mockMvcResultActions@$id" ) { - override val dependencies: Collection get() = emptySet() + override fun shallowMap(mapper: UtModelMapper) = copy( + origin = origin?.mapPreservingType(mapper), + model = model?.map(mapper) + ) } /** diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/UtModelDeepMapper.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/UtModelDeepMapper.kt new file mode 100644 index 0000000000..85e590aaf8 --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/UtModelDeepMapper.kt @@ -0,0 +1,123 @@ +package org.utbot.framework.plugin.api.mapper + +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.UtEnumConstantModel +import org.utbot.framework.plugin.api.UtLambdaModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.plugin.api.UtReferenceModel +import org.utbot.framework.plugin.api.UtVoidModel + +/** + * Performs deep mapping of [UtModel]s. + * + * NOTE: + * - [shallowMapper] is invoked on models **before** mapping their sub models. + * - [shallowMapper] is responsible for caching own results (it may be called repeatedly on same models). + */ +class UtModelDeepMapper private constructor( + private val shallowMapper: UtModelMapper +) : UtModelMapper { + constructor(shallowMapper: (UtModel) -> UtModel) : this(UtModelSafeCastingCachingShallowMapper(shallowMapper)) + + /** + * Keys are models that have been shallowly mapped by [shallowMapper]. + * Values are models that have been deeply mapped by this [UtModelDeepMapper]. + * Models are only associated with models of the same type (i.e. the cache type is actually `MutableMap`) + */ + private val cache = mutableMapOf() + + private val allInputtedModels get() = cache.keys + private val allOutputtedModels get() = cache.values + + override fun map(model: T, clazz: Class): T = + clazz.cast(mapNestedModels(shallowMapper.map(model, clazz))) + + /** + * Maps models contained inside [model], but not the [model] itself. + */ + private fun mapNestedModels(model: UtModel): UtModel = cache.getOrPut(model) { + when (model) { + is UtNullModel, + is UtPrimitiveModel, + is UtEnumConstantModel, + is UtClassRefModel, + is UtVoidModel -> model + is UtArrayModel -> mapNestedModels(model) + is UtCompositeModel -> mapNestedModels(model) + is UtLambdaModel -> mapNestedModels(model) + is UtAssembleModel -> mapNestedModels(model) + is UtCustomModel -> mapNestedModels(model) + + // PythonModel, JsUtModel may be here + else -> throw UnsupportedOperationException("UtModel $this cannot be mapped") + } + } + + private fun mapNestedModels(model: UtArrayModel): UtReferenceModel { + val mappedModel = UtArrayModel( + id = model.id, + classId = model.classId, + length = model.length, + constModel = model.constModel, + stores = model.stores, + ) + cache[model] = mappedModel + + mappedModel.constModel = model.constModel.map(this) + mappedModel.stores.putAll(model.stores.mapModelValues(this)) + + return mappedModel + } + + private fun mapNestedModels(model: UtCompositeModel): UtCompositeModel { + val mappedModel = UtCompositeModel( + id = model.id, + classId = model.classId, + isMock = model.isMock, + ) + cache[model] = mappedModel + + mappedModel.fields.putAll(model.fields.mapModelValues(this)) + mappedModel.mocks.putAll(model.mocks.mapValuesTo(mutableMapOf()) { it.value.mapModels(this@UtModelDeepMapper) }) + + return mappedModel + } + + private fun mapNestedModels(model: UtLambdaModel): UtReferenceModel = UtLambdaModel( + id = model.id, + samType = model.samType, + declaringClass = model.declaringClass, + lambdaName = model.lambdaName, + capturedValues = model.capturedValues.mapModels(this@UtModelDeepMapper).toMutableList() + ) + + private fun mapNestedModels(model: UtAssembleModel): UtReferenceModel = UtAssembleModel( + id = model.id, + classId = model.classId, + modelName = model.modelName, + instantiationCall = model.instantiationCall.mapModels(this), + modificationsChainProvider = { + cache[model] = this@UtAssembleModel + model.modificationsChain.map { it.mapModels(this@UtModelDeepMapper) } + }, + origin = model.origin?.mapPreservingType(this) + ) + + private fun mapNestedModels(model: UtCustomModel): UtReferenceModel = + model.shallowMap(this) + + companion object { + /** + * Creates identity deep mapper, runs [block] on it, and returns the set of all models that + * were mapped (i.e. deeply collects all models reachable from models passed to `collector`). + */ + fun collectAllModels(block: (collector: UtModelDeepMapper) -> Unit): Set = + UtModelDeepMapper(UtModelNoopMapper).also(block).allInputtedModels + } +} diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/UtModelMapper.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/UtModelMapper.kt new file mode 100644 index 0000000000..8db21f8baf --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/UtModelMapper.kt @@ -0,0 +1,18 @@ +package org.utbot.framework.plugin.api.mapper + +import org.utbot.framework.plugin.api.UtCompositeModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtModelWithCompositeOrigin + +interface UtModelMapper { + /** + * Performs depending on the implementation deep or shallow mapping of the [model]. + * + * In some cases (e.g. when mapping [UtModelWithCompositeOrigin.origin]) you may want to get result + * of some specific type (e.g. [UtCompositeModel]), only then you should specify specific value for [clazz]. + * + * NOTE: if you are fine with result model and [model] having different types, then you should + * use `UtModel::class.java` as a value for [clazz] or just use [UtModel.map]. + */ + fun map(model: T, clazz: Class): T +} diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/UtModelNoopMapper.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/UtModelNoopMapper.kt new file mode 100644 index 0000000000..0325b52343 --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/UtModelNoopMapper.kt @@ -0,0 +1,7 @@ +package org.utbot.framework.plugin.api.mapper + +import org.utbot.framework.plugin.api.UtModel + +object UtModelNoopMapper : UtModelMapper { + override fun map(model: T, clazz: Class): T = model +} \ No newline at end of file diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/UtModelSafeCastingCachingShallowMapper.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/UtModelSafeCastingCachingShallowMapper.kt new file mode 100644 index 0000000000..1f2164fad4 --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/UtModelSafeCastingCachingShallowMapper.kt @@ -0,0 +1,15 @@ +package org.utbot.framework.plugin.api.mapper + +import org.utbot.framework.plugin.api.UtModel + +class UtModelSafeCastingCachingShallowMapper( + val mapper: (UtModel) -> UtModel +) : UtModelMapper { + private val cache = mutableMapOf() + + override fun map(model: T, clazz: Class): T { + val mapped = cache.getOrPut(model) { mapper(model) } + return if (clazz.isInstance(mapped)) clazz.cast(mapped) + else model + } +} \ No newline at end of file diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/Utils.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/Utils.kt new file mode 100644 index 0000000000..30bbbe01d7 --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/Utils.kt @@ -0,0 +1,64 @@ +package org.utbot.framework.plugin.api.mapper + +import org.utbot.framework.plugin.api.EnvironmentModels +import org.utbot.framework.plugin.api.UtDirectGetFieldModel +import org.utbot.framework.plugin.api.UtDirectSetFieldModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtExecution +import org.utbot.framework.plugin.api.UtInstrumentation +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNewInstanceInstrumentation +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 + +inline fun T.mapPreservingType(mapper: UtModelMapper): T = + mapper.map(this, T::class.java) + +fun UtModel.map(mapper: UtModelMapper) = mapPreservingType(mapper) + +fun List.mapModels(mapper: UtModelMapper): List = + map { model -> model.map(mapper) } + +fun Map.mapModelValues(mapper: UtModelMapper): Map = + mapValues { (_, model) -> model.map(mapper) } + +fun UtStatementModel.mapModels(mapper: UtModelMapper): UtStatementModel = + when(this) { + is UtStatementCallModel -> mapModels(mapper) + is UtDirectSetFieldModel -> UtDirectSetFieldModel( + instance = instance.mapPreservingType(mapper), + fieldId = fieldId, + fieldModel = fieldModel.map(mapper) + ) + } + +fun UtStatementCallModel.mapModels(mapper: UtModelMapper): UtStatementCallModel = + when(this) { + is UtDirectGetFieldModel -> UtDirectGetFieldModel( + instance = instance.mapPreservingType(mapper), + fieldAccess = fieldAccess, + ) + is UtExecutableCallModel -> UtExecutableCallModel( + instance = instance?.mapPreservingType(mapper), + executable = executable, + params = params.mapModels(mapper) + ) + } + +fun EnvironmentModels.mapModels(mapper: UtModelMapper) = EnvironmentModels( + thisInstance = thisInstance?.map(mapper), + statics = statics.mapModelValues(mapper), + parameters = parameters.mapModels(mapper), + executableToCall = executableToCall, +) + +fun UtInstrumentation.mapModels(mapper: UtModelMapper) = when (this) { + is UtNewInstanceInstrumentation -> copy(instances = instances.mapModels(mapper)) + is UtStaticMethodInstrumentation -> copy(values = values.mapModels(mapper)) +} + +fun UtExecution.mapStateBeforeModels(mapper: UtModelMapper) = copy( + stateBefore = stateBefore.mapModels(mapper) +) diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/SpringModelUtils.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/SpringModelUtils.kt index 32705f57a9..3907ca131e 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/SpringModelUtils.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/SpringModelUtils.kt @@ -69,6 +69,17 @@ object SpringModelUtils { ) } + val flushMethodIdOrNull: MethodId? + get() { + return MethodId( + classId = entityManagerClassIds.firstOrNull() ?: return null, + name = "flush", + returnType = voidClassId, + parameters = listOf(), + bypassesSandbox = true // TODO may be we can use some alternative sandbox that has more permissions + ) + } + val detachMethodIdOrNull: MethodId? get() { return MethodId( @@ -182,12 +193,13 @@ object SpringModelUtils { parameters = listOf(httpHeaderClassId) ) - private val mockHttpServletCookieMethodId = MethodId( - classId = mockHttpServletRequestBuilderClassId, - name = "cookie", - returnType = mockHttpServletRequestBuilderClassId, - parameters = listOf(getArrayClassIdByElementClassId(cookieClassId)) - ) +// // TODO uncomment when #2542 is fixed +// private val mockHttpServletCookieMethodId = MethodId( +// classId = mockHttpServletRequestBuilderClassId, +// name = "cookie", +// returnType = mockHttpServletRequestBuilderClassId, +// parameters = listOf(getArrayClassIdByElementClassId(cookieClassId)) +// ) private val mockHttpServletContentTypeMethodId = MethodId( classId = mockHttpServletRequestBuilderClassId, @@ -376,9 +388,10 @@ object SpringModelUtils { val headersContentModel = createHeadersContentModel(methodId, arguments, idGenerator) requestBuilderModel = addHeadersToRequestBuilderModel(headersContentModel, requestBuilderModel, idGenerator) - val cookieValuesModel = createCookieValuesModel(methodId, arguments, idGenerator) - requestBuilderModel = - addCookiesToRequestBuilderModel(cookieValuesModel, requestBuilderModel, idGenerator) +// // TODO uncomment when #2542 is fixed +// val cookieValuesModel = createCookieValuesModel(methodId, arguments, idGenerator) +// requestBuilderModel = +// addCookiesToRequestBuilderModel(cookieValuesModel, requestBuilderModel, idGenerator) val requestAttributes = collectArgumentsWithAnnotationModels(methodId, requestAttributesClassId, arguments) requestBuilderModel = @@ -455,28 +468,29 @@ object SpringModelUtils { return requestBuilderModel } - private fun addCookiesToRequestBuilderModel( - cookieValuesModel: UtArrayModel, - requestBuilderModel: UtAssembleModel, - idGenerator: () -> Int - ): UtAssembleModel { - @Suppress("NAME_SHADOWING") - var requestBuilderModel = requestBuilderModel - - if(cookieValuesModel.length > 0) { - requestBuilderModel = UtAssembleModel( - id = idGenerator(), - classId = mockHttpServletRequestBuilderClassId, - modelName = "requestBuilder", - instantiationCall = UtExecutableCallModel( - instance = requestBuilderModel, - executable = mockHttpServletCookieMethodId, - params = listOf(cookieValuesModel) - ) - ) - } - return requestBuilderModel - } +// // TODO uncomment when #2542 is fixed +// private fun addCookiesToRequestBuilderModel( +// cookieValuesModel: UtArrayModel, +// requestBuilderModel: UtAssembleModel, +// idGenerator: () -> Int +// ): UtAssembleModel { +// @Suppress("NAME_SHADOWING") +// var requestBuilderModel = requestBuilderModel +// +// if(cookieValuesModel.length > 0) { +// requestBuilderModel = UtAssembleModel( +// id = idGenerator(), +// classId = mockHttpServletRequestBuilderClassId, +// modelName = "requestBuilder", +// instantiationCall = UtExecutableCallModel( +// instance = requestBuilderModel, +// executable = mockHttpServletCookieMethodId, +// params = listOf(cookieValuesModel) +// ) +// ) +// } +// return requestBuilderModel +// } private fun addHeadersToRequestBuilderModel( headersContentModel: UtAssembleModel, diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/CgMethodTestSet.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/CgMethodTestSet.kt index e6248d6a6e..05bbbf1b04 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/CgMethodTestSet.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/CgMethodTestSet.kt @@ -135,6 +135,6 @@ data class CgMethodTestSet( return substituteExecutions(symbolicExecutionsWithoutMocking) } - private fun substituteExecutions(newExecutions: List): CgMethodTestSet = + fun substituteExecutions(newExecutions: List): CgMethodTestSet = copy().apply { executions = newExecutions } } \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/TestClassModel.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/TestClassModel.kt index 6cecbf68e9..d218e87451 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/TestClassModel.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/TestClassModel.kt @@ -1,7 +1,8 @@ package org.utbot.framework.codegen.domain.models -import org.utbot.framework.codegen.domain.models.builders.TypedModelWrappers import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.mapper.UtModelDeepMapper +import org.utbot.framework.plugin.api.mapper.mapStateBeforeModels /** * Stores method test sets in a structure that replicates structure of their methods in [classUnderTest]. @@ -20,3 +21,13 @@ class SimpleTestClassModel( nestedClasses: List = listOf(), ): TestClassModel(classUnderTest, methodTestSets, nestedClasses) +fun SimpleTestClassModel.mapStateBeforeModels(mapperProvider: () -> UtModelDeepMapper) = + SimpleTestClassModel( + classUnderTest = classUnderTest, + nestedClasses = nestedClasses, + methodTestSets = methodTestSets.map { testSet -> + testSet.substituteExecutions( + testSet.executions.map { execution -> execution.mapStateBeforeModels(mapperProvider()) } + ) + } + ) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgSpringIntegrationTestClassConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgSpringIntegrationTestClassConstructor.kt index 2208a7744e..fa6ed5fbaf 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgSpringIntegrationTestClassConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgSpringIntegrationTestClassConstructor.kt @@ -16,8 +16,15 @@ import org.utbot.framework.codegen.util.escapeControlChars import org.utbot.framework.codegen.util.resolve import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.ConcreteContextLoadingResult +import org.utbot.framework.plugin.api.MethodId import org.utbot.framework.plugin.api.SpringSettings.* import org.utbot.framework.plugin.api.SpringConfiguration.* +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtSpringEntityManagerModel +import org.utbot.framework.plugin.api.mapper.UtModelDeepMapper +import org.utbot.framework.plugin.api.mapper.UtModelDeepMapper.Companion.collectAllModels import org.utbot.framework.plugin.api.util.IndentUtil.TAB import org.utbot.framework.plugin.api.util.SpringModelUtils import org.utbot.framework.plugin.api.util.SpringModelUtils.activeProfilesClassId @@ -27,6 +34,7 @@ import org.utbot.framework.plugin.api.util.SpringModelUtils.contextConfiguration import org.utbot.framework.plugin.api.util.SpringModelUtils.dirtiesContextClassId import org.utbot.framework.plugin.api.util.SpringModelUtils.dirtiesContextClassModeClassId import org.utbot.framework.plugin.api.util.SpringModelUtils.extendWithClassId +import org.utbot.framework.plugin.api.util.SpringModelUtils.flushMethodIdOrNull import org.utbot.framework.plugin.api.util.SpringModelUtils.mockMvcClassId import org.utbot.framework.plugin.api.util.SpringModelUtils.repositoryClassId import org.utbot.framework.plugin.api.util.SpringModelUtils.runWithClassId @@ -52,6 +60,33 @@ class CgSpringIntegrationTestClassConstructor( private val logger = KotlinLogging.logger {} } + override fun construct(testClassModel: SimpleTestClassModel): CgClassFile = super.construct( + flushMethodIdOrNull?.let { flushMethodId -> + testClassModel.mapStateBeforeModels { UtModelDeepMapper { model -> + shallowlyAddFlushes(model, flushMethodId) + } } + } ?: testClassModel + ) + + private fun shallowlyAddFlushes(model: UtModel, flushMethodId: MethodId): UtModel = + when (model) { + is UtAssembleModel -> model.copy( + modificationsChain = model.modificationsChain.flatMap { modification -> + if (modification.instance is UtSpringEntityManagerModel) + listOf( + modification, + UtExecutableCallModel( + instance = UtSpringEntityManagerModel(), + executable = flushMethodId, + params = emptyList() + ) + ) + else listOf(modification) + } + ) + else -> model + } + override fun constructTestClass(testClassModel: SimpleTestClassModel): CgClass { addNecessarySpringSpecificAnnotations(testClassModel) return super.constructTestClass(testClassModel) @@ -59,8 +94,7 @@ class CgSpringIntegrationTestClassConstructor( override fun constructClassFields(testClassModel: SimpleTestClassModel): List { val autowiredFields = autowiredFieldManager.createFieldDeclarations(testClassModel) - val persistentContextFields = persistenceContextFieldsManager - ?.let { fieldsManager -> fieldsManager.createFieldDeclarations(testClassModel) } + val persistentContextFields = persistenceContextFieldsManager?.createFieldDeclarations(testClassModel) .orEmpty() return autowiredFields + persistentContextFields @@ -203,9 +237,9 @@ class CgSpringIntegrationTestClassConstructor( if (utContext.classLoader.tryLoadClass(repositoryClassId.name) != null) addAnnotation(autoConfigureTestDbClassId, Class) - //TODO: revert a check that this annotation is really required - addAnnotation(SpringModelUtils.autoConfigureMockMvcClassId, Class) - + val allStateBeforeModels = collectAllModels { collector -> testClassModel.mapStateBeforeModels { collector } } + if (allStateBeforeModels.any { it.classId == mockMvcClassId }) + addAnnotation(SpringModelUtils.autoConfigureMockMvcClassId, Class) if (utContext.classLoader.tryLoadClass(withMockUserClassId.name) != null) addAnnotation(withMockUserClassId, Class) 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 fdfd8ba2bd..2d4c5b8169 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 @@ -10,6 +10,7 @@ import org.utbot.framework.plugin.api.UtExecution import org.utbot.fuzzer.IdentityPreservingIdGenerator import org.utbot.instrumentation.ConcreteExecutor import org.utbot.instrumentation.getRelevantSpringRepositories +import org.utbot.instrumentation.instrumentation.execution.RemovingConstructFailsUtExecutionInstrumentation import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult import org.utbot.instrumentation.instrumentation.execution.UtExecutionInstrumentation import org.utbot.instrumentation.instrumentation.spring.SpringUtExecutionInstrumentation @@ -29,13 +30,15 @@ class SpringIntegrationTestConcreteExecutionContext( } override val instrumentationFactory: UtExecutionInstrumentation.Factory<*> = - SpringUtExecutionInstrumentation.Factory( - delegateContext.instrumentationFactory, - springSettings, - springApplicationContext.beanDefinitions, - buildDirs = classpathWithoutDependencies.split(File.pathSeparator) - .map { File(it).toURI().toURL() } - .toTypedArray(), + RemovingConstructFailsUtExecutionInstrumentation.Factory( + SpringUtExecutionInstrumentation.Factory( + delegateContext.instrumentationFactory, + springSettings, + springApplicationContext.beanDefinitions, + buildDirs = classpathWithoutDependencies.split(File.pathSeparator) + .map { File(it).toURI().toURL() } + .toTypedArray(), + ) ) override fun loadContext( diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/Instrumentation.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/Instrumentation.kt index e2c604b38b..92042d1cc7 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/Instrumentation.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/Instrumentation.kt @@ -2,6 +2,9 @@ package org.utbot.instrumentation.instrumentation import java.lang.instrument.ClassFileTransformer import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.process.kryo.KryoHelper +import org.utbot.instrumentation.process.generated.InstrumentedProcessModel +import org.utbot.rd.IdleWatchdog /** * Abstract class for the instrumentation. @@ -27,6 +30,8 @@ interface Instrumentation : ClassFileTransformer fun getStaticField(fieldId: FieldId): Result<*> + fun InstrumentedProcessModel.setupAdditionalRdResponses(kryoHelper: KryoHelper, watchdog: IdleWatchdog) {} + interface Factory> { val additionalRuntimeClasspath: Set get() = emptySet() val forceDisableSandbox: Boolean get() = false diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/RemovingConstructFailsUtExecutionInstrumentation.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/RemovingConstructFailsUtExecutionInstrumentation.kt new file mode 100644 index 0000000000..803f0b6e8b --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/RemovingConstructFailsUtExecutionInstrumentation.kt @@ -0,0 +1,155 @@ +package org.utbot.instrumentation.instrumentation.execution + +import com.jetbrains.rd.util.getLogger +import com.jetbrains.rd.util.info +import org.utbot.framework.plugin.api.Coverage +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.MissingState +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtConcreteExecutionProcessedFailure +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtStatementCallModel +import org.utbot.framework.plugin.api.isNull +import org.utbot.framework.plugin.api.mapper.UtModelDeepMapper +import org.utbot.framework.plugin.api.util.defaultValueModel +import org.utbot.framework.process.kryo.KryoHelper +import org.utbot.instrumentation.instrumentation.ArgumentList +import org.utbot.instrumentation.instrumentation.execution.context.InstrumentationContext +import org.utbot.instrumentation.instrumentation.execution.phases.ExecutionPhaseStop +import org.utbot.instrumentation.instrumentation.execution.phases.PhasesController +import org.utbot.instrumentation.instrumentation.execution.phases.ValueConstructionPhase +import org.utbot.instrumentation.process.generated.InstrumentedProcessModel +import org.utbot.rd.IdleWatchdog +import java.security.ProtectionDomain + +/** + * [UtExecutionInstrumentation] that on [invoke] tries to run [invoke] of the [delegateInstrumentation] + * a few times, each time removing failing [UtStatementCallModel]s, until either max number of reruns + * is reached or [invoke] of the [delegateInstrumentation] no longer fails with [UtConcreteExecutionProcessedFailure]. + * + * @see [UtStatementCallModel.thrownConcreteException] + */ +class RemovingConstructFailsUtExecutionInstrumentation( + instrumentationContext: InstrumentationContext, + delegateInstrumentationFactory: UtExecutionInstrumentation.Factory<*> +) : UtExecutionInstrumentation { + companion object { + private const val MAX_RETRIES = 5 + private val logger = getLogger() + } + + private val delegateInstrumentation = delegateInstrumentationFactory.create(object : InstrumentationContext by instrumentationContext { + override fun handleLastCaughtConstructionException(exception: Throwable) { + throw ExecutionPhaseStop( + phase = ValueConstructionPhase::class.java.simpleName, + result = PreliminaryUtConcreteExecutionResult( + stateAfter = MissingState, + result = UtConcreteExecutionProcessedFailure(exception), + coverage = Coverage() + ) + ) + } + }) + private var runsCompleted = 0 + private var nextRunIndexToLog = 1 // we log `attemptsDistribution` every run that has index that is a power of 10 + private val attemptsDistribution = mutableMapOf() + + override fun invoke( + clazz: Class<*>, + methodSignature: String, + arguments: ArgumentList, + parameters: Any?, + phasesWrapper: PhasesController.(invokeBasePhases: () -> PreliminaryUtConcreteExecutionResult) -> PreliminaryUtConcreteExecutionResult + ): UtConcreteExecutionResult { + @Suppress("NAME_SHADOWING") + var parameters = parameters as UtConcreteExecutionData + var attempt = 0 + var res: UtConcreteExecutionResult + try { + do { + res = delegateInstrumentation.invoke(clazz, methodSignature, arguments, parameters, phasesWrapper) + + if (res.result !is UtConcreteExecutionProcessedFailure) + return res + + parameters = parameters.mapModels(UtModelDeepMapper { model -> + shallowlyRemoveFailingCalls(model) + }) + + // if `thisInstance` is present and became `isNull`, then we should stop trying to + // correct this execution and return `UtConcreteExecutionProcessedFailure` + if (parameters.stateBefore.thisInstance?.isNull() == true) + return res + + } while (attempt++ < MAX_RETRIES) + + return res + } finally { + runsCompleted++ + attemptsDistribution[attempt] = (attemptsDistribution[attempt] ?: 0) + 1 + if (runsCompleted == nextRunIndexToLog) { + nextRunIndexToLog *= 10 + logger.info { "Run: $runsCompleted, attemptsDistribution: $attemptsDistribution" } + } + } + } + + private fun shallowlyRemoveFailingCalls(model: UtModel): UtModel = when { + model !is UtAssembleModel -> model + model.instantiationCall.thrownConcreteException != null -> model.classId.defaultValueModel() + else -> UtAssembleModel( + id = model.id, + classId = model.classId, + modelName = model.modelName, + instantiationCall = model.instantiationCall, + origin = model.origin, + modificationsChainProvider = { + model.modificationsChain.filter { + (it as? UtStatementCallModel)?.thrownConcreteException == null && + (it.instance as? UtAssembleModel)?.instantiationCall?.thrownConcreteException == null + } + } + ) + } + + override fun getStaticField(fieldId: FieldId): Result<*> = delegateInstrumentation.getStaticField(fieldId) + + override fun transform( + loader: ClassLoader?, + className: String, + classBeingRedefined: Class<*>?, + protectionDomain: ProtectionDomain, + classfileBuffer: ByteArray + ): ByteArray? = delegateInstrumentation.transform( + loader, className, classBeingRedefined, protectionDomain, classfileBuffer + ) + + override fun InstrumentedProcessModel.setupAdditionalRdResponses(kryoHelper: KryoHelper, watchdog: IdleWatchdog) = + delegateInstrumentation.run { setupAdditionalRdResponses(kryoHelper, watchdog) } + + class Factory( + private val delegateInstrumentationFactory: UtExecutionInstrumentation.Factory<*> + ) : UtExecutionInstrumentation.Factory { + override val additionalRuntimeClasspath: Set + get() = delegateInstrumentationFactory.additionalRuntimeClasspath + + override val forceDisableSandbox: Boolean + get() = delegateInstrumentationFactory.forceDisableSandbox + + override fun create(instrumentationContext: InstrumentationContext): UtExecutionInstrumentation = + RemovingConstructFailsUtExecutionInstrumentation(instrumentationContext, delegateInstrumentationFactory) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Factory + + return delegateInstrumentationFactory == other.delegateInstrumentationFactory + } + + override fun hashCode(): Int { + return delegateInstrumentationFactory.hashCode() + } + } +} \ No newline at end of file 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 3fe6d66360..9b5217df55 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 @@ -2,6 +2,8 @@ package org.utbot.instrumentation.instrumentation.execution import org.utbot.framework.UtSettings import org.utbot.framework.plugin.api.* +import org.utbot.framework.plugin.api.mapper.UtModelMapper +import org.utbot.framework.plugin.api.mapper.mapModels import org.utbot.instrumentation.instrumentation.ArgumentList import org.utbot.instrumentation.instrumentation.Instrumentation import org.utbot.instrumentation.instrumentation.execution.context.InstrumentationContext @@ -22,6 +24,11 @@ data class UtConcreteExecutionData( val timeout: Long ) +fun UtConcreteExecutionData.mapModels(mapper: UtModelMapper) = copy( + stateBefore = stateBefore.mapModels(mapper), + instrumentation = instrumentation.map { it.mapModels(mapper) } +) + /** * [UtConcreteExecutionResult] that has not yet been populated with extra data, e.g.: * - updated [UtConcreteExecutionResult.stateBefore] 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 d9b6176fe9..51c398ca36 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 @@ -91,6 +91,9 @@ class InstrumentationContextAwareValueConstructor( val detectedMockingCandidates: MutableSet = mutableSetOf() + var lastCaughtException: Throwable? = null + private set + // TODO: JIRA:1379 -- replace UtReferenceModel with Int private val constructedObjects = HashMap() @@ -532,7 +535,10 @@ class InstrumentationContextAwareValueConstructor( .also { result -> result .exceptionOrNull() - ?.let { callModel.thrownConcreteException = it.javaClass.id } + ?.let { + lastCaughtException = it + callModel.thrownConcreteException = it.javaClass.id + } } 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 index cc54b1d4c6..4192283aec 100644 --- 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 @@ -1,86 +1,19 @@ 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.framework.plugin.api.mapper.UtModelDeepMapper.Companion.collectAllModels import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionData -import java.util.* +import org.utbot.instrumentation.instrumentation.execution.mapModels -class StateBeforeAwareIdGenerator(preExistingModels: Collection) { - private val seenIds = mutableSetOf() - - // there's no `IdentityHashSet`, so we use `IdentityHashMap` with dummy values - private val seenModels = IdentityHashMap() +class StateBeforeAwareIdGenerator(allPreExistingModels: Collection) { + private val seenIds = allPreExistingModels + .filterIsInstance() + .mapNotNull { it.id } + .toMutableSet() 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++ @@ -88,16 +21,6 @@ class StateBeforeAwareIdGenerator(preExistingModels: Collection) { 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 - } - } - ) + StateBeforeAwareIdGenerator(collectAllModels { collector -> data.mapModels(collector) }) } } \ No newline at end of file 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 b9bf5053f9..dd371a6e25 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 @@ -53,7 +53,7 @@ class UtModelConstructor( ) return UtModelConstructor( objectToModelCache = cache, - idGenerator = StateBeforeAwareIdGenerator(preExistingModels = emptySet()), + idGenerator = StateBeforeAwareIdGenerator(allPreExistingModels = emptySet()), utModelWithCompositeOriginConstructorFinder = utModelWithCompositeOriginConstructorFinder, compositeModelStrategy = strategy ) diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/context/InstrumentationContext.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/context/InstrumentationContext.kt index 5c86b389a6..7c95db4dcb 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/context/InstrumentationContext.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/context/InstrumentationContext.kt @@ -1,10 +1,14 @@ package org.utbot.instrumentation.instrumentation.execution.context import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtConcreteExecutionProcessedFailure import org.utbot.framework.plugin.api.UtConcreteValue import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtStatementCallModel import org.utbot.instrumentation.instrumentation.execution.constructors.UtModelWithCompositeOriginConstructor import org.utbot.instrumentation.instrumentation.execution.phases.ExecutionPhase +import org.utbot.instrumentation.instrumentation.execution.phases.ValueConstructionPhase import java.lang.reflect.Method import java.util.IdentityHashMap import org.utbot.instrumentation.instrumentation.mock.computeKeyForMethod @@ -42,6 +46,17 @@ interface InstrumentationContext { */ fun onPhaseTimeout(timedOutedPhase: ExecutionPhase) + /** + * At the very end of the [ValueConstructionPhase], instrumentation context gets to decide what to do + * with last caught [UtStatementCallModel.thrownConcreteException] (it can be caught if the call + * is non-essential for value construction, i.e. it's in [UtAssembleModel.modificationsChain]). + * + * A reasonable implementation may: + * - ignore the [exception] + * - cause phase to terminate with [UtConcreteExecutionProcessedFailure] + */ + fun handleLastCaughtConstructionException(exception: Throwable) + object MockGetter { data class MockContainer(private val values: List<*>) { private var ptr: Int = 0 diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/context/SimpleInstrumentationContext.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/context/SimpleInstrumentationContext.kt index a1633548a8..f0d91da558 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/context/SimpleInstrumentationContext.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/context/SimpleInstrumentationContext.kt @@ -24,4 +24,6 @@ class SimpleInstrumentationContext : InstrumentationContext { javaStdLibModelWithCompositeOriginConstructors[classId.jClass]?.invoke() override fun onPhaseTimeout(timedOutedPhase: ExecutionPhase) = Unit + + override fun handleLastCaughtConstructionException(exception: Throwable) = Unit } \ No newline at end of file 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 c23ed675da..4138d4882e 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 @@ -105,6 +105,8 @@ class PhasesController( // here static methods and instances are mocked mock(parameters.instrumentation) + lastCaughtException?.let { instrumentationContext.handleLastCaughtConstructionException(it) } + ConstructedData(params, statics, getCache()) } 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 55e322bfad..aa71548fd4 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 @@ -43,6 +43,7 @@ class ValueConstructionPhase( idGenerator, ) val detectedMockingCandidates: Set get() = constructor.detectedMockingCandidates + val lastCaughtException: Throwable? get() = constructor.lastCaughtException fun getCache(): ConstructedCache { return constructor.objectToModelCache diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/spring/SpringUtExecutionInstrumentation.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/spring/SpringUtExecutionInstrumentation.kt index 8540edd957..eb24804aa9 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/spring/SpringUtExecutionInstrumentation.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/spring/SpringUtExecutionInstrumentation.kt @@ -8,19 +8,27 @@ import org.utbot.framework.plugin.api.BeanDefinitionData import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.FieldId import org.utbot.framework.plugin.api.ConcreteContextLoadingResult +import org.utbot.framework.plugin.api.Coverage +import org.utbot.framework.plugin.api.MissingState import org.utbot.framework.plugin.api.SpringRepositoryId import org.utbot.framework.plugin.api.SpringSettings.* -import org.utbot.framework.plugin.api.UtModel -import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.UtConcreteExecutionProcessedFailure +import org.utbot.framework.plugin.api.isNull +import org.utbot.framework.plugin.api.util.SpringModelUtils.mockMvcPerformMethodId import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.process.kryo.KryoHelper import org.utbot.instrumentation.instrumentation.ArgumentList import org.utbot.instrumentation.instrumentation.execution.PreliminaryUtConcreteExecutionResult +import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionData import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult import org.utbot.instrumentation.instrumentation.execution.UtExecutionInstrumentation -import org.utbot.instrumentation.instrumentation.execution.constructors.UtModelConstructor import org.utbot.instrumentation.instrumentation.execution.context.InstrumentationContext import org.utbot.instrumentation.instrumentation.execution.phases.ExecutionPhaseFailingOnAnyException import org.utbot.instrumentation.instrumentation.execution.phases.PhasesController +import org.utbot.instrumentation.process.generated.GetSpringRepositoriesResult +import org.utbot.instrumentation.process.generated.InstrumentedProcessModel +import org.utbot.instrumentation.process.generated.TryLoadingSpringContextResult +import org.utbot.rd.IdleWatchdog import org.utbot.spring.api.SpringApi import java.io.File import java.net.URL @@ -76,6 +84,22 @@ class SpringUtExecutionInstrumentation( parameters: Any?, phasesWrapper: PhasesController.(invokeBasePhases: () -> PreliminaryUtConcreteExecutionResult) -> PreliminaryUtConcreteExecutionResult ): UtConcreteExecutionResult = synchronized(this) { + if (parameters !is UtConcreteExecutionData) { + throw IllegalArgumentException("Argument parameters must be of type UtConcreteExecutionData, but was: ${parameters?.javaClass}") + } + + // `RemovingConstructFailsUtExecutionInstrumentation` may detect that we fail to + // construct `RequestBuilder` and use `requestBuilder = null`, leading to a nonsensical + // test `mockMvc.perform((RequestBuilder) null)`, which we should discard + if (parameters.stateBefore.executableToCall == mockMvcPerformMethodId && parameters.stateBefore.parameters.single().isNull()) + return UtConcreteExecutionResult( + stateBefore = parameters.stateBefore, + stateAfter = MissingState, + result = UtConcreteExecutionProcessedFailure(IllegalStateException("requestBuilder can't be null")), + coverage = Coverage(), + detectedMockingCandidates = emptySet() + ) + getRelevantBeans(clazz).forEach { beanName -> springApi.resetBean(beanName) } return delegateInstrumentation.invoke(clazz, methodSignature, arguments, parameters) { invokeBasePhases -> phasesWrapper { @@ -107,14 +131,6 @@ class SpringUtExecutionInstrumentation( .also { logger.info { "Detected relevant beans for class ${clazz.name}: $it" } } } - fun getBeanModel(beanName: String, classpathToConstruct: Set): UtModel { - val bean = springApi.getBean(beanName) - return UtModelConstructor.createOnlyUserClassesConstructor( - pathsToUserClasses = classpathToConstruct, - utModelWithCompositeOriginConstructorFinder = instrumentationContext::findUtModelWithCompositeOriginConstructor - ).construct(bean, bean::class.java.id) - } - fun getRepositoryDescriptions(classId: ClassId): Set { val relevantBeanNames = getRelevantBeans(classId.jClass) val repositoryDescriptions = springApi.resolveRepositories(relevantBeanNames.toSet(), userSourcesClassLoader) @@ -149,6 +165,19 @@ class SpringUtExecutionInstrumentation( } } + override fun InstrumentedProcessModel.setupAdditionalRdResponses(kryoHelper: KryoHelper, watchdog: IdleWatchdog) { + watchdog.measureTimeForActiveCall(getRelevantSpringRepositories, "Getting Spring repositories") { params -> + val classId: ClassId = kryoHelper.readObject(params.classId) + val repositoryDescriptions = getRepositoryDescriptions(classId) + GetSpringRepositoriesResult(kryoHelper.writeObject(repositoryDescriptions)) + } + watchdog.measureTimeForActiveCall(tryLoadingSpringContext, "Trying to load Spring application context") { params -> + val contextLoadingResult = tryLoadingSpringContext() + TryLoadingSpringContextResult(kryoHelper.writeObject(contextLoadingResult)) + } + delegateInstrumentation.run { setupAdditionalRdResponses(kryoHelper, watchdog) } + } + class Factory( private val delegateInstrumentationFactory: UtExecutionInstrumentation.Factory<*>, private val springSettings: PresentSpringSettings, diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/process/InstrumentedProcessMain.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/process/InstrumentedProcessMain.kt index 34bb5300c7..12b2f996b3 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/process/InstrumentedProcessMain.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/process/InstrumentedProcessMain.kt @@ -6,19 +6,14 @@ import com.jetbrains.rd.util.reactive.adviseOnce import kotlinx.coroutines.* import org.mockito.Mockito import org.utbot.common.* -import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.util.UtContext import org.utbot.framework.process.kryo.KryoHelper import org.utbot.instrumentation.agent.Agent import org.utbot.instrumentation.instrumentation.Instrumentation import org.utbot.instrumentation.instrumentation.coverage.CoverageInstrumentation -import org.utbot.instrumentation.instrumentation.spring.SpringUtExecutionInstrumentation import org.utbot.instrumentation.process.generated.CollectCoverageResult -import org.utbot.instrumentation.process.generated.GetSpringBeanResult -import org.utbot.instrumentation.process.generated.GetSpringRepositoriesResult import org.utbot.instrumentation.process.generated.InstrumentedProcessModel import org.utbot.instrumentation.process.generated.InvokeMethodCommandResult -import org.utbot.instrumentation.process.generated.TryLoadingSpringContextResult import org.utbot.instrumentation.process.generated.instrumentedProcessModel import org.utbot.rd.IdleWatchdog import org.utbot.rd.ClientProtocolBuilder @@ -149,6 +144,7 @@ private fun InstrumentedProcessModel.setup(kryoHelper: KryoHelper, watchdog: Idl Agent.dynamicClassTransformer.useBytecodeTransformation = params.useBytecodeTransformation Agent.dynamicClassTransformer.transformer = instrumentation Agent.dynamicClassTransformer.addUserPaths(pathsToUserClasses) + instrumentation.run { setupAdditionalRdResponses(kryoHelper, watchdog) } } watchdog.measureTimeForActiveCall(addPaths, "User and dependency classpath setup") { params -> pathsToUserClasses = params.pathsToUserClasses.split(File.pathSeparatorChar).toSet() @@ -162,18 +158,4 @@ private fun InstrumentedProcessModel.setup(kryoHelper: KryoHelper, watchdog: Idl val result = (instrumentation as CoverageInstrumentation).collectCoverageInfo(anyClass) CollectCoverageResult(kryoHelper.writeObject(result)) } - watchdog.measureTimeForActiveCall(getSpringBean, "Getting Spring bean") { params -> - val springUtExecutionInstrumentation = instrumentation as SpringUtExecutionInstrumentation - val beanModel = springUtExecutionInstrumentation.getBeanModel(params.beanName, pathsToUserClasses) - GetSpringBeanResult(kryoHelper.writeObject(beanModel)) - } - watchdog.measureTimeForActiveCall(getRelevantSpringRepositories, "Getting Spring repositories") { params -> - val classId: ClassId = kryoHelper.readObject(params.classId) - val repositoryDescriptions = (instrumentation as SpringUtExecutionInstrumentation).getRepositoryDescriptions(classId) - GetSpringRepositoriesResult(kryoHelper.writeObject(repositoryDescriptions)) - } - watchdog.measureTimeForActiveCall(tryLoadingSpringContext, "Trying to load Spring application context") { params -> - val contextLoadingResult = (instrumentation as SpringUtExecutionInstrumentation).tryLoadingSpringContext() - TryLoadingSpringContextResult(kryoHelper.writeObject(contextLoadingResult)) - } } \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/rd/InstrumentedProcess.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/rd/InstrumentedProcess.kt index 0bfc2ac99d..d752643d3f 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/rd/InstrumentedProcess.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/rd/InstrumentedProcess.kt @@ -7,14 +7,12 @@ import org.utbot.common.debug import org.utbot.common.getPid import org.utbot.common.measureTime import org.utbot.framework.UtSettings -import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.services.WorkingDirService import org.utbot.framework.process.AbstractRDProcessCompanion import org.utbot.framework.process.kryo.KryoHelper import org.utbot.instrumentation.instrumentation.Instrumentation import org.utbot.instrumentation.process.DISABLE_SANDBOX_OPTION import org.utbot.instrumentation.process.generated.AddPathsParams -import org.utbot.instrumentation.process.generated.GetSpringBeanParams import org.utbot.instrumentation.process.generated.InstrumentedProcessModel import org.utbot.instrumentation.process.generated.SetInstrumentationParams import org.utbot.instrumentation.process.generated.instrumentedProcessModel @@ -24,7 +22,6 @@ import org.utbot.rd.generated.LoggerModel import org.utbot.rd.generated.loggerModel import org.utbot.rd.loggers.setup import org.utbot.rd.onSchedulerBlocking -import org.utbot.rd.startBlocking import org.utbot.rd.startUtProcessWithRdServer import org.utbot.rd.terminateOnException import java.io.File @@ -141,7 +138,4 @@ class InstrumentedProcess private constructor( return proc } } - - fun getBean(beanName: String): UtModel = - kryoHelper.readObject(instrumentedProcessModel.getSpringBean.startBlocking(GetSpringBeanParams(beanName)).beanModel) } \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/rd/generated/InstrumentedProcessModel.Generated.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/rd/generated/InstrumentedProcessModel.Generated.kt index 5500b9121b..2fe14c35d9 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/rd/generated/InstrumentedProcessModel.Generated.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/rd/generated/InstrumentedProcessModel.Generated.kt @@ -25,7 +25,6 @@ class InstrumentedProcessModel private constructor( private val _invokeMethodCommand: RdCall, private val _collectCoverage: RdCall, private val _computeStaticField: RdCall, - private val _getSpringBean: RdCall, private val _getRelevantSpringRepositories: RdCall, private val _tryLoadingSpringContext: RdCall ) : RdExtBase() { @@ -42,8 +41,6 @@ class InstrumentedProcessModel private constructor( serializers.register(CollectCoverageResult) serializers.register(ComputeStaticFieldParams) serializers.register(ComputeStaticFieldResult) - serializers.register(GetSpringBeanParams) - serializers.register(GetSpringBeanResult) serializers.register(GetSpringRepositoriesParams) serializers.register(GetSpringRepositoriesResult) serializers.register(TryLoadingSpringContextResult) @@ -67,7 +64,7 @@ class InstrumentedProcessModel private constructor( } - const val serializationHash = 2667021258656776274L + const val serializationHash = 8567129171874407469L } override val serializersOwner: ISerializersOwner get() = InstrumentedProcessModel @@ -109,11 +106,6 @@ class InstrumentedProcessModel private constructor( */ val computeStaticField: RdCall get() = _computeStaticField - /** - * Gets Spring bean by name (requires Spring instrumentation) - */ - val getSpringBean: RdCall get() = _getSpringBean - /** * Gets a list of [SpringRepositoryId]s that class specified by the [ClassId] (possibly indirectly) depends on (requires Spring instrumentation) */ @@ -133,7 +125,6 @@ class InstrumentedProcessModel private constructor( _invokeMethodCommand.async = true _collectCoverage.async = true _computeStaticField.async = true - _getSpringBean.async = true _getRelevantSpringRepositories.async = true _tryLoadingSpringContext.async = true } @@ -145,7 +136,6 @@ class InstrumentedProcessModel private constructor( bindableChildren.add("invokeMethodCommand" to _invokeMethodCommand) bindableChildren.add("collectCoverage" to _collectCoverage) bindableChildren.add("computeStaticField" to _computeStaticField) - bindableChildren.add("getSpringBean" to _getSpringBean) bindableChildren.add("getRelevantSpringRepositories" to _getRelevantSpringRepositories) bindableChildren.add("tryLoadingSpringContext" to _tryLoadingSpringContext) } @@ -159,7 +149,6 @@ class InstrumentedProcessModel private constructor( RdCall(InvokeMethodCommandParams, InvokeMethodCommandResult), RdCall(CollectCoverageParams, CollectCoverageResult), RdCall(ComputeStaticFieldParams, ComputeStaticFieldResult), - RdCall(GetSpringBeanParams, GetSpringBeanResult), RdCall(GetSpringRepositoriesParams, GetSpringRepositoriesResult), RdCall(FrameworkMarshallers.Void, TryLoadingSpringContextResult) ) @@ -176,7 +165,6 @@ class InstrumentedProcessModel private constructor( print("invokeMethodCommand = "); _invokeMethodCommand.print(printer); println() print("collectCoverage = "); _collectCoverage.print(printer); println() print("computeStaticField = "); _computeStaticField.print(printer); println() - print("getSpringBean = "); _getSpringBean.print(printer); println() print("getRelevantSpringRepositories = "); _getRelevantSpringRepositories.print(printer); println() print("tryLoadingSpringContext = "); _tryLoadingSpringContext.print(printer); println() } @@ -191,7 +179,6 @@ class InstrumentedProcessModel private constructor( _invokeMethodCommand.deepClonePolymorphic(), _collectCoverage.deepClonePolymorphic(), _computeStaticField.deepClonePolymorphic(), - _getSpringBean.deepClonePolymorphic(), _getRelevantSpringRepositories.deepClonePolymorphic(), _tryLoadingSpringContext.deepClonePolymorphic() ) @@ -490,120 +477,6 @@ data class ComputeStaticFieldResult ( /** * #### Generated from [InstrumentedProcessModel.kt:45] */ -data class GetSpringBeanParams ( - val beanName: String -) : IPrintable { - //companion - - companion object : IMarshaller { - override val _type: KClass = GetSpringBeanParams::class - - @Suppress("UNCHECKED_CAST") - override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): GetSpringBeanParams { - val beanName = buffer.readString() - return GetSpringBeanParams(beanName) - } - - override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: GetSpringBeanParams) { - buffer.writeString(value.beanName) - } - - - } - //fields - //methods - //initializer - //secondary constructor - //equals trait - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other == null || other::class != this::class) return false - - other as GetSpringBeanParams - - if (beanName != other.beanName) return false - - return true - } - //hash code trait - override fun hashCode(): Int { - var __r = 0 - __r = __r*31 + beanName.hashCode() - return __r - } - //pretty print - override fun print(printer: PrettyPrinter) { - printer.println("GetSpringBeanParams (") - printer.indent { - print("beanName = "); beanName.print(printer); println() - } - printer.print(")") - } - //deepClone - //contexts -} - - -/** - * #### Generated from [InstrumentedProcessModel.kt:49] - */ -data class GetSpringBeanResult ( - val beanModel: ByteArray -) : IPrintable { - //companion - - companion object : IMarshaller { - override val _type: KClass = GetSpringBeanResult::class - - @Suppress("UNCHECKED_CAST") - override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): GetSpringBeanResult { - val beanModel = buffer.readByteArray() - return GetSpringBeanResult(beanModel) - } - - override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: GetSpringBeanResult) { - buffer.writeByteArray(value.beanModel) - } - - - } - //fields - //methods - //initializer - //secondary constructor - //equals trait - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other == null || other::class != this::class) return false - - other as GetSpringBeanResult - - if (!(beanModel contentEquals other.beanModel)) return false - - return true - } - //hash code trait - override fun hashCode(): Int { - var __r = 0 - __r = __r*31 + beanModel.contentHashCode() - return __r - } - //pretty print - override fun print(printer: PrettyPrinter) { - printer.println("GetSpringBeanResult (") - printer.indent { - print("beanModel = "); beanModel.print(printer); println() - } - printer.print(")") - } - //deepClone - //contexts -} - - -/** - * #### Generated from [InstrumentedProcessModel.kt:53] - */ data class GetSpringRepositoriesParams ( val classId: ByteArray ) : IPrintable { @@ -659,7 +532,7 @@ data class GetSpringRepositoriesParams ( /** - * #### Generated from [InstrumentedProcessModel.kt:57] + * #### Generated from [InstrumentedProcessModel.kt:49] */ data class GetSpringRepositoriesResult ( val springRepositoryIds: ByteArray @@ -911,7 +784,7 @@ data class SetInstrumentationParams ( /** - * #### Generated from [InstrumentedProcessModel.kt:61] + * #### Generated from [InstrumentedProcessModel.kt:53] */ data class TryLoadingSpringContextResult ( val springContextLoadingResult: ByteArray 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 f4eb63966b..3410ae8cab 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,7 +27,7 @@ abstract class BaseConstructorTest { protected fun computeReconstructed(value: T): T { val model = UtModelConstructor( objectToModelCache = IdentityHashMap(), - idGenerator = StateBeforeAwareIdGenerator(preExistingModels = emptySet()), + idGenerator = StateBeforeAwareIdGenerator(allPreExistingModels = emptySet()), utModelWithCompositeOriginConstructorFinder = ::findUtCustomModelConstructor ).construct(value, value::class.java.id) diff --git a/utbot-rd/src/main/rdgen/org/utbot/rd/models/InstrumentedProcessModel.kt b/utbot-rd/src/main/rdgen/org/utbot/rd/models/InstrumentedProcessModel.kt index db0d827687..f9d3d76f50 100644 --- a/utbot-rd/src/main/rdgen/org/utbot/rd/models/InstrumentedProcessModel.kt +++ b/utbot-rd/src/main/rdgen/org/utbot/rd/models/InstrumentedProcessModel.kt @@ -42,14 +42,6 @@ object InstrumentedProcessModel : Ext(InstrumentedProcessRoot) { field("result", array(PredefinedType.byte)) } - val GetSpringBeanParams = structdef { - field("beanName", PredefinedType.string) - } - - val GetSpringBeanResult = structdef { - field("beanModel", array(PredefinedType.byte)) - } - val GetSpringRepositoriesParams = structdef { field("classId", array(PredefinedType.byte)) } @@ -97,10 +89,6 @@ object InstrumentedProcessModel : Ext(InstrumentedProcessRoot) { "This command is sent to the instrumented process from the [ConcreteExecutor] if user wants to get value of static field\n" + "[fieldId]" } - call("GetSpringBean", GetSpringBeanParams, GetSpringBeanResult).apply { - async - documentation = "Gets Spring bean by name (requires Spring instrumentation)" - } call("getRelevantSpringRepositories", GetSpringRepositoriesParams, GetSpringRepositoriesResult).apply { async documentation = "Gets a list of [SpringRepositoryId]s that class specified by the [ClassId]" + From b1075f6322039d17e9d5ac914e08f11815fe9bda Mon Sep 17 00:00:00 2001 From: IlyaMuravjov <71839386+IlyaMuravjov@users.noreply.github.com> Date: Mon, 28 Aug 2023 15:59:43 +0300 Subject: [PATCH 6/6] Let concrete add additional mocks to `UtCompositeModel` created by the engine #2549 (#2553) --- .../src/main/kotlin/org/utbot/framework/plugin/api/Api.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 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 3fad5afca8..755ad82928 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 @@ -502,7 +502,11 @@ data class UtClassRefModel( * - 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 + * created by: + * - fuzzer which doesn't know which methods will actually be called + * - engine which also doesn't know which methods will actually be + * called during concrete execution that may be only **partially** + * backed up by the symbolic analysis * * [fields] contains non-static fields */ @@ -512,7 +516,7 @@ data class UtCompositeModel( val isMock: Boolean, val fields: MutableMap = mutableMapOf(), val mocks: MutableMap> = mutableMapOf(), - val canHaveRedundantOrMissingMocks: Boolean = false, + val canHaveRedundantOrMissingMocks: Boolean = true, ) : UtReferenceModel(id, classId) { //TODO: SAT-891 - rewrite toString() method override fun toString() = withToStringThreadLocalReentrancyGuard {