Skip to content

Commit f08a328

Browse files
authored
Support creation and persistence of valid entities #2340 (#2475)
* Support creation and persistence of valid entities * Make `GeneratedFieldValueProvider` be aware of entity class and not just field declaring class * Introduce `PreservableFuzzedTypeProperty` * Make so `persist()` and `detach()` modifications are always applied for MANAGED and DETACHED entities * Actualize `HandlerClassesLoader.loadClass` javadoc * Introduce value replacement for common collection types in `UtModelConstructor` * Make minimization encourage use of non-hardcoded values * Improve valid value providers * Add `@WithMockUser` in concrete execution and code generation * Fix compilation after rebase * Fix inconsistent naming after rebase * Fix inconsistent naming after one more rebase * Fix `stateBeforeDependentModels` after #2473 * Make `getDependenciesForBean` actually use `analyzedBeanNames` * Add `@WithMockUser` in code generation
1 parent 0826bf7 commit f08a328

File tree

31 files changed

+809
-167
lines changed

31 files changed

+809
-167
lines changed

gradle.properties

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,13 @@ openblasVersion=0.3.10-1.5.4
9393
arpackNgVersion=3.7.0-1.5.4
9494
commonsLoggingVersion=1.2
9595
commonsIOVersion=2.11.0
96+
javaxVersion=2.2
97+
jakartaVersion=3.1.0
9698

9799
# use latest Java 8 compaitable Spring and Spring Boot versions
98100
springVersion=5.3.28
99101
springBootVersion=2.7.13
102+
springSecurityVersion=5.8.5
100103

101104
# configuration for build server
102105
#
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package org.utbot.common
2+
3+
import java.util.*
4+
5+
/**
6+
* @param TOwner used purely to make type system enforce the use of properties with correct receiver,
7+
* e.g. if property `NotEmptyTypeFlag` is defined for `FuzzedType` it can't be used on `CgContext`.
8+
*
9+
* **See also:** [this post](https://stackoverflow.com/a/58219723/10536125).
10+
*/
11+
interface DynamicProperty<TOwner, T>
12+
13+
data class InitialisedDynamicProperty<TOwner, T>(
14+
val property: DynamicProperty<TOwner, T>,
15+
val value: T
16+
)
17+
18+
fun <TOwner, T> DynamicProperty<TOwner, T>.withValue(value: T) =
19+
InitialisedDynamicProperty(this, value)
20+
21+
interface DynamicProperties<TOwner> {
22+
val entries: Set<InitialisedDynamicProperty<TOwner, *>>
23+
24+
operator fun <T> get(property: DynamicProperty<TOwner, T>): T?
25+
fun <T> getValue(property: DynamicProperty<TOwner, T>): T
26+
operator fun contains(property: DynamicProperty<TOwner, *>): Boolean
27+
28+
/**
29+
* Two instances of [DynamicProperties] implementations are equal iff their [entries] are equal
30+
*/
31+
override fun equals(other: Any?): Boolean
32+
override fun hashCode(): Int
33+
}
34+
35+
interface MutableDynamicProperties<TOwner> : DynamicProperties<TOwner> {
36+
operator fun <T> set(property: DynamicProperty<TOwner, T>, value: T)
37+
}
38+
39+
fun <TOwner> Iterable<InitialisedDynamicProperty<TOwner, *>>.toMutableDynamicProperties(): MutableDynamicProperties<TOwner> =
40+
DynamicPropertiesImpl<TOwner>().also { properties ->
41+
forEach { properties.add(it) }
42+
}
43+
44+
fun <TOwner> Iterable<InitialisedDynamicProperty<TOwner, *>>.toDynamicProperties(): DynamicProperties<TOwner> =
45+
toMutableDynamicProperties()
46+
47+
fun <TOwner> mutableDynamicPropertiesOf(
48+
vararg initialisedDynamicProperties: InitialisedDynamicProperty<TOwner, *>
49+
): MutableDynamicProperties<TOwner> = initialisedDynamicProperties.asIterable().toMutableDynamicProperties()
50+
51+
fun <TOwner> dynamicPropertiesOf(
52+
vararg initialisedDynamicProperties: InitialisedDynamicProperty<TOwner, *>
53+
): DynamicProperties<TOwner> = initialisedDynamicProperties.asIterable().toDynamicProperties()
54+
55+
fun <TOwner> DynamicProperties<TOwner>.withoutProperty(property: DynamicProperty<TOwner, *>): DynamicProperties<TOwner> =
56+
entries.filterNot { it.property == property }.toMutableDynamicProperties()
57+
58+
operator fun <TOwner> DynamicProperties<TOwner>.plus(other: DynamicProperties<TOwner>): DynamicProperties<TOwner> =
59+
(entries + other.entries).toMutableDynamicProperties()
60+
61+
class DynamicPropertiesImpl<TOwner> : MutableDynamicProperties<TOwner> {
62+
/**
63+
* Actual type of [properties] should be `Map<DynamicProperty<TOwner, T>, T>`, but
64+
* such type is not representable withing kotlin type system, hence unchecked casts are
65+
* used later.
66+
*/
67+
private val properties = IdentityHashMap<DynamicProperty<TOwner, *>, Any?>()
68+
override val entries: Set<InitialisedDynamicProperty<TOwner, *>>
69+
get() = properties.mapTo(mutableSetOf()) { unsafeInitializedDynamicProperty(it.key, it.value) }
70+
71+
@Suppress("UNCHECKED_CAST")
72+
private fun <T> unsafeInitializedDynamicProperty(property: DynamicProperty<TOwner, T>, value: Any?) =
73+
InitialisedDynamicProperty(property, value as T)
74+
75+
@Suppress("UNCHECKED_CAST")
76+
override fun <T> get(property: DynamicProperty<TOwner, T>): T? =
77+
properties[property] as T?
78+
79+
@Suppress("UNCHECKED_CAST")
80+
override fun <T> getValue(property: DynamicProperty<TOwner, T>): T =
81+
properties.getValue(property) as T
82+
83+
override fun <T> set(property: DynamicProperty<TOwner, T>, value: T) {
84+
properties[property] = value
85+
}
86+
87+
override fun contains(property: DynamicProperty<TOwner, *>): Boolean =
88+
property in properties
89+
90+
fun <T> add(initialisedDynamicProperty: InitialisedDynamicProperty<TOwner, T>) =
91+
set(initialisedDynamicProperty.property, initialisedDynamicProperty.value)
92+
93+
override fun toString(): String {
94+
return "DynamicPropertiesImpl(properties=$properties)"
95+
}
96+
97+
override fun equals(other: Any?): Boolean {
98+
if (this === other) return true
99+
if (other !is DynamicProperties<*>) return false
100+
return entries == other.entries
101+
}
102+
103+
override fun hashCode(): Int =
104+
properties.hashCode()
105+
}

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

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
package org.utbot.framework.plugin.api
1010

11-
import mu.KotlinLogging
1211
import org.utbot.common.FileUtil
1312
import org.utbot.common.isDefaultValue
1413
import org.utbot.common.withToStringThreadLocalReentrancyGuard
@@ -778,15 +777,27 @@ object UtSpringContextModel : UtCustomModel(
778777
override fun hashCode(): Int = 0
779778
}
780779

780+
class UtSpringEntityManagerModel : UtCustomModel(
781+
id = null,
782+
classId = SpringModelUtils.entityManagerClassIds.first(),
783+
modelName = "entityManager"
784+
) {
785+
// NOTE that overriding equals is required just because without it
786+
// we will lose equality for objects after deserialization
787+
override fun equals(other: Any?): Boolean = other is UtSpringEntityManagerModel
788+
789+
override fun hashCode(): Int = 0
790+
}
791+
781792
data class SpringRepositoryId(
782793
val repositoryBeanName: String,
783794
val repositoryClassId: ClassId,
784795
val entityClassId: ClassId,
785796
)
786797

787-
class UtSpringMockMvcResultActionsModel(
788-
id: Int?,
789-
origin: UtCompositeModel?,
798+
data class UtSpringMockMvcResultActionsModel(
799+
override val id: Int?,
800+
override val origin: UtCompositeModel?,
790801
val status: Int,
791802
val errorMessage: String?,
792803
val contentAsString: String,

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

Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package org.utbot.framework.plugin.api.util
33
import org.utbot.common.tryLoadClass
44
import org.utbot.framework.plugin.api.ClassId
55
import org.utbot.framework.plugin.api.MethodId
6-
import org.utbot.framework.plugin.api.SpringRepositoryId
76
import org.utbot.framework.plugin.api.UtArrayModel
87
import org.utbot.framework.plugin.api.UtAssembleModel
98
import org.utbot.framework.plugin.api.UtExecutableCallModel
@@ -16,7 +15,7 @@ object SpringModelUtils {
1615
val autowiredClassId = ClassId("org.springframework.beans.factory.annotation.Autowired")
1716

1817
val applicationContextClassId = ClassId("org.springframework.context.ApplicationContext")
19-
val crudRepositoryClassId = ClassId("org.springframework.data.repository.CrudRepository")
18+
val repositoryClassId = ClassId("org.springframework.data.repository.Repository")
2019

2120
val springBootTestClassId = ClassId("org.springframework.boot.test.context.SpringBootTest")
2221

@@ -25,6 +24,7 @@ object SpringModelUtils {
2524
val transactionalClassId = ClassId("org.springframework.transaction.annotation.Transactional")
2625
val autoConfigureTestDbClassId = ClassId("org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase")
2726
val autoConfigureMockMvcClassId = ClassId("org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc")
27+
val withMockUserClassId = ClassId("org.springframework.security.test.context.support.WithMockUser")
2828

2929
val runWithClassId = ClassId("org.junit.runner.RunWith")
3030
val springRunnerClassId = ClassId("org.springframework.test.context.junit4.SpringRunner")
@@ -39,13 +39,52 @@ object SpringModelUtils {
3939
val activeProfilesClassId = ClassId("org.springframework.test.context.ActiveProfiles")
4040
val contextConfigurationClassId = ClassId("org.springframework.test.context.ContextConfiguration")
4141

42+
private fun getClassIdFromEachAvailablePackage(
43+
packages: List<String>,
44+
classNameFromPackage: String
45+
): List<ClassId> = packages.map { ClassId("$it.$classNameFromPackage") }
46+
.filter { utContext.classLoader.tryLoadClass(it.name) != null }
4247

4348
// most likely only one persistent library is on the classpath, but we need to be able to work with either of them
4449
private val persistentLibraries = listOf("javax.persistence", "jakarta.persistence")
45-
private fun persistentClassIds(simpleName: String) = persistentLibraries.map { ClassId("$it.$simpleName") }
50+
private fun persistentClassIds(simpleName: String) = getClassIdFromEachAvailablePackage(persistentLibraries, simpleName)
51+
52+
val entityClassIds get() = persistentClassIds("Entity")
53+
val generatedValueClassIds get() = persistentClassIds("GeneratedValue")
54+
val idClassIds get() = persistentClassIds("Id")
55+
val persistenceContextClassIds get() = persistentClassIds("PersistenceContext")
56+
val entityManagerClassIds get() = persistentClassIds("EntityManager")
57+
58+
val persistMethodIdOrNull: MethodId?
59+
get() {
60+
return MethodId(
61+
classId = entityManagerClassIds.firstOrNull() ?: return null,
62+
name = "persist",
63+
returnType = voidClassId,
64+
parameters = listOf(objectClassId),
65+
bypassesSandbox = true // TODO may be we can use some alternative sandbox that has more permissions
66+
)
67+
}
68+
69+
val detachMethodIdOrNull: MethodId?
70+
get() {
71+
return MethodId(
72+
classId = entityManagerClassIds.firstOrNull() ?: return null,
73+
name = "detach",
74+
returnType = voidClassId,
75+
parameters = listOf(objectClassId),
76+
bypassesSandbox = true // TODO may be we can use some alternative sandbox that has more permissions
77+
)
78+
}
79+
80+
private val validationLibraries = listOf("jakarta.validation.constraints")
81+
private fun validationClassIds(simpleName: String) = getClassIdFromEachAvailablePackage(validationLibraries, simpleName)
82+
.filter { utContext.classLoader.tryLoadClass(it.name) != null }
83+
84+
val notEmptyClassIds get() = validationClassIds("NotEmpty")
85+
val notBlankClassIds get() = validationClassIds("NotBlank")
86+
val emailClassIds get() = validationClassIds("Email")
4687

47-
val entityClassIds = persistentClassIds("Entity")
48-
val generatedValueClassIds = persistentClassIds("GeneratedValue")
4988

5089
private val getBeanMethodId = MethodId(
5190
classId = applicationContextClassId,
@@ -55,15 +94,6 @@ object SpringModelUtils {
5594
bypassesSandbox = true // TODO may be we can use some alternative sandbox that has more permissions
5695
)
5796

58-
private val saveMethodId = MethodId(
59-
classId = crudRepositoryClassId,
60-
name = "save",
61-
returnType = Any::class.id,
62-
parameters = listOf(Any::class.id),
63-
bypassesSandbox = true // TODO may be we can use some alternative sandbox that has more permissions
64-
)
65-
66-
6797
fun createBeanModel(beanName: String, id: Int, classId: ClassId) = UtAssembleModel(
6898
id = id,
6999
classId = classId,
@@ -76,16 +106,6 @@ object SpringModelUtils {
76106
modificationsChainProvider = { mutableListOf() }
77107
)
78108

79-
fun createSaveCallModel(repositoryId: SpringRepositoryId, id: Int, entityModel: UtModel) = UtExecutableCallModel(
80-
instance = createBeanModel(
81-
beanName = repositoryId.repositoryBeanName,
82-
id = id,
83-
classId = repositoryId.repositoryClassId,
84-
),
85-
executable = saveMethodId,
86-
params = listOf(entityModel)
87-
)
88-
89109
fun UtModel.isAutowiredFromContext(): Boolean =
90110
this is UtAssembleModel && this.instantiationCall.instance is UtSpringContextModel
91111

@@ -291,7 +311,7 @@ object SpringModelUtils {
291311
)
292312
)
293313

294-
// TODO support @RequestParam, @RequestHeader, @CookieValue, @RequestAttribute
314+
// TODO #2462 (support @RequestParam, @RequestHeader, @CookieValue, @RequestAttribute, ...)
295315
return addContentToRequestBuilderModel(methodId, arguments, requestBuilderModel, idGenerator)
296316
}
297317

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,5 @@ class SpringSpecificInformation(
3535
val thisInstanceDependentMocks: TypedModelWrappers,
3636
val thisInstanceDependentSpies: TypedModelWrappers,
3737
val autowiredFromContextModels: TypedModelWrappers,
38+
val entityManagerModels: TypedModelWrappers,
3839
)

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

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import org.utbot.framework.plugin.api.UtLambdaModel
1717
import org.utbot.framework.plugin.api.UtModel
1818
import org.utbot.framework.plugin.api.UtNullModel
1919
import org.utbot.framework.plugin.api.UtPrimitiveModel
20+
import org.utbot.framework.plugin.api.UtSpringEntityManagerModel
2021
import org.utbot.framework.plugin.api.UtVoidModel
2122
import org.utbot.framework.plugin.api.isMockModel
2223
import org.utbot.framework.plugin.api.UtStatementCallModel
@@ -55,13 +56,15 @@ class SpringTestClassModelBuilder(val context: CgContext) :
5556
.filterNotNull()
5657
.forEach { model ->
5758
thisInstanceModels += model.wrap()
58-
thisInstancesDependentModels += collectDependentModels(model)
59-
59+
thisInstancesDependentModels += collectImmediateDependentModels(
60+
model,
61+
skipModificationChains = true
62+
)
6063
}
6164

6265
(execution.stateBefore.parameters + execution.stateBefore.thisInstance)
6366
.filterNotNull()
64-
.forEach { model -> stateBeforeDependentModels += collectAutowiredModels(model) }
67+
.forEach { model -> stateBeforeDependentModels += collectRecursively(model) }
6568
}
6669
}
6770
}
@@ -82,15 +85,19 @@ class SpringTestClassModelBuilder(val context: CgContext) :
8285
val autowiredFromContextModels =
8386
stateBeforeDependentModels.filterTo(HashSet()) { it.model.isAutowiredFromContext() }
8487

88+
val entityManagerModels =
89+
stateBeforeDependentModels.filterTo(HashSet()) { it.model is UtSpringEntityManagerModel }
90+
8591
return SpringSpecificInformation(
8692
thisInstanceModels.groupByClassId(),
8793
dependentMockModels.groupByClassId(),
8894
dependentSpyModels.groupByClassId(),
8995
autowiredFromContextModels.groupByClassId(),
96+
entityManagerModels.groupByClassId(),
9097
)
9198
}
9299

93-
private fun collectAutowiredModels(model: UtModel): Set<UtModelWrapper> {
100+
private fun collectRecursively(model: UtModel): Set<UtModelWrapper> {
94101
val allDependentModels = mutableSetOf<UtModelWrapper>()
95102

96103
collectRecursively(model, allDependentModels)
@@ -102,10 +109,12 @@ class SpringTestClassModelBuilder(val context: CgContext) :
102109
if(!allDependentModels.add(model.wrap())){
103110
return
104111
}
105-
collectDependentModels(model).forEach { collectRecursively(it.model, allDependentModels) }
112+
collectImmediateDependentModels(model, skipModificationChains = false).forEach {
113+
collectRecursively(it.model, allDependentModels)
114+
}
106115
}
107116

108-
private fun collectDependentModels(model: UtModel): Set<UtModelWrapper> {
117+
private fun collectImmediateDependentModels(model: UtModel, skipModificationChains: Boolean): Set<UtModelWrapper> {
109118
val dependentModels = mutableSetOf<UtModelWrapper>()
110119

111120
when (model) {
@@ -134,7 +143,7 @@ class SpringTestClassModelBuilder(val context: CgContext) :
134143
model.instantiationCall.instance?.let { dependentModels.add(it.wrap()) }
135144
model.instantiationCall.params.forEach { dependentModels.add(it.wrap()) }
136145

137-
if(model.isAutowiredFromContext()) {
146+
if(!skipModificationChains) {
138147
model.modificationsChain.forEach { stmt ->
139148
stmt.instance?.let { dependentModels.add(it.wrap()) }
140149
when (stmt) {

0 commit comments

Comments
 (0)