Skip to content

Commit 226eaa0

Browse files
committed
Support creation and persistence of valid entities
1 parent f28ce77 commit 226eaa0

File tree

25 files changed

+658
-140
lines changed

25 files changed

+658
-140
lines changed

gradle.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ 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
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: 43 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

@@ -39,13 +38,51 @@ object SpringModelUtils {
3938
val activeProfilesClassId = ClassId("org.springframework.test.context.ActiveProfiles")
4039
val contextConfigurationClassId = ClassId("org.springframework.test.context.ContextConfiguration")
4140

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

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

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

5087
private val getBeanMethodId = MethodId(
5188
classId = applicationContextClassId,
@@ -55,15 +92,6 @@ object SpringModelUtils {
5592
bypassesSandbox = true // TODO may be we can use some alternative sandbox that has more permissions
5693
)
5794

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-
6795
fun createBeanModel(beanName: String, id: Int, classId: ClassId) = UtAssembleModel(
6896
id = id,
6997
classId = classId,
@@ -76,16 +104,6 @@ object SpringModelUtils {
76104
modificationsChainProvider = { mutableListOf() }
77105
)
78106

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-
89107
fun UtModel.isAutowiredFromContext(): Boolean =
90108
this is UtAssembleModel && this.instantiationCall.instance is UtSpringContextModel
91109

@@ -291,7 +309,7 @@ object SpringModelUtils {
291309
)
292310
)
293311

294-
// TODO support @RequestParam, @RequestHeader, @CookieValue, @RequestAttribute
312+
// TODO #2462 (support @RequestParam, @RequestHeader, @CookieValue, @RequestAttribute, ...)
295313
return addContentToRequestBuilderModel(methodId, arguments, requestBuilderModel, idGenerator)
296314
}
297315

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: 5 additions & 0 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.util.SpringModelUtils.isAutowiredFromContext
@@ -80,11 +81,15 @@ class SpringTestClassModelBuilder(val context: CgContext) :
8081
val autowiredFromContextModels =
8182
stateBeforeDependentModels.filterTo(HashSet()) { it.model.isAutowiredFromContext() }
8283

84+
val entityManagerModels =
85+
stateBeforeDependentModels.filterTo(HashSet()) { it.model is UtSpringEntityManagerModel }
86+
8387
return SpringSpecificInformation(
8488
thisInstanceModels.groupByClassId(),
8589
dependentMockModels.groupByClassId(),
8690
dependentSpyModels.groupByClassId(),
8791
autowiredFromContextModels.groupByClassId(),
92+
entityManagerModels.groupByClassId(),
8893
)
8994
}
9095

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgClassFieldManager.kt

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,17 @@ import org.utbot.framework.plugin.api.UtAssembleModel
1515
import org.utbot.framework.plugin.api.UtCompositeModel
1616
import org.utbot.framework.plugin.api.UtModel
1717
import org.utbot.framework.plugin.api.UtModelWithCompositeOrigin
18+
import org.utbot.framework.plugin.api.UtSpringEntityManagerModel
1819
import org.utbot.framework.plugin.api.isMockModel
1920
import org.utbot.framework.plugin.api.canBeSpied
2021
import org.utbot.framework.plugin.api.util.SpringModelUtils.autowiredClassId
2122
import org.utbot.framework.plugin.api.util.SpringModelUtils.getBeanNameOrNull
2223
import org.utbot.framework.plugin.api.util.SpringModelUtils.isAutowiredFromContext
24+
import org.utbot.framework.plugin.api.util.SpringModelUtils.persistenceContextClassIds
2325
import org.utbot.framework.plugin.api.util.allDeclaredFieldIds
2426
import org.utbot.framework.plugin.api.util.isSubtypeOf
2527

26-
sealed interface CgClassFieldManager : CgContextOwner {
28+
interface CgClassFieldManager : CgContextOwner {
2729

2830
val annotationType: ClassId
2931

@@ -151,18 +153,43 @@ class CgAutowiredFieldsManager(context: CgContext) : CgAbstractClassFieldManager
151153
override fun fieldWithAnnotationIsRequired(classId: ClassId): Boolean = true
152154
}
153155

156+
class CgPersistenceContextFieldsManager private constructor(
157+
context: CgContext,
158+
override val annotationType: ClassId,
159+
) : CgAbstractClassFieldManager(context) {
160+
companion object {
161+
fun createIfPossible(context: CgContext): CgPersistenceContextFieldsManager? = persistenceContextClassIds.firstOrNull()
162+
?.let { persistenceContextClassId -> CgPersistenceContextFieldsManager(context, persistenceContextClassId) }
163+
}
164+
165+
override fun constructFieldsForVariable(model: UtModel, modelVariable: CgValue): CgValue {
166+
return when(model) {
167+
is UtSpringEntityManagerModel -> modelVariable
168+
else -> error("Trying to use @PersistenceContext for model $model but it is not appropriate")
169+
}
170+
}
171+
172+
override fun fieldWithAnnotationIsRequired(classId: ClassId): Boolean = true
173+
}
174+
154175
class ClassFieldManagerFacade(context: CgContext) : CgContextOwner by context {
155176

156177
private val injectingMocksFieldsManager = CgInjectingMocksFieldsManager(context)
157178
private val mockedFieldsManager = CgMockedFieldsManager(context)
158179
private val spiedFieldsManager = CgSpiedFieldsManager(context)
159180
private val autowiredFieldsManager = CgAutowiredFieldsManager(context)
181+
private val persistenceContextFieldsManager = CgPersistenceContextFieldsManager.createIfPossible(context)
160182

161183
fun constructVariableForField(
162184
model: UtModel,
163185
): CgValue? {
164-
val annotationManagers =
165-
listOf(injectingMocksFieldsManager, mockedFieldsManager, spiedFieldsManager, autowiredFieldsManager)
186+
val annotationManagers = listOfNotNull(
187+
injectingMocksFieldsManager,
188+
mockedFieldsManager,
189+
spiedFieldsManager,
190+
autowiredFieldsManager,
191+
persistenceContextFieldsManager,
192+
)
166193

167194
annotationManagers.forEach { manager ->
168195
val annotatedModelGroups = manager.variableConstructor.annotatedModelGroups

0 commit comments

Comments
 (0)