Skip to content

Commit f9c9534

Browse files
authored
Call entityManager.flush() on every entityManager usage and dynamically remove failing UtStatementCallModels (#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
1 parent 1931f26 commit f9c9534

File tree

27 files changed

+592
-315
lines changed

27 files changed

+592
-315
lines changed

utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ import java.io.File
5555
import kotlin.contracts.ExperimentalContracts
5656
import kotlin.contracts.contract
5757
import org.utbot.common.isAbstract
58+
import org.utbot.framework.plugin.api.mapper.UtModelMapper
59+
import org.utbot.framework.plugin.api.mapper.map
60+
import org.utbot.framework.plugin.api.mapper.mapPreservingType
5861
import org.utbot.framework.plugin.api.util.SpringModelUtils
5962
import org.utbot.framework.process.OpenModulesContainer
6063
import soot.SootMethod
@@ -767,15 +770,15 @@ abstract class UtCustomModel(
767770
modelName: String = id.toString(),
768771
override val origin: UtCompositeModel? = null,
769772
) : UtModelWithCompositeOrigin(id, classId, modelName, origin) {
770-
abstract val dependencies: Collection<UtModel>
773+
abstract fun shallowMap(mapper: UtModelMapper): UtCustomModel
771774
}
772775

773776
object UtSpringContextModel : UtCustomModel(
774777
id = null,
775778
classId = SpringModelUtils.applicationContextClassId,
776779
modelName = "applicationContext"
777780
) {
778-
override val dependencies: Collection<UtModel> get() = emptySet()
781+
override fun shallowMap(mapper: UtModelMapper) = this
779782

780783
// NOTE that overriding equals is required just because without it
781784
// we will lose equality for objects after deserialization
@@ -789,7 +792,7 @@ class UtSpringEntityManagerModel : UtCustomModel(
789792
classId = SpringModelUtils.entityManagerClassIds.first(),
790793
modelName = "entityManager"
791794
) {
792-
override val dependencies: Collection<UtModel> get() = emptySet()
795+
override fun shallowMap(mapper: UtModelMapper) = this
793796

794797
// NOTE that overriding equals is required just because without it
795798
// we will lose equality for objects after deserialization
@@ -820,7 +823,10 @@ data class UtSpringMockMvcResultActionsModel(
820823
id = id,
821824
modelName = "mockMvcResultActions@$id"
822825
) {
823-
override val dependencies: Collection<UtModel> get() = emptySet()
826+
override fun shallowMap(mapper: UtModelMapper) = copy(
827+
origin = origin?.mapPreservingType<UtCompositeModel>(mapper),
828+
model = model?.map(mapper)
829+
)
824830
}
825831

826832
/**
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package org.utbot.framework.plugin.api.mapper
2+
3+
import org.utbot.framework.plugin.api.UtArrayModel
4+
import org.utbot.framework.plugin.api.UtAssembleModel
5+
import org.utbot.framework.plugin.api.UtClassRefModel
6+
import org.utbot.framework.plugin.api.UtCompositeModel
7+
import org.utbot.framework.plugin.api.UtCustomModel
8+
import org.utbot.framework.plugin.api.UtEnumConstantModel
9+
import org.utbot.framework.plugin.api.UtLambdaModel
10+
import org.utbot.framework.plugin.api.UtModel
11+
import org.utbot.framework.plugin.api.UtNullModel
12+
import org.utbot.framework.plugin.api.UtPrimitiveModel
13+
import org.utbot.framework.plugin.api.UtReferenceModel
14+
import org.utbot.framework.plugin.api.UtVoidModel
15+
16+
/**
17+
* Performs deep mapping of [UtModel]s.
18+
*
19+
* NOTE:
20+
* - [shallowMapper] is invoked on models **before** mapping their sub models.
21+
* - [shallowMapper] is responsible for caching own results (it may be called repeatedly on same models).
22+
*/
23+
class UtModelDeepMapper private constructor(
24+
private val shallowMapper: UtModelMapper
25+
) : UtModelMapper {
26+
constructor(shallowMapper: (UtModel) -> UtModel) : this(UtModelSafeCastingCachingShallowMapper(shallowMapper))
27+
28+
/**
29+
* Keys are models that have been shallowly mapped by [shallowMapper].
30+
* Values are models that have been deeply mapped by this [UtModelDeepMapper].
31+
* Models are only associated with models of the same type (i.e. the cache type is actually `MutableMap<T, T>`)
32+
*/
33+
private val cache = mutableMapOf<UtModel, UtModel>()
34+
35+
private val allInputtedModels get() = cache.keys
36+
private val allOutputtedModels get() = cache.values
37+
38+
override fun <T : UtModel> map(model: T, clazz: Class<T>): T =
39+
clazz.cast(mapNestedModels(shallowMapper.map(model, clazz)))
40+
41+
/**
42+
* Maps models contained inside [model], but not the [model] itself.
43+
*/
44+
private fun mapNestedModels(model: UtModel): UtModel = cache.getOrPut(model) {
45+
when (model) {
46+
is UtNullModel,
47+
is UtPrimitiveModel,
48+
is UtEnumConstantModel,
49+
is UtClassRefModel,
50+
is UtVoidModel -> model
51+
is UtArrayModel -> mapNestedModels(model)
52+
is UtCompositeModel -> mapNestedModels(model)
53+
is UtLambdaModel -> mapNestedModels(model)
54+
is UtAssembleModel -> mapNestedModels(model)
55+
is UtCustomModel -> mapNestedModels(model)
56+
57+
// PythonModel, JsUtModel may be here
58+
else -> throw UnsupportedOperationException("UtModel $this cannot be mapped")
59+
}
60+
}
61+
62+
private fun mapNestedModels(model: UtArrayModel): UtReferenceModel {
63+
val mappedModel = UtArrayModel(
64+
id = model.id,
65+
classId = model.classId,
66+
length = model.length,
67+
constModel = model.constModel,
68+
stores = model.stores,
69+
)
70+
cache[model] = mappedModel
71+
72+
mappedModel.constModel = model.constModel.map(this)
73+
mappedModel.stores.putAll(model.stores.mapModelValues(this))
74+
75+
return mappedModel
76+
}
77+
78+
private fun mapNestedModels(model: UtCompositeModel): UtCompositeModel {
79+
val mappedModel = UtCompositeModel(
80+
id = model.id,
81+
classId = model.classId,
82+
isMock = model.isMock,
83+
)
84+
cache[model] = mappedModel
85+
86+
mappedModel.fields.putAll(model.fields.mapModelValues(this))
87+
mappedModel.mocks.putAll(model.mocks.mapValuesTo(mutableMapOf()) { it.value.mapModels(this@UtModelDeepMapper) })
88+
89+
return mappedModel
90+
}
91+
92+
private fun mapNestedModels(model: UtLambdaModel): UtReferenceModel = UtLambdaModel(
93+
id = model.id,
94+
samType = model.samType,
95+
declaringClass = model.declaringClass,
96+
lambdaName = model.lambdaName,
97+
capturedValues = model.capturedValues.mapModels(this@UtModelDeepMapper).toMutableList()
98+
)
99+
100+
private fun mapNestedModels(model: UtAssembleModel): UtReferenceModel = UtAssembleModel(
101+
id = model.id,
102+
classId = model.classId,
103+
modelName = model.modelName,
104+
instantiationCall = model.instantiationCall.mapModels(this),
105+
modificationsChainProvider = {
106+
cache[model] = this@UtAssembleModel
107+
model.modificationsChain.map { it.mapModels(this@UtModelDeepMapper) }
108+
},
109+
origin = model.origin?.mapPreservingType<UtCompositeModel>(this)
110+
)
111+
112+
private fun mapNestedModels(model: UtCustomModel): UtReferenceModel =
113+
model.shallowMap(this)
114+
115+
companion object {
116+
/**
117+
* Creates identity deep mapper, runs [block] on it, and returns the set of all models that
118+
* were mapped (i.e. deeply collects all models reachable from models passed to `collector`).
119+
*/
120+
fun collectAllModels(block: (collector: UtModelDeepMapper) -> Unit): Set<UtModel> =
121+
UtModelDeepMapper(UtModelNoopMapper).also(block).allInputtedModels
122+
}
123+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.utbot.framework.plugin.api.mapper
2+
3+
import org.utbot.framework.plugin.api.UtCompositeModel
4+
import org.utbot.framework.plugin.api.UtModel
5+
import org.utbot.framework.plugin.api.UtModelWithCompositeOrigin
6+
7+
interface UtModelMapper {
8+
/**
9+
* Performs depending on the implementation deep or shallow mapping of the [model].
10+
*
11+
* In some cases (e.g. when mapping [UtModelWithCompositeOrigin.origin]) you may want to get result
12+
* of some specific type (e.g. [UtCompositeModel]), only then you should specify specific value for [clazz].
13+
*
14+
* NOTE: if you are fine with result model and [model] having different types, then you should
15+
* use `UtModel::class.java` as a value for [clazz] or just use [UtModel.map].
16+
*/
17+
fun <T : UtModel> map(model: T, clazz: Class<T>): T
18+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package org.utbot.framework.plugin.api.mapper
2+
3+
import org.utbot.framework.plugin.api.UtModel
4+
5+
object UtModelNoopMapper : UtModelMapper {
6+
override fun <T : UtModel> map(model: T, clazz: Class<T>): T = model
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package org.utbot.framework.plugin.api.mapper
2+
3+
import org.utbot.framework.plugin.api.UtModel
4+
5+
class UtModelSafeCastingCachingShallowMapper(
6+
val mapper: (UtModel) -> UtModel
7+
) : UtModelMapper {
8+
private val cache = mutableMapOf<UtModel, UtModel>()
9+
10+
override fun <T : UtModel> map(model: T, clazz: Class<T>): T {
11+
val mapped = cache.getOrPut(model) { mapper(model) }
12+
return if (clazz.isInstance(mapped)) clazz.cast(mapped)
13+
else model
14+
}
15+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package org.utbot.framework.plugin.api.mapper
2+
3+
import org.utbot.framework.plugin.api.EnvironmentModels
4+
import org.utbot.framework.plugin.api.UtDirectGetFieldModel
5+
import org.utbot.framework.plugin.api.UtDirectSetFieldModel
6+
import org.utbot.framework.plugin.api.UtExecutableCallModel
7+
import org.utbot.framework.plugin.api.UtExecution
8+
import org.utbot.framework.plugin.api.UtInstrumentation
9+
import org.utbot.framework.plugin.api.UtModel
10+
import org.utbot.framework.plugin.api.UtNewInstanceInstrumentation
11+
import org.utbot.framework.plugin.api.UtReferenceModel
12+
import org.utbot.framework.plugin.api.UtStatementCallModel
13+
import org.utbot.framework.plugin.api.UtStatementModel
14+
import org.utbot.framework.plugin.api.UtStaticMethodInstrumentation
15+
16+
inline fun <reified T : UtModel> T.mapPreservingType(mapper: UtModelMapper): T =
17+
mapper.map(this, T::class.java)
18+
19+
fun UtModel.map(mapper: UtModelMapper) = mapPreservingType<UtModel>(mapper)
20+
21+
fun List<UtModel>.mapModels(mapper: UtModelMapper): List<UtModel> =
22+
map { model -> model.map(mapper) }
23+
24+
fun <K> Map<K, UtModel>.mapModelValues(mapper: UtModelMapper): Map<K, UtModel> =
25+
mapValues { (_, model) -> model.map(mapper) }
26+
27+
fun UtStatementModel.mapModels(mapper: UtModelMapper): UtStatementModel =
28+
when(this) {
29+
is UtStatementCallModel -> mapModels(mapper)
30+
is UtDirectSetFieldModel -> UtDirectSetFieldModel(
31+
instance = instance.mapPreservingType<UtReferenceModel>(mapper),
32+
fieldId = fieldId,
33+
fieldModel = fieldModel.map(mapper)
34+
)
35+
}
36+
37+
fun UtStatementCallModel.mapModels(mapper: UtModelMapper): UtStatementCallModel =
38+
when(this) {
39+
is UtDirectGetFieldModel -> UtDirectGetFieldModel(
40+
instance = instance.mapPreservingType<UtReferenceModel>(mapper),
41+
fieldAccess = fieldAccess,
42+
)
43+
is UtExecutableCallModel -> UtExecutableCallModel(
44+
instance = instance?.mapPreservingType<UtReferenceModel>(mapper),
45+
executable = executable,
46+
params = params.mapModels(mapper)
47+
)
48+
}
49+
50+
fun EnvironmentModels.mapModels(mapper: UtModelMapper) = EnvironmentModels(
51+
thisInstance = thisInstance?.map(mapper),
52+
statics = statics.mapModelValues(mapper),
53+
parameters = parameters.mapModels(mapper),
54+
executableToCall = executableToCall,
55+
)
56+
57+
fun UtInstrumentation.mapModels(mapper: UtModelMapper) = when (this) {
58+
is UtNewInstanceInstrumentation -> copy(instances = instances.mapModels(mapper))
59+
is UtStaticMethodInstrumentation -> copy(values = values.mapModels(mapper))
60+
}
61+
62+
fun UtExecution.mapStateBeforeModels(mapper: UtModelMapper) = copy(
63+
stateBefore = stateBefore.mapModels(mapper)
64+
)

utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/SpringModelUtils.kt

Lines changed: 45 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,17 @@ object SpringModelUtils {
6969
)
7070
}
7171

72+
val flushMethodIdOrNull: MethodId?
73+
get() {
74+
return MethodId(
75+
classId = entityManagerClassIds.firstOrNull() ?: return null,
76+
name = "flush",
77+
returnType = voidClassId,
78+
parameters = listOf(),
79+
bypassesSandbox = true // TODO may be we can use some alternative sandbox that has more permissions
80+
)
81+
}
82+
7283
val detachMethodIdOrNull: MethodId?
7384
get() {
7485
return MethodId(
@@ -182,12 +193,13 @@ object SpringModelUtils {
182193
parameters = listOf(httpHeaderClassId)
183194
)
184195

185-
private val mockHttpServletCookieMethodId = MethodId(
186-
classId = mockHttpServletRequestBuilderClassId,
187-
name = "cookie",
188-
returnType = mockHttpServletRequestBuilderClassId,
189-
parameters = listOf(getArrayClassIdByElementClassId(cookieClassId))
190-
)
196+
// // TODO uncomment when #2542 is fixed
197+
// private val mockHttpServletCookieMethodId = MethodId(
198+
// classId = mockHttpServletRequestBuilderClassId,
199+
// name = "cookie",
200+
// returnType = mockHttpServletRequestBuilderClassId,
201+
// parameters = listOf(getArrayClassIdByElementClassId(cookieClassId))
202+
// )
191203

192204
private val mockHttpServletContentTypeMethodId = MethodId(
193205
classId = mockHttpServletRequestBuilderClassId,
@@ -376,9 +388,10 @@ object SpringModelUtils {
376388
val headersContentModel = createHeadersContentModel(methodId, arguments, idGenerator)
377389
requestBuilderModel = addHeadersToRequestBuilderModel(headersContentModel, requestBuilderModel, idGenerator)
378390

379-
val cookieValuesModel = createCookieValuesModel(methodId, arguments, idGenerator)
380-
requestBuilderModel =
381-
addCookiesToRequestBuilderModel(cookieValuesModel, requestBuilderModel, idGenerator)
391+
// // TODO uncomment when #2542 is fixed
392+
// val cookieValuesModel = createCookieValuesModel(methodId, arguments, idGenerator)
393+
// requestBuilderModel =
394+
// addCookiesToRequestBuilderModel(cookieValuesModel, requestBuilderModel, idGenerator)
382395

383396
val requestAttributes = collectArgumentsWithAnnotationModels(methodId, requestAttributesClassId, arguments)
384397
requestBuilderModel =
@@ -455,28 +468,29 @@ object SpringModelUtils {
455468
return requestBuilderModel
456469
}
457470

458-
private fun addCookiesToRequestBuilderModel(
459-
cookieValuesModel: UtArrayModel,
460-
requestBuilderModel: UtAssembleModel,
461-
idGenerator: () -> Int
462-
): UtAssembleModel {
463-
@Suppress("NAME_SHADOWING")
464-
var requestBuilderModel = requestBuilderModel
465-
466-
if(cookieValuesModel.length > 0) {
467-
requestBuilderModel = UtAssembleModel(
468-
id = idGenerator(),
469-
classId = mockHttpServletRequestBuilderClassId,
470-
modelName = "requestBuilder",
471-
instantiationCall = UtExecutableCallModel(
472-
instance = requestBuilderModel,
473-
executable = mockHttpServletCookieMethodId,
474-
params = listOf(cookieValuesModel)
475-
)
476-
)
477-
}
478-
return requestBuilderModel
479-
}
471+
// // TODO uncomment when #2542 is fixed
472+
// private fun addCookiesToRequestBuilderModel(
473+
// cookieValuesModel: UtArrayModel,
474+
// requestBuilderModel: UtAssembleModel,
475+
// idGenerator: () -> Int
476+
// ): UtAssembleModel {
477+
// @Suppress("NAME_SHADOWING")
478+
// var requestBuilderModel = requestBuilderModel
479+
//
480+
// if(cookieValuesModel.length > 0) {
481+
// requestBuilderModel = UtAssembleModel(
482+
// id = idGenerator(),
483+
// classId = mockHttpServletRequestBuilderClassId,
484+
// modelName = "requestBuilder",
485+
// instantiationCall = UtExecutableCallModel(
486+
// instance = requestBuilderModel,
487+
// executable = mockHttpServletCookieMethodId,
488+
// params = listOf(cookieValuesModel)
489+
// )
490+
// )
491+
// }
492+
// return requestBuilderModel
493+
// }
480494

481495
private fun addHeadersToRequestBuilderModel(
482496
headersContentModel: UtAssembleModel,

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/CgMethodTestSet.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,6 @@ data class CgMethodTestSet(
135135
return substituteExecutions(symbolicExecutionsWithoutMocking)
136136
}
137137

138-
private fun substituteExecutions(newExecutions: List<UtExecution>): CgMethodTestSet =
138+
fun substituteExecutions(newExecutions: List<UtExecution>): CgMethodTestSet =
139139
copy().apply { executions = newExecutions }
140140
}

0 commit comments

Comments
 (0)