Skip to content

Commit c2813b6

Browse files
Automatically detect the names of tables and repositories (#2274)
--------- Co-authored-by: IlyaMuravjov <muravjovilya@gmail.com>
1 parent 9893856 commit c2813b6

File tree

15 files changed

+408
-72
lines changed

15 files changed

+408
-72
lines changed

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

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ 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.getRelevantSpringRepositories
4950
import org.utbot.instrumentation.instrumentation.Instrumentation
5051
import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionData
5152
import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult
@@ -388,18 +389,12 @@ class UtBotSymbolicEngine(
388389
.letIf(applicationContext is SpringApplicationContext
389390
&& applicationContext.typeReplacementApproach is TypeReplacementApproach.ReplaceIfPossible
390391
) { 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-
)
402-
logger.info { "Relevant repositories: $relevantRepositories" }
392+
val relevantRepositories = concreteExecutor.getRelevantSpringRepositories(methodUnderTest.classId)
393+
val generatedValueFieldIds = relevantRepositories.map {
394+
repository -> FieldId(repository.entityClassId, "id")
395+
}
396+
397+
logger.info { "Detected relevant repositories for class ${methodUnderTest.classId}: $relevantRepositories" }
403398
// spring should try to generate bean values, but if it fails, then object value provider is used for it
404399
val springBeanValueProvider = SpringBeanValueProvider(
405400
defaultIdGenerator,

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<*, *>.getRelevantSpringRepositories(classId: ClassId): Set<SpringRepositoryId> = runBlocking {
286+
withProcess {
287+
val classId = kryoHelper.writeObject(classId)
288+
val params = GetSpringRepositoriesParams(classId)
289+
val result = instrumentedProcessModel.getRelevantSpringRepositories.startSuspending(lifetime, params)
290+
291+
kryoHelper.readObject(result.springRepositoryIds)
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: 58 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,32 @@ package org.utbot.instrumentation.instrumentation.execution
22

33
import org.utbot.common.JarUtils
44
import com.jetbrains.rd.util.getLogger
5+
import com.jetbrains.rd.util.info
6+
import org.utbot.framework.plugin.api.BeanDefinitionData
7+
import org.utbot.framework.plugin.api.ClassId
8+
import org.utbot.framework.plugin.api.SpringRepositoryId
9+
import org.utbot.framework.plugin.api.util.jClass
510
import org.utbot.instrumentation.instrumentation.ArgumentList
611
import org.utbot.instrumentation.instrumentation.Instrumentation
712
import org.utbot.instrumentation.instrumentation.execution.mock.SpringInstrumentationContext
813
import org.utbot.instrumentation.process.HandlerClassesLoader
914
import org.utbot.spring.api.context.ContextWrapper
1015
import org.utbot.spring.api.repositoryWrapper.RepositoryInteraction
1116
import java.security.ProtectionDomain
17+
import java.util.IdentityHashMap
1218

1319
/**
1420
* UtExecutionInstrumentation wrapper that is aware of Spring config and initialises Spring context
1521
*/
1622
class SpringUtExecutionInstrumentation(
1723
private val delegateInstrumentation: UtExecutionInstrumentation,
18-
private val springConfig: String
24+
private val springConfig: String,
25+
private val beanDefinitions: List<BeanDefinitionData>,
1926
) : Instrumentation<UtConcreteExecutionResult> by delegateInstrumentation {
2027
private lateinit var instrumentationContext: SpringInstrumentationContext
28+
29+
private val relatedBeansCache = mutableMapOf<Class<*>, Set<String>>()
30+
2131
private val springContext: ContextWrapper get() = instrumentationContext.springContext
2232

2333
companion object {
@@ -26,11 +36,16 @@ class SpringUtExecutionInstrumentation(
2636
}
2737

2838
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))
39+
HandlerClassesLoader.addUrls(
40+
listOf(
41+
JarUtils.extractJarFileFromResources(
42+
jarFileName = SPRING_COMMONS_JAR_FILENAME,
43+
jarResourcePath = "lib/$SPRING_COMMONS_JAR_FILENAME",
44+
targetDirectoryName = "spring-commons"
45+
).path
46+
)
47+
)
48+
3449
instrumentationContext = SpringInstrumentationContext(springConfig)
3550
delegateInstrumentation.instrumentationContext = instrumentationContext
3651
delegateInstrumentation.init(pathsToUserClasses)
@@ -43,39 +58,48 @@ class SpringUtExecutionInstrumentation(
4358
parameters: Any?
4459
): UtConcreteExecutionResult {
4560
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")
4861

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

65+
beanNamesToReset.forEach { beanName -> springContext.resetBean(beanName) }
5566
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)
67+
68+
for (repositoryDefinition in repositoryDefinitions) {
69+
val truncateTableCommand = "TRUNCATE TABLE ${repositoryDefinition.tableName}"
70+
jdbcTemplate::class.java
71+
.getMethod("execute", truncateTableCommand::class.java)
72+
.invoke(jdbcTemplate, truncateTableCommand)
73+
74+
val restartIdCommand = "ALTER TABLE ${repositoryDefinition.tableName} ALTER COLUMN id RESTART WITH 1"
75+
jdbcTemplate::class.java
76+
.getMethod("execute", restartIdCommand::class.java)
77+
.invoke(jdbcTemplate, restartIdCommand)
78+
}
6579

6680
return delegateInstrumentation.invoke(clazz, methodSignature, arguments, parameters)
6781
}
6882

83+
private fun getRelevantBeanNames(clazz: Class<*>): Set<String> = relatedBeansCache.getOrPut(clazz) {
84+
beanDefinitions
85+
.filter { it.beanTypeFqn == clazz.name }
86+
.flatMap { springContext.getDependenciesForBean(it.beanName) }
87+
.toSet()
88+
.also { logger.info { "Detected relevant beans for class ${clazz.name}: $it" } }
89+
}
90+
6991
fun getBean(beanName: String): Any = springContext.getBean(beanName)
7092

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)
93+
fun getRepositoryDescriptions(classId: ClassId): Set<SpringRepositoryId> {
94+
val relevantBeanNames = getRelevantBeanNames(classId.jClass)
95+
val repositoryDescriptions = springContext.resolveRepositories(relevantBeanNames.toSet())
96+
return repositoryDescriptions.map { repositoryDescription ->
97+
SpringRepositoryId(
98+
repositoryDescription.beanName,
99+
ClassId(repositoryDescription.repositoryName),
100+
ClassId(repositoryDescription.entityName),
101+
)
102+
}.toSet()
79103
}
80104

81105
override fun transform(
@@ -85,14 +109,15 @@ class SpringUtExecutionInstrumentation(
85109
protectionDomain: ProtectionDomain,
86110
classfileBuffer: ByteArray
87111
): ByteArray? =
88-
// TODO automatically detect which libraries we don't want to transform (by total transformation time)
89-
// transforming Spring takes too long
112+
// TODO: automatically detect which libraries we don't want to transform (by total transformation time)
90113
if (listOf(
91114
"org/springframework",
92115
"com/fasterxml",
93116
"org/hibernate",
94117
"org/apache",
95-
"org/h2"
118+
"org/h2",
119+
"javax/",
120+
"ch/qos",
96121
).any { className.startsWith(it) }
97122
) {
98123
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(getRelevantSpringRepositories, "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)