Skip to content

Commit 81e364d

Browse files
IlyaMuravjovEgorkaKulikov
authored andcommitted
Add a prototype of Spring integration tests
1 parent dd1506e commit 81e364d

File tree

62 files changed

+1149
-328
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

+1149
-328
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
@@ -236,6 +236,12 @@ open class EnvironmentModels(
236236
operator fun component1(): UtModel? = thisInstance
237237
operator fun component2(): List<UtModel> = parameters
238238
operator fun component3(): Map<FieldId, UtModel> = statics
239+
240+
fun copy(
241+
thisInstance: UtModel? = this.thisInstance,
242+
parameters: List<UtModel> = this.parameters,
243+
statics: Map<FieldId, UtModel> = this.statics
244+
) = EnvironmentModels(thisInstance, parameters, statics)
239245
}
240246

241247
/**
@@ -632,6 +638,46 @@ class UtLambdaModel(
632638
}
633639
}
634640

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

1302+
sealed class TypeReplacementApproach {
1303+
/**
1304+
* Do not replace interfaces and abstract classes with concrete implementors.
1305+
* Use mocking instead of it.
1306+
*/
1307+
object DoNotReplace : TypeReplacementApproach()
1308+
1309+
/**
1310+
* Try to replace interfaces and abstract classes with concrete implementors
1311+
* obtained from bean definitions.
1312+
* If it is impossible, use mocking.
1313+
*
1314+
* Currently used in Spring applications only.
1315+
*/
1316+
class ReplaceIfPossible(val config: String) : TypeReplacementApproach()
1317+
}
1318+
12561319
/**
12571320
* Data we get from Spring application context
12581321
* to manage engine and code generator behaviour.
12591322
*
12601323
* @param beanDefinitions describes bean definitions (bean name, type, some optional additional data)
12611324
* @param shouldUseImplementors describes it we want to replace interfaces with injected types or not
12621325
*/
1326+
// TODO move this class to utbot-framework so we can use it as abstract factory
1327+
// to get rid of numerous `when`s and polymorphically create things like:
1328+
// - Instrumentation<UtConcreteExecutionResult>
1329+
// - FuzzedType (to get rid of thisInstanceFuzzedTypeWrapper)
1330+
// - JavaValueProvider
1331+
// - CgVariableConstructor
1332+
// - CodeGeneratorResult (generateForSpringClass)
1333+
// Right now this refactoring is blocked because some interfaces need to get extracted and moved to utbot-framework-api
1334+
// As an alternative we can just move ApplicationContext itself to utbot-framework
12631335
class SpringApplicationContext(
12641336
mockInstalled: Boolean,
12651337
staticsMockingIsConfigured: Boolean,
1266-
private val beanDefinitions: List<BeanDefinitionData> = emptyList(),
1338+
val beanDefinitions: List<BeanDefinitionData> = emptyList(),
12671339
private val shouldUseImplementors: Boolean,
1340+
val typeReplacementApproach: TypeReplacementApproach,
1341+
val testType: SpringTestType
12681342
): ApplicationContext(mockInstalled, staticsMockingIsConfigured) {
12691343

12701344
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.
@@ -593,7 +619,7 @@ private fun ResolvedModels.constructStateForMethod(methodUnderTest: ExecutableId
593619
return EnvironmentModels(thisInstanceBefore, paramsBefore, statics)
594620
}
595621

596-
private suspend fun ConcreteExecutor<UtConcreteExecutionResult, UtExecutionInstrumentation>.executeConcretely(
622+
private suspend fun ConcreteExecutor<UtConcreteExecutionResult, Instrumentation<UtConcreteExecutionResult>>.executeConcretely(
597623
methodUnderTest: ExecutableId,
598624
stateBefore: EnvironmentModels,
599625
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
@@ -328,6 +328,8 @@ open class CgMethodConstructor(val context: CgContext) : CgContextOwner by conte
328328
when (methodType) {
329329
SUCCESSFUL -> error("Unexpected successful without exception method type for execution with exception $expectedException")
330330
PASSED_EXCEPTION -> {
331+
// TODO consider rendering message in a comment
332+
// expectedException.message?.let { +comment(it) }
331333
testFrameworkManager.expectException(expectedException::class.id) {
332334
methodInvocationBlock()
333335
}

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)