Skip to content

Commit

Permalink
Call entityManager.flush() on every entityManager usage and dynam…
Browse files Browse the repository at this point in the history
…ically 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
  • Loading branch information
IlyaMuravjov authored Aug 25, 2023
1 parent 1931f26 commit f9c9534
Show file tree
Hide file tree
Showing 27 changed files with 592 additions and 315 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -767,15 +770,15 @@ abstract class UtCustomModel(
modelName: String = id.toString(),
override val origin: UtCompositeModel? = null,
) : UtModelWithCompositeOrigin(id, classId, modelName, origin) {
abstract val dependencies: Collection<UtModel>
abstract fun shallowMap(mapper: UtModelMapper): UtCustomModel
}

object UtSpringContextModel : UtCustomModel(
id = null,
classId = SpringModelUtils.applicationContextClassId,
modelName = "applicationContext"
) {
override val dependencies: Collection<UtModel> 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
Expand All @@ -789,7 +792,7 @@ class UtSpringEntityManagerModel : UtCustomModel(
classId = SpringModelUtils.entityManagerClassIds.first(),
modelName = "entityManager"
) {
override val dependencies: Collection<UtModel> 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
Expand Down Expand Up @@ -820,7 +823,10 @@ data class UtSpringMockMvcResultActionsModel(
id = id,
modelName = "mockMvcResultActions@$id"
) {
override val dependencies: Collection<UtModel> get() = emptySet()
override fun shallowMap(mapper: UtModelMapper) = copy(
origin = origin?.mapPreservingType<UtCompositeModel>(mapper),
model = model?.map(mapper)
)
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -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<T, T>`)
*/
private val cache = mutableMapOf<UtModel, UtModel>()

private val allInputtedModels get() = cache.keys
private val allOutputtedModels get() = cache.values

override fun <T : UtModel> map(model: T, clazz: Class<T>): 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<UtCompositeModel>(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<UtModel> =
UtModelDeepMapper(UtModelNoopMapper).also(block).allInputtedModels
}
}
Original file line number Diff line number Diff line change
@@ -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 <T : UtModel> map(model: T, clazz: Class<T>): T
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.utbot.framework.plugin.api.mapper

import org.utbot.framework.plugin.api.UtModel

object UtModelNoopMapper : UtModelMapper {
override fun <T : UtModel> map(model: T, clazz: Class<T>): T = model
}
Original file line number Diff line number Diff line change
@@ -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<UtModel, UtModel>()

override fun <T : UtModel> map(model: T, clazz: Class<T>): T {
val mapped = cache.getOrPut(model) { mapper(model) }
return if (clazz.isInstance(mapped)) clazz.cast(mapped)
else model
}
}
Original file line number Diff line number Diff line change
@@ -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 <reified T : UtModel> T.mapPreservingType(mapper: UtModelMapper): T =
mapper.map(this, T::class.java)

fun UtModel.map(mapper: UtModelMapper) = mapPreservingType<UtModel>(mapper)

fun List<UtModel>.mapModels(mapper: UtModelMapper): List<UtModel> =
map { model -> model.map(mapper) }

fun <K> Map<K, UtModel>.mapModelValues(mapper: UtModelMapper): Map<K, UtModel> =
mapValues { (_, model) -> model.map(mapper) }

fun UtStatementModel.mapModels(mapper: UtModelMapper): UtStatementModel =
when(this) {
is UtStatementCallModel -> mapModels(mapper)
is UtDirectSetFieldModel -> UtDirectSetFieldModel(
instance = instance.mapPreservingType<UtReferenceModel>(mapper),
fieldId = fieldId,
fieldModel = fieldModel.map(mapper)
)
}

fun UtStatementCallModel.mapModels(mapper: UtModelMapper): UtStatementCallModel =
when(this) {
is UtDirectGetFieldModel -> UtDirectGetFieldModel(
instance = instance.mapPreservingType<UtReferenceModel>(mapper),
fieldAccess = fieldAccess,
)
is UtExecutableCallModel -> UtExecutableCallModel(
instance = instance?.mapPreservingType<UtReferenceModel>(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)
)
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,6 @@ data class CgMethodTestSet(
return substituteExecutions(symbolicExecutionsWithoutMocking)
}

private fun substituteExecutions(newExecutions: List<UtExecution>): CgMethodTestSet =
fun substituteExecutions(newExecutions: List<UtExecution>): CgMethodTestSet =
copy().apply { executions = newExecutions }
}
Loading

0 comments on commit f9c9534

Please sign in to comment.