1
1
package org.utbot.framework.context.spring
2
2
3
- import mu.KotlinLogging
4
- import org.utbot.common.isAbstract
5
- import org.utbot.common.isStatic
6
3
import org.utbot.framework.context.ApplicationContext
7
4
import org.utbot.framework.plugin.api.BeanDefinitionData
8
5
import org.utbot.framework.plugin.api.ClassId
9
6
import org.utbot.framework.plugin.api.SpringCodeGenerationContext
10
7
import org.utbot.framework.plugin.api.SpringContextLoadingResult
11
- import org.utbot.framework.plugin.api.SpringSettings
12
- import org.utbot.framework.plugin.api.SpringTestType
13
- import org.utbot.framework.plugin.api.TypeReplacementMode
14
- import org.utbot.framework.plugin.api.UtError
15
- import org.utbot.framework.plugin.api.classId
16
- import org.utbot.framework.plugin.api.id
17
- import org.utbot.framework.plugin.api.isAbstractType
18
- import org.utbot.framework.plugin.api.util.allDeclaredFieldIds
19
- import org.utbot.framework.plugin.api.util.allSuperTypes
20
- import org.utbot.framework.plugin.api.util.fieldId
21
- import org.utbot.framework.plugin.api.util.id
22
- import org.utbot.framework.plugin.api.util.isSubtypeOf
23
- import org.utbot.framework.plugin.api.util.jClass
24
- import org.utbot.framework.plugin.api.util.utContext
25
- import soot.RefType
26
- import soot.SootField
27
8
28
9
/* *
29
10
* Data we get from Spring application context
30
11
* to manage engine and code generator behaviour.
31
- *
32
- * @param beanDefinitions describes bean definitions (bean name, type, some optional additional data)
33
- * @param shouldUseImplementors describes it we want to replace interfaces with injected types or not
34
12
*/
35
- // TODO move this class to utbot-framework so we can use it as abstract factory
36
- // to get rid of numerous `when`s and polymorphically create things like:
37
- // - Instrumentation<UtConcreteExecutionResult>
38
- // - FuzzedType (to get rid of thisInstanceFuzzedTypeWrapper)
39
- // - JavaValueProvider
40
- // - CgVariableConstructor
41
- // - CodeGeneratorResult (generateForSpringClass)
42
- // Right now this refactoring is blocked because some interfaces need to get extracted and moved to utbot-framework-api
43
- // As an alternative we can just move ApplicationContext itself to utbot-framework
44
- class SpringApplicationContext (
45
- private val delegateContext : ApplicationContext ,
46
- val beanDefinitions : List <BeanDefinitionData > = emptyList(),
47
- private val shouldUseImplementors : Boolean ,
48
- override val springTestType : SpringTestType ,
49
- override val springSettings : SpringSettings ,
50
- ): ApplicationContext by delegateContext, SpringCodeGenerationContext {
51
-
52
- override var springContextLoadingResult: SpringContextLoadingResult ? = null
53
-
54
- companion object {
55
- private val logger = KotlinLogging .logger {}
56
- }
57
-
58
- private var areInjectedClassesInitialized : Boolean = false
59
- private var areAllInjectedTypesInitialized: Boolean = false
60
-
61
- // Classes representing concrete types that are actually used in Spring application
62
- private val springInjectedClasses: Set <ClassId >
63
- get() {
64
- if (! areInjectedClassesInitialized) {
65
- for (beanTypeName in beanDefinitions.map { it.beanTypeName }) {
66
- try {
67
- val beanClass = utContext.classLoader.loadClass(beanTypeName)
68
- if (! beanClass.isAbstract && ! beanClass.isInterface &&
69
- ! beanClass.isLocalClass && (! beanClass.isMemberClass || beanClass.isStatic)) {
70
- springInjectedClassesStorage + = beanClass.id
71
- }
72
- } catch (e: Throwable ) {
73
- // For some Spring beans (e.g. with anonymous classes)
74
- // it is possible to have problems with classes loading.
75
- when (e) {
76
- is ClassNotFoundException , is NoClassDefFoundError , is IllegalAccessError ->
77
- logger.warn { " Failed to load bean class for $beanTypeName (${e.message} )" }
78
-
79
- else -> throw e
80
- }
81
- }
82
- }
83
-
84
- // This is done to be sure that this storage is not empty after the first class loading iteration.
85
- // So, even if all loaded classes were filtered out, we will not try to load them again.
86
- areInjectedClassesInitialized = true
87
- }
88
-
89
- return springInjectedClassesStorage
90
- }
91
-
92
- private val allInjectedTypes: Set <ClassId >
93
- get() {
94
- if (! areAllInjectedTypesInitialized) {
95
- allInjectedTypesStorage = springInjectedClasses.flatMap { it.allSuperTypes() }.toSet()
96
- areAllInjectedTypesInitialized = true
97
- }
98
-
99
- return allInjectedTypesStorage
100
- }
101
-
102
- // imitates `by lazy` (we can't use actual `by lazy` because communication via RD breaks it)
103
- private var allInjectedTypesStorage: Set <ClassId > = emptySet()
104
-
105
- // This is a service field to model the lazy behavior of [springInjectedClasses].
106
- // Do not call it outside the getter.
107
- //
108
- // Actually, we should just call [springInjectedClasses] with `by lazy`, but we had problems
109
- // with a strange `kotlin.UNINITIALIZED_VALUE` in `speculativelyCannotProduceNullPointerException` method call.
110
- private val springInjectedClassesStorage = mutableSetOf<ClassId >()
111
-
112
- override val typeReplacementMode: TypeReplacementMode =
113
- if (shouldUseImplementors) TypeReplacementMode .KnownImplementor else TypeReplacementMode .NoImplementors
114
-
13
+ // TODO #2358
14
+ interface SpringApplicationContext : ApplicationContext , SpringCodeGenerationContext {
115
15
/* *
116
- * Replaces an interface type with its implementor type
117
- * if there is the unique implementor in bean definitions.
16
+ * Describes bean definitions (bean name, type, some optional additional data)
118
17
*/
119
- override fun replaceTypeIfNeeded (type : RefType ): ClassId ? =
120
- if (type.isAbstractType) {
121
- springInjectedClasses.singleOrNull { it.isSubtypeOf(type.id) }
122
- } else {
123
- null
124
- }
125
-
126
- /* *
127
- * In Spring applications we can mark as speculatively not null
128
- * fields if they are mocked and injecting into class under test so on.
129
- *
130
- * Fields are not mocked if their actual type is obtained from [springInjectedClasses].
131
- *
132
- */
133
- override fun speculativelyCannotProduceNullPointerException (
134
- field : SootField ,
135
- classUnderTest : ClassId ,
136
- ): Boolean = field.fieldId in classUnderTest.allDeclaredFieldIds && field.type.classId !in allInjectedTypes
137
-
138
- override fun preventsFurtherTestGeneration (): Boolean =
139
- delegateContext.preventsFurtherTestGeneration() || springContextLoadingResult?.contextLoaded == false
140
-
141
- override fun getErrors (): List <UtError > =
142
- springContextLoadingResult?.exceptions?.map { exception ->
143
- UtError (
144
- " Failed to load Spring application context" ,
145
- exception
146
- )
147
- }.orEmpty() + delegateContext.getErrors()
18
+ val beanDefinitions: List <BeanDefinitionData >
19
+ val springInjectedClasses: Set <ClassId >
20
+ val allInjectedTypes: Set <ClassId >
148
21
149
- fun getBeansAssignableTo (classId : ClassId ): List <BeanDefinitionData > = beanDefinitions.filter { beanDef ->
150
- // some bean classes may fail to load
151
- runCatching {
152
- val beanClass = ClassId (beanDef.beanTypeName).jClass
153
- classId.jClass.isAssignableFrom(beanClass)
154
- }.getOrElse { false }
155
- }
22
+ override var springContextLoadingResult: SpringContextLoadingResult ?
23
+ fun getBeansAssignableTo (classId : ClassId ): List <BeanDefinitionData >
156
24
}
0 commit comments