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 e739432f5b..5d2de395f2 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]" +