Skip to content

Commit fd85321

Browse files
Introduce Spring integration tests (#2205)
--------- Co-authored-by: Egor Kulikov <egor.k.kulikov@gmail.com>
1 parent f2c6c56 commit fd85321

File tree

62 files changed

+1143
-321
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+1143
-321
lines changed

settings.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,4 @@ if (goIde.split(",").contains(ideType)) {
7171
}
7272

7373
include("utbot-spring-analyzer")
74+
include("utbot-spring-commons")
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package org.utbot.common
2+
3+
import java.io.File
4+
import java.net.URL
5+
import java.nio.file.Files
6+
import java.nio.file.StandardCopyOption
7+
8+
object JarUtils {
9+
private const val UNKNOWN_MODIFICATION_TIME = 0L
10+
11+
fun extractJarFileFromResources(jarFileName: String, jarResourcePath: String, targetDirectoryName: String): File {
12+
val targetDirectory =
13+
Files.createDirectories(utBotTempDirectory.toFile().resolve(targetDirectoryName).toPath()).toFile()
14+
return targetDirectory.resolve(jarFileName).also { jarFile ->
15+
val resource = this::class.java.classLoader.getResource(jarResourcePath)
16+
?: error("Unable to find \"$jarResourcePath\" in resources, make sure it's on the classpath")
17+
updateJarIfRequired(jarFile, resource)
18+
}
19+
}
20+
21+
private fun updateJarIfRequired(jarFile: File, resource: URL) {
22+
val resourceConnection = resource.openConnection()
23+
resourceConnection.getInputStream().use { inputStream ->
24+
val lastResourceModification = resourceConnection.lastModified
25+
if (
26+
!jarFile.exists() ||
27+
jarFile.lastModified() == UNKNOWN_MODIFICATION_TIME ||
28+
lastResourceModification == UNKNOWN_MODIFICATION_TIME ||
29+
jarFile.lastModified() < lastResourceModification
30+
) {
31+
Files.copy(inputStream, jarFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
32+
}
33+
}
34+
}
35+
}

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

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,12 @@ open class EnvironmentModels(
241241
operator fun component1(): UtModel? = thisInstance
242242
operator fun component2(): List<UtModel> = parameters
243243
operator fun component3(): Map<FieldId, UtModel> = statics
244+
245+
fun copy(
246+
thisInstance: UtModel? = this.thisInstance,
247+
parameters: List<UtModel> = this.parameters,
248+
statics: Map<FieldId, UtModel> = this.statics
249+
) = EnvironmentModels(thisInstance, parameters, statics)
244250
}
245251

246252
/**
@@ -637,6 +643,46 @@ class UtLambdaModel(
637643
}
638644
}
639645

646+
abstract class UtAutowiredBaseModel(
647+
override val id: Int?,
648+
override val classId: ClassId,
649+
val origin: UtModel,
650+
modelName: String
651+
) : UtReferenceModel(
652+
id, classId, modelName
653+
)
654+
655+
class UtAutowiredStateBeforeModel(
656+
id: Int?,
657+
classId: ClassId,
658+
origin: UtModel,
659+
val beanName: String,
660+
val repositoriesContent: List<RepositoryContentModel>,
661+
) : UtAutowiredBaseModel(
662+
id, classId, origin, modelName = "@Autowired $beanName#$id"
663+
)
664+
665+
data class RepositoryContentModel(
666+
val repositoryBeanName: String,
667+
val entityModels: List<UtModel>,
668+
)
669+
670+
class UtAutowiredStateAfterModel(
671+
id: Int?,
672+
classId: ClassId,
673+
origin: UtModel,
674+
val repositoryInteractions: List<RepositoryInteractionModel>,
675+
) : UtAutowiredBaseModel(
676+
id, classId, origin, modelName = "@Autowired ${classId.name}#$id"
677+
)
678+
679+
data class RepositoryInteractionModel(
680+
val beanName: String,
681+
val executableId: ExecutableId,
682+
val args: List<UtModel>,
683+
val result: UtExecutionResult
684+
)
685+
640686
/**
641687
* Model for a step to obtain [UtAssembleModel].
642688
*/
@@ -1259,18 +1305,46 @@ open class ApplicationContext(
12591305
): Boolean = field.isFinal || !field.isPublic
12601306
}
12611307

1308+
sealed class TypeReplacementApproach {
1309+
/**
1310+
* Do not replace interfaces and abstract classes with concrete implementors.
1311+
* Use mocking instead of it.
1312+
*/
1313+
object DoNotReplace : TypeReplacementApproach()
1314+
1315+
/**
1316+
* Try to replace interfaces and abstract classes with concrete implementors
1317+
* obtained from bean definitions.
1318+
* If it is impossible, use mocking.
1319+
*
1320+
* Currently used in Spring applications only.
1321+
*/
1322+
class ReplaceIfPossible(val config: String) : TypeReplacementApproach()
1323+
}
1324+
12621325
/**
12631326
* Data we get from Spring application context
12641327
* to manage engine and code generator behaviour.
12651328
*
12661329
* @param beanDefinitions describes bean definitions (bean name, type, some optional additional data)
12671330
* @param shouldUseImplementors describes it we want to replace interfaces with injected types or not
12681331
*/
1332+
// TODO move this class to utbot-framework so we can use it as abstract factory
1333+
// to get rid of numerous `when`s and polymorphically create things like:
1334+
// - Instrumentation<UtConcreteExecutionResult>
1335+
// - FuzzedType (to get rid of thisInstanceFuzzedTypeWrapper)
1336+
// - JavaValueProvider
1337+
// - CgVariableConstructor
1338+
// - CodeGeneratorResult (generateForSpringClass)
1339+
// Right now this refactoring is blocked because some interfaces need to get extracted and moved to utbot-framework-api
1340+
// As an alternative we can just move ApplicationContext itself to utbot-framework
12691341
class SpringApplicationContext(
12701342
mockInstalled: Boolean,
12711343
staticsMockingIsConfigured: Boolean,
1272-
private val beanDefinitions: List<BeanDefinitionData> = emptyList(),
1344+
val beanDefinitions: List<BeanDefinitionData> = emptyList(),
12731345
private val shouldUseImplementors: Boolean,
1346+
val typeReplacementApproach: TypeReplacementApproach,
1347+
val testType: SpringTestsType
12741348
): ApplicationContext(mockInstalled, staticsMockingIsConfigured) {
12751349

12761350
companion object {

utbot-framework/build.gradle

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
configurations {
22
fetchSpringAnalyzerJar
3+
fetchInstrumentationJar
34
}
45

56
dependencies {
@@ -41,10 +42,14 @@ dependencies {
4142
implementation project(':utbot-spring-analyzer')
4243

4344
fetchSpringAnalyzerJar project(path: ':utbot-spring-analyzer', configuration: 'springAnalyzerJar')
45+
fetchInstrumentationJar project(path: ':utbot-instrumentation', configuration: 'instrumentationArchive')
4446
}
4547

4648
processResources {
4749
from(configurations.fetchSpringAnalyzerJar) {
4850
into "lib"
4951
}
52+
from(configurations.fetchInstrumentationJar) {
53+
into "lib"
54+
}
5055
}

utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,14 @@ import org.utbot.framework.util.graph
4040
import org.utbot.framework.util.sootMethod
4141
import org.utbot.fuzzer.*
4242
import org.utbot.fuzzing.*
43+
import org.utbot.fuzzing.providers.AutowiredValueProvider
44+
import org.utbot.fuzzing.type.factories.SimpleFuzzedTypeFactory
45+
import org.utbot.fuzzing.type.factories.SpringFuzzedTypeFactory
4346
import org.utbot.fuzzing.utils.Trie
4447
import org.utbot.instrumentation.ConcreteExecutor
48+
import org.utbot.instrumentation.instrumentation.Instrumentation
4549
import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionData
4650
import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult
47-
import org.utbot.instrumentation.instrumentation.execution.UtExecutionInstrumentation
4851
import soot.jimple.Stmt
4952
import soot.tagkit.ParamNamesTag
5053
import java.lang.reflect.Method
@@ -105,7 +108,8 @@ class UtBotSymbolicEngine(
105108
dependencyPaths: String,
106109
val mockStrategy: MockStrategy = NO_MOCKS,
107110
chosenClassesToMockAlways: Set<ClassId>,
108-
applicationContext: ApplicationContext,
111+
val applicationContext: ApplicationContext,
112+
executionInstrumentation: Instrumentation<UtConcreteExecutionResult>,
109113
private val solverTimeoutInMillis: Int = checkSolverTimeoutMillis
110114
) : UtContextInitializer() {
111115
private val graph = methodUnderTest.sootMethod.jimpleBody().apply {
@@ -154,7 +158,7 @@ class UtBotSymbolicEngine(
154158

155159
private val concreteExecutor =
156160
ConcreteExecutor(
157-
UtExecutionInstrumentation,
161+
executionInstrumentation,
158162
classpath,
159163
).apply { this.classLoader = utContext.classLoader }
160164

@@ -355,7 +359,29 @@ class UtBotSymbolicEngine(
355359
methodUnderTest,
356360
collectConstantsForFuzzer(graph),
357361
names,
358-
listOf(transform(ValueProvider.of(defaultValueProviders(defaultIdGenerator))))
362+
listOf(transform(ValueProvider.of(defaultValueProviders(defaultIdGenerator)))),
363+
fuzzedTypeFactory = when (applicationContext) {
364+
is SpringApplicationContext -> when (applicationContext.typeReplacementApproach) {
365+
is TypeReplacementApproach.ReplaceIfPossible -> SpringFuzzedTypeFactory(
366+
autowiredValueProvider = AutowiredValueProvider(
367+
defaultIdGenerator,
368+
autowiredModelOriginCreator = { beanName ->
369+
runBlocking {
370+
logger.info { "Getting bean: $beanName" }
371+
concreteExecutor.withProcess { getBean(beanName) }
372+
}
373+
}
374+
),
375+
beanNamesFinder = { classId ->
376+
applicationContext.beanDefinitions
377+
.filter { it.beanTypeFqn == classId.name }
378+
.map { it.beanName }
379+
}
380+
)
381+
is TypeReplacementApproach.DoNotReplace -> SimpleFuzzedTypeFactory()
382+
}
383+
else -> SimpleFuzzedTypeFactory()
384+
},
359385
) { thisInstance, descr, values ->
360386
if (thisInstance?.model is UtNullModel) {
361387
// We should not try to run concretely any models with null-this.
@@ -595,7 +621,7 @@ private fun ResolvedModels.constructStateForMethod(methodUnderTest: ExecutableId
595621
return EnvironmentModels(thisInstanceBefore, paramsBefore, statics)
596622
}
597623

598-
private suspend fun ConcreteExecutor<UtConcreteExecutionResult, UtExecutionInstrumentation>.executeConcretely(
624+
private suspend fun ConcreteExecutor<UtConcreteExecutionResult, Instrumentation<UtConcreteExecutionResult>>.executeConcretely(
599625
methodUnderTest: ExecutableId,
600626
stateBefore: EnvironmentModels,
601627
instrumentation: List<UtInstrumentation>,

utbot-framework/src/main/kotlin/org/utbot/framework/assemble/AssembleModelGenerator.kt

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.utbot.framework.assemble
22

3+
import mu.KotlinLogging
34
import org.utbot.common.isPrivate
45
import org.utbot.common.isPublic
56
import org.utbot.engine.ResolvedExecution
@@ -55,6 +56,10 @@ import java.util.IdentityHashMap
5556
*/
5657
class AssembleModelGenerator(private val basePackageName: String) {
5758

59+
companion object {
60+
private val logger = KotlinLogging.logger {}
61+
}
62+
5863
//Instantiated models are stored to avoid cyclic references during reference graph analysis
5964
private val instantiatedModels: IdentityHashMap<UtModel, UtReferenceModel> =
6065
IdentityHashMap<UtModel, UtReferenceModel>()
@@ -175,8 +180,13 @@ class AssembleModelGenerator(private val basePackageName: String) {
175180
private fun assembleModel(utModel: UtModel): UtModel {
176181
val collectedCallChain = callChain.toMutableList()
177182

178-
// We cannot create an assemble model for an anonymous class instance
179-
if (utModel.classId.isAnonymous) {
183+
try {
184+
// We cannot create an assemble model for an anonymous class instance
185+
if (utModel.classId.isAnonymous) {
186+
return utModel
187+
}
188+
} catch (e: ClassNotFoundException) {
189+
// happens, for example, when `utModel.classId.name` is something like "jdk.proxy3.$Proxy144"
180190
return utModel
181191
}
182192

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

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -688,23 +688,6 @@ enum class ProjectType {
688688
JavaScript,
689689
}
690690

691-
sealed class TypeReplacementApproach {
692-
/**
693-
* Do not replace interfaces and abstract classes with concrete implementors.
694-
* Use mocking instead of it.
695-
*/
696-
object DoNotReplace : TypeReplacementApproach()
697-
698-
/**
699-
* Try to replace interfaces and abstract classes with concrete implementors
700-
* obtained from bean definitions.
701-
* If it is impossible, use mocking.
702-
*
703-
* Currently used in Spring applications only.
704-
*/
705-
class ReplaceIfPossible(val config: String) : TypeReplacementApproach()
706-
}
707-
708691
abstract class DependencyInjectionFramework(
709692
override val id: String,
710693
override val displayName: String,

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,8 @@ open class CgMethodConstructor(val context: CgContext) : CgContextOwner by conte
330330
when (methodType) {
331331
SUCCESSFUL -> error("Unexpected successful without exception method type for execution with exception $expectedException")
332332
PASSED_EXCEPTION -> {
333+
// TODO consider rendering message in a comment
334+
// expectedException.message?.let { +comment(it) }
333335
testFrameworkManager.expectException(expectedException::class.id) {
334336
methodInvocationBlock()
335337
}

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,38 @@ import org.utbot.framework.codegen.domain.context.CgContext
55
import org.utbot.framework.codegen.domain.models.CgValue
66
import org.utbot.framework.codegen.domain.models.CgVariable
77
import org.utbot.framework.plugin.api.UtAssembleModel
8+
import org.utbot.framework.plugin.api.UtAutowiredBaseModel
9+
import org.utbot.framework.plugin.api.UtAutowiredStateBeforeModel
810
import org.utbot.framework.plugin.api.UtCompositeModel
911
import org.utbot.framework.plugin.api.UtModel
1012
import org.utbot.framework.plugin.api.isMockModel
13+
import org.utbot.framework.plugin.api.util.jClass
1114

1215
class CgSpringVariableConstructor(context: CgContext) : CgVariableConstructor(context) {
1316
val injectedMocksModelsVariables: MutableSet<UtModelWrapper> = mutableSetOf()
1417
val mockedModelsVariables: MutableSet<UtModelWrapper> = mutableSetOf()
1518

1619
override fun getOrCreateVariable(model: UtModel, name: String?): CgValue {
20+
if (model is UtAutowiredBaseModel) {
21+
// TODO also, properly render corresponding @Autowired field(s), and make sure name isn't taken
22+
if (model is UtAutowiredStateBeforeModel) {
23+
comment("begin repository fill up")
24+
model.repositoriesContent.forEach { repositoryContent ->
25+
repositoryContent.entityModels.forEach { entity ->
26+
// TODO actually fill up repositories
27+
getOrCreateVariable(entity)
28+
emptyLine()
29+
}
30+
}
31+
comment("end repository fill up")
32+
}
33+
emptyLine()
34+
return CgVariable(
35+
name ?: model.classId.jClass.simpleName.let { it[0].lowercase() + it.drop(1) },
36+
model.classId
37+
)
38+
}
39+
1740
val alreadyCreatedInjectMocks = findCgValueByModel(model, injectedMocksModelsVariables)
1841
if (alreadyCreatedInjectMocks != null) {
1942
val fields: Collection<UtModel> = when (model) {

utbot-framework/src/main/kotlin/org/utbot/framework/fields/ExecutionStateAnalyzer.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import org.utbot.framework.plugin.api.ClassId
88
import org.utbot.framework.plugin.api.MissingState
99
import org.utbot.framework.plugin.api.UtArrayModel
1010
import org.utbot.framework.plugin.api.UtAssembleModel
11+
import org.utbot.framework.plugin.api.UtAutowiredBaseModel
1112
import org.utbot.framework.plugin.api.UtClassRefModel
1213
import org.utbot.framework.plugin.api.UtCompositeModel
1314
import org.utbot.framework.plugin.api.UtEnumConstantModel
@@ -237,6 +238,10 @@ private class FieldStateVisitor : UtModelVisitor<FieldData>() {
237238
recordFieldState(data, element)
238239
}
239240

241+
override fun visit(element: UtAutowiredBaseModel, data: FieldData) {
242+
element.origin.accept(this, data)
243+
}
244+
240245
private fun recordFieldState(data: FieldData, model: UtModel) {
241246
val fields = data.fields
242247
val path = data.path

0 commit comments

Comments
 (0)