Skip to content

Commit a77e321

Browse files
committed
Avoid hardcoding the names of tables and repositories
1 parent 8227e23 commit a77e321

15 files changed

Lines changed: 384 additions & 71 deletions

File tree

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

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,11 @@ import org.utbot.fuzzing.spring.SavedEntityProvider
4646
import org.utbot.fuzzing.spring.SpringBeanValueProvider
4747
import org.utbot.fuzzing.utils.Trie
4848
import org.utbot.instrumentation.ConcreteExecutor
49+
import org.utbot.instrumentation.getSpringRepositories
4950
import org.utbot.instrumentation.instrumentation.Instrumentation
5051
import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionData
5152
import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult
53+
import org.utbot.instrumentation.process.generated.GetSpringRepositoriesParams
5254
import org.utbot.taint.*
5355
import org.utbot.taint.model.TaintConfiguration
5456
import soot.jimple.Stmt
@@ -388,17 +390,11 @@ class UtBotSymbolicEngine(
388390
.letIf(applicationContext is SpringApplicationContext
389391
&& applicationContext.typeReplacementApproach is TypeReplacementApproach.ReplaceIfPossible
390392
) { provider ->
391-
// TODO #2274 properly detect relevant repositories (right now orderRepository is hardcoded)
392-
val relevantRepositories = listOf(
393-
SpringRepositoryId(
394-
repositoryBeanName = "orderRepository",
395-
repositoryClassId = ClassId("com.rest.order.repositories.OrderRepository"),
396-
entityClassId = ClassId("com.rest.order.models.Order")
397-
)
398-
)
399-
val generatedValueFieldIds = listOf(
400-
FieldId(ClassId("com.rest.order.models.Order"), "id")
401-
)
393+
val relevantRepositories = concreteExecutor.getSpringRepositories(methodUnderTest.classId)
394+
val generatedValueFieldIds = relevantRepositories.map {
395+
repository -> FieldId(repository.entityClassId, "id")
396+
}
397+
402398
logger.info { "Relevant repositories: $relevantRepositories" }
403399
// spring should try to generate bean values, but if it fails, then object value provider is used for it
404400
val springBeanValueProvider = SpringBeanValueProvider(

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ import org.utbot.framework.plugin.api.BuiltinClassId
55
internal val autowiredClassId = BuiltinClassId(
66
canonicalName = "org.springframework.beans.factory.annotation.Autowired",
77
simpleName = "Autowired",
8-
)
8+
)

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,11 @@ open class TestCaseGenerator(
7373
is TypeReplacementApproach.ReplaceIfPossible ->
7474
when (applicationContext.testType) {
7575
SpringTestsType.UNIT_TESTS -> UtExecutionInstrumentation
76-
SpringTestsType.INTEGRATION_TESTS -> SpringUtExecutionInstrumentation(UtExecutionInstrumentation, approach.config)
76+
SpringTestsType.INTEGRATION_TESTS -> SpringUtExecutionInstrumentation(
77+
UtExecutionInstrumentation,
78+
approach.config,
79+
applicationContext.beanDefinitions,
80+
)
7781
}
7882
is TypeReplacementApproach.DoNotReplace -> UtExecutionInstrumentation
7983
}

utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/ConcreteExecutor.kt

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
package org.utbot.instrumentation
22

3-
import com.jetbrains.rd.util.ILoggerFactory
4-
import com.jetbrains.rd.util.Logger
5-
import com.jetbrains.rd.util.Statics
6-
import com.jetbrains.rd.util.lifetime.Lifetime
73
import com.jetbrains.rd.util.lifetime.LifetimeDefinition
84
import com.jetbrains.rd.util.lifetime.isAlive
95
import com.jetbrains.rd.util.lifetime.throwIfNotAlive
@@ -15,24 +11,25 @@ import kotlin.reflect.KProperty
1511
import kotlin.reflect.jvm.javaConstructor
1612
import kotlin.reflect.jvm.javaGetter
1713
import kotlin.reflect.jvm.javaMethod
18-
import kotlin.streams.toList
1914
import kotlinx.coroutines.CancellationException
2015
import kotlinx.coroutines.runBlocking
2116
import kotlinx.coroutines.sync.Mutex
2217
import kotlinx.coroutines.sync.withLock
2318
import mu.KotlinLogging
2419
import org.utbot.framework.plugin.api.InstrumentedProcessDeathException
2520
import org.utbot.common.logException
21+
import org.utbot.framework.plugin.api.ClassId
2622
import org.utbot.framework.plugin.api.FieldId
23+
import org.utbot.framework.plugin.api.SpringRepositoryId
2724
import org.utbot.framework.plugin.api.util.UtContext
2825
import org.utbot.framework.plugin.api.util.signature
2926
import org.utbot.instrumentation.instrumentation.Instrumentation
3027
import org.utbot.instrumentation.process.generated.ComputeStaticFieldParams
28+
import org.utbot.instrumentation.process.generated.GetSpringRepositoriesParams
3129
import org.utbot.instrumentation.process.generated.InvokeMethodCommandParams
3230
import org.utbot.instrumentation.rd.InstrumentedProcess
3331
import org.utbot.instrumentation.util.InstrumentedProcessError
3432
import org.utbot.rd.generated.synchronizationModel
35-
import org.utbot.rd.loggers.UtRdKLoggerFactory
3633
import org.utbot.rd.loggers.overrideDefaultRdLoggerFactoryWithKLogger
3734

3835
private val logger = KotlinLogging.logger {}
@@ -285,6 +282,16 @@ fun ConcreteExecutor<*,*>.warmup() = runBlocking {
285282
}
286283
}
287284

285+
fun ConcreteExecutor<*,*>.getSpringRepositories(classId: ClassId): Set<SpringRepositoryId> = runBlocking {
286+
withProcess {
287+
val classId = kryoHelper.writeObject(classId)
288+
val params = GetSpringRepositoriesParams(classId)
289+
val result = instrumentedProcessModel.getSpringRepositories.startSuspending(lifetime, params)
290+
291+
kryoHelper.readObject(result.repositoryDescriptions)
292+
}
293+
}
294+
288295
/**
289296
* Extension function for the [ConcreteExecutor], which allows to collect static field value of [fieldId].
290297
*/

utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/SpringUtExecutionInstrumentation.kt

Lines changed: 57 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,31 @@ package org.utbot.instrumentation.instrumentation.execution
22

33
import org.utbot.common.JarUtils
44
import com.jetbrains.rd.util.getLogger
5+
import org.utbot.framework.plugin.api.BeanDefinitionData
6+
import org.utbot.framework.plugin.api.ClassId
7+
import org.utbot.framework.plugin.api.SpringRepositoryId
8+
import org.utbot.framework.plugin.api.util.jClass
59
import org.utbot.instrumentation.instrumentation.ArgumentList
610
import org.utbot.instrumentation.instrumentation.Instrumentation
711
import org.utbot.instrumentation.instrumentation.execution.mock.SpringInstrumentationContext
812
import org.utbot.instrumentation.process.HandlerClassesLoader
913
import org.utbot.spring.api.context.ContextWrapper
1014
import org.utbot.spring.api.repositoryWrapper.RepositoryInteraction
1115
import java.security.ProtectionDomain
16+
import java.util.IdentityHashMap
1217

1318
/**
1419
* UtExecutionInstrumentation wrapper that is aware of Spring config and initialises Spring context
1520
*/
1621
class SpringUtExecutionInstrumentation(
1722
private val delegateInstrumentation: UtExecutionInstrumentation,
18-
private val springConfig: String
23+
private val springConfig: String,
24+
private val beanDefinitions: List<BeanDefinitionData>,
1925
) : Instrumentation<UtConcreteExecutionResult> by delegateInstrumentation {
2026
private lateinit var instrumentationContext: SpringInstrumentationContext
27+
28+
private lateinit var relatedBeansCache: IdentityHashMap<Class<*>, Set<String>>
29+
2130
private val springContext: ContextWrapper get() = instrumentationContext.springContext
2231

2332
companion object {
@@ -26,14 +35,21 @@ class SpringUtExecutionInstrumentation(
2635
}
2736

2837
override fun init(pathsToUserClasses: Set<String>) {
29-
HandlerClassesLoader.addUrls(listOf(JarUtils.extractJarFileFromResources(
30-
jarFileName = SPRING_COMMONS_JAR_FILENAME,
31-
jarResourcePath = "lib/$SPRING_COMMONS_JAR_FILENAME",
32-
targetDirectoryName = "spring-commons"
33-
).path))
38+
HandlerClassesLoader.addUrls(
39+
listOf(
40+
JarUtils.extractJarFileFromResources(
41+
jarFileName = SPRING_COMMONS_JAR_FILENAME,
42+
jarResourcePath = "lib/$SPRING_COMMONS_JAR_FILENAME",
43+
targetDirectoryName = "spring-commons"
44+
).path
45+
)
46+
)
47+
3448
instrumentationContext = SpringInstrumentationContext(springConfig)
3549
delegateInstrumentation.instrumentationContext = instrumentationContext
3650
delegateInstrumentation.init(pathsToUserClasses)
51+
52+
relatedBeansCache = IdentityHashMap<Class<*>, Set<String>>()
3753
}
3854

3955
override fun invoke(
@@ -43,39 +59,46 @@ class SpringUtExecutionInstrumentation(
4359
parameters: Any?
4460
): UtConcreteExecutionResult {
4561
RepositoryInteraction.recordedInteractions.clear()
46-
// TODO properly detect which beans need to be reset, right now "orderRepository" and "orderService" are hardcoded
47-
val beanNamesToReset = listOf("orderRepository", "orderService")
4862

49-
beanNamesToReset.forEach { beanNameToReset ->
50-
val beanDefToReset = springContext.getBeanDefinition(beanNameToReset)
51-
springContext.removeBeanDefinition(beanNameToReset)
52-
springContext.registerBeanDefinition(beanNameToReset, beanDefToReset)
53-
}
63+
val beanNamesToReset: Set<String> = relatedBeansCache.getOrPut(clazz) { getRelevantBeanNames(clazz) }
64+
val repositoryDefinitions = springContext.resolveRepositories(beanNamesToReset)
5465

66+
beanNamesToReset.forEach { beanName -> springContext.resetBean(beanName) }
5567
val jdbcTemplate = getBean("jdbcTemplate")
56-
// TODO properly detect which repositories need to be cleared, right now "orders" is hardcoded
57-
val sql = "TRUNCATE TABLE orders"
58-
jdbcTemplate::class.java
59-
.getMethod("execute", sql::class.java)
60-
.invoke(jdbcTemplate, sql)
61-
val sql2 = "ALTER TABLE orders ALTER COLUMN id RESTART WITH 1"
62-
jdbcTemplate::class.java
63-
.getMethod("execute", sql::class.java)
64-
.invoke(jdbcTemplate, sql2)
68+
69+
for (repositoryDefinition in repositoryDefinitions) {
70+
val truncateTableCommand = "TRUNCATE TABLE ${repositoryDefinition.tableName}"
71+
jdbcTemplate::class.java
72+
.getMethod("execute", truncateTableCommand::class.java)
73+
.invoke(jdbcTemplate, truncateTableCommand)
74+
75+
val restartIdCommand = "ALTER TABLE ${repositoryDefinition.tableName} ALTER COLUMN id RESTART WITH 1"
76+
jdbcTemplate::class.java
77+
.getMethod("execute", restartIdCommand::class.java)
78+
.invoke(jdbcTemplate, restartIdCommand)
79+
}
6580

6681
return delegateInstrumentation.invoke(clazz, methodSignature, arguments, parameters)
6782
}
6883

84+
private fun getRelevantBeanNames(clazz: Class<*>): Set<String> =
85+
beanDefinitions
86+
.filter { it.beanTypeFqn == clazz.name }
87+
.flatMap { springContext.getDependenciesForBean(it.beanName) }
88+
.toSet()
89+
6990
fun getBean(beanName: String): Any = springContext.getBean(beanName)
7091

71-
fun saveToRepository(repository: Any, entity: Any) {
72-
// ignore repository interactions done during repository fill up
73-
val savedRecordedRepositoryResponses = RepositoryInteraction.recordedInteractions.toList()
74-
repository::class.java
75-
.getMethod("save", Any::class.java)
76-
.invoke(repository, entity)
77-
RepositoryInteraction.recordedInteractions.clear()
78-
RepositoryInteraction.recordedInteractions.addAll(savedRecordedRepositoryResponses)
92+
fun getRepositoryDescriptions(classId: ClassId): Set<SpringRepositoryId> {
93+
val relevantBeanNames = getRelevantBeanNames(classId.jClass)
94+
val repositoryDescriptions = springContext.resolveRepositories(relevantBeanNames.toSet())
95+
return repositoryDescriptions.map { repositoryDescription ->
96+
SpringRepositoryId(
97+
repositoryDescription.beanName,
98+
ClassId(repositoryDescription.repositoryName),
99+
ClassId(repositoryDescription.entityName),
100+
)
101+
}.toSet()
79102
}
80103

81104
override fun transform(
@@ -85,14 +108,15 @@ class SpringUtExecutionInstrumentation(
85108
protectionDomain: ProtectionDomain,
86109
classfileBuffer: ByteArray
87110
): ByteArray? =
88-
// TODO automatically detect which libraries we don't want to transform (by total transformation time)
89-
// transforming Spring takes too long
111+
// TODO: automatically detect which libraries we don't want to transform (by total transformation time)
90112
if (listOf(
91113
"org/springframework",
92114
"com/fasterxml",
93115
"org/hibernate",
94116
"org/apache",
95-
"org/h2"
117+
"org/h2",
118+
"javax/",
119+
"ch/qos",
96120
).any { className.startsWith(it) }
97121
) {
98122
null

utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/process/InstrumentedProcessMain.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import org.utbot.instrumentation.instrumentation.execution.SpringUtExecutionInst
1515
import org.utbot.instrumentation.instrumentation.execution.constructors.UtModelConstructor
1616
import org.utbot.instrumentation.process.generated.CollectCoverageResult
1717
import org.utbot.instrumentation.process.generated.GetSpringBeanResult
18+
import org.utbot.instrumentation.process.generated.GetSpringRepositoriesResult
1819
import org.utbot.instrumentation.process.generated.InstrumentedProcessModel
1920
import org.utbot.instrumentation.process.generated.InvokeMethodCommandResult
2021
import org.utbot.instrumentation.process.generated.instrumentedProcessModel
@@ -167,4 +168,9 @@ private fun InstrumentedProcessModel.setup(kryoHelper: KryoHelper, watchdog: Idl
167168
)
168169
GetSpringBeanResult(kryoHelper.writeObject(model))
169170
}
171+
watchdog.measureTimeForActiveCall(getSpringRepositories, "Getting Spring repositories") { params ->
172+
val classId: ClassId = kryoHelper.readObject(params.classId)
173+
val repositoryDescriptions = (instrumentation as SpringUtExecutionInstrumentation).getRepositoryDescriptions(classId)
174+
GetSpringRepositoriesResult(kryoHelper.writeObject(repositoryDescriptions))
175+
}
170176
}

0 commit comments

Comments
 (0)