Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Improve dependent beans calculation
  • Loading branch information
EgorkaKulikov committed Jun 26, 2023
commit fcdf275391b38fe458c1593a525d355f423b42c3
11 changes: 11 additions & 0 deletions utbot-core/src/main/kotlin/org/utbot/common/ClassLoaderUtil.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.utbot.common

import java.net.URLClassLoader

/**
* Checks that the class given by its binary name can be loaded with given classLoader.
*/
fun URLClassLoader.canLoad(classBinaryName: String): Boolean {
Comment thread
EgorkaKulikov marked this conversation as resolved.
Outdated
val classFqn = classBinaryName.replace('.', '/').plus(".class")
return this.findResource(classFqn) != null
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ open class TestCaseGenerator(
UtExecutionInstrumentation,
approach.config,
applicationContext.beanDefinitions,
buildDirs.map { it.toURL() }.toTypedArray(),
)
}
is TypeReplacementApproach.DoNotReplace -> UtExecutionInstrumentation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ import org.utbot.instrumentation.instrumentation.execution.mock.SpringInstrument
import org.utbot.instrumentation.process.HandlerClassesLoader
import org.utbot.spring.api.context.ContextWrapper
import org.utbot.spring.api.repositoryWrapper.RepositoryInteraction
import java.net.URL
import java.net.URLClassLoader
import java.security.ProtectionDomain
import java.util.IdentityHashMap

/**
* UtExecutionInstrumentation wrapper that is aware of Spring config and initialises Spring context
Expand All @@ -23,8 +24,11 @@ class SpringUtExecutionInstrumentation(
private val delegateInstrumentation: UtExecutionInstrumentation,
private val springConfig: String,
private val beanDefinitions: List<BeanDefinitionData>,
private val buildDirs: Array<URL>,
) : Instrumentation<UtConcreteExecutionResult> by delegateInstrumentation {

private lateinit var instrumentationContext: SpringInstrumentationContext
private lateinit var userSourcesClassLoader: URLClassLoader

private val relatedBeansCache = mutableMapOf<Class<*>, Set<String>>()

Expand All @@ -47,6 +51,7 @@ class SpringUtExecutionInstrumentation(
)

instrumentationContext = SpringInstrumentationContext(springConfig)
userSourcesClassLoader = URLClassLoader(buildDirs, null)
delegateInstrumentation.instrumentationContext = instrumentationContext
delegateInstrumentation.init(pathsToUserClasses)
}
Expand All @@ -60,7 +65,7 @@ class SpringUtExecutionInstrumentation(
RepositoryInteraction.recordedInteractions.clear()

val beanNamesToReset: Set<String> = getRelevantBeanNames(clazz)
val repositoryDefinitions = springContext.resolveRepositories(beanNamesToReset)
val repositoryDefinitions = springContext.resolveRepositories(beanNamesToReset, userSourcesClassLoader)

beanNamesToReset.forEach { beanName -> springContext.resetBean(beanName) }
val jdbcTemplate = getBean("jdbcTemplate")
Expand All @@ -83,7 +88,7 @@ class SpringUtExecutionInstrumentation(
private fun getRelevantBeanNames(clazz: Class<*>): Set<String> = relatedBeansCache.getOrPut(clazz) {
beanDefinitions
.filter { it.beanTypeFqn == clazz.name }
.flatMap { springContext.getDependenciesForBean(it.beanName) }
.flatMap { springContext.getDependenciesForBean(it.beanName, userSourcesClassLoader) }
.toSet()
.also { logger.info { "Detected relevant beans for class ${clazz.name}: $it" } }
}
Expand All @@ -92,7 +97,7 @@ class SpringUtExecutionInstrumentation(

fun getRepositoryDescriptions(classId: ClassId): Set<SpringRepositoryId> {
val relevantBeanNames = getRelevantBeanNames(classId.jClass)
val repositoryDescriptions = springContext.resolveRepositories(relevantBeanNames.toSet())
val repositoryDescriptions = springContext.resolveRepositories(relevantBeanNames.toSet(), userSourcesClassLoader)
return repositoryDescriptions.map { repositoryDescription ->
SpringRepositoryId(
repositoryDescription.beanName,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
package org.utbot.spring.api.context

import java.net.URLClassLoader

//TODO: `userSourcesClassLoader` must not be passed as a method argument, requires refactoring
interface ContextWrapper {
val context: Any

fun getBean(beanName: String): Any

fun getDependenciesForBean(beanName: String): Set<String>
fun getDependenciesForBean(beanName: String, userSourcesClassLoader: URLClassLoader): Set<String>

fun resetBean(beanName: String): Any

fun resolveRepositories(beanNames: Set<String>): Set<RepositoryDescription>
fun resolveRepositories(beanNames: Set<String>, userSourcesClassLoader: URLClassLoader): Set<RepositoryDescription>
}

data class RepositoryDescription(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ package org.utbot.spring.api.instantiator
class InstantiationSettings(
val configurationClasses: Array<Class<*>>,
val profileExpression: String?,
)
)
1 change: 1 addition & 0 deletions utbot-spring-commons/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ java {

dependencies {
implementation(project(":utbot-spring-commons-api"))
implementation(project(":utbot-core"))

// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot
compileOnly("org.springframework.boot:spring-boot:$springBootVersion")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import com.jetbrains.rd.util.warn
import org.springframework.beans.factory.support.BeanDefinitionRegistry
import org.springframework.context.ConfigurableApplicationContext
import org.springframework.data.repository.CrudRepository
import org.utbot.common.canLoad
import org.utbot.spring.api.context.ContextWrapper
import org.utbot.spring.api.context.RepositoryDescription
import java.net.URLClassLoader

private val logger = getLogger<SpringContextWrapper>()

class SpringContextWrapper(override val context: ConfigurableApplicationContext) : ContextWrapper {

private val springClassPrefix = "org.springframework"

private val isCrudRepositoryOnClasspath = try {
CrudRepository::class.java.name
true
Expand All @@ -23,14 +23,15 @@ class SpringContextWrapper(override val context: ConfigurableApplicationContext)

override fun getBean(beanName: String): Any = context.getBean(beanName)

override fun getDependenciesForBean(beanName: String): Set<String> {
override fun getDependenciesForBean(beanName: String, userSourcesClassLoader: URLClassLoader): Set<String> {
val analyzedBeanNames = mutableSetOf<String>()
return getDependenciesForBeanInternal(beanName, analyzedBeanNames)
return getDependenciesForBeanInternal(beanName, analyzedBeanNames, userSourcesClassLoader)
}

private fun getDependenciesForBeanInternal(
beanName: String,
analyzedBeanNames: MutableSet<String>,
userSourcesClassLoader: URLClassLoader,
): Set<String> {
if (beanName in analyzedBeanNames) {
return emptySet()
Expand All @@ -40,10 +41,17 @@ class SpringContextWrapper(override val context: ConfigurableApplicationContext)

val dependencyBeanNames = context.beanFactory
.getDependenciesForBean(beanName)
.filter { it in context.beanDefinitionNames } // filters out inner beans
// this filtering is applied to avoid inner beans
.filter { it in context.beanDefinitionNames }
.filter { name ->
val clazz = getBean(name)::class.java
// here immediate hierarchy is enough because proxies are inherited directly
val immediateClazzHierarchy = clazz.interfaces + clazz.superclass + clazz
immediateClazzHierarchy.any { clazz -> userSourcesClassLoader.canLoad(clazz.name) }
}
.toSet()

return setOf(beanName) + dependencyBeanNames.flatMap { getDependenciesForBean(it) }
return setOf(beanName) + dependencyBeanNames.flatMap { getDependenciesForBean(it, userSourcesClassLoader) }
}

override fun resetBean(beanName: String) {
Expand All @@ -54,7 +62,7 @@ class SpringContextWrapper(override val context: ConfigurableApplicationContext)
beanDefinitionRegistry.registerBeanDefinition(beanName, beanDefinition)
}

override fun resolveRepositories(beanNames: Set<String>): Set<RepositoryDescription> {
override fun resolveRepositories(beanNames: Set<String>, userSourcesClassLoader: URLClassLoader): Set<RepositoryDescription> {
if (!isCrudRepositoryOnClasspath) return emptySet()
val repositoryBeans = beanNames
.map { beanName -> SimpleBeanDefinition(beanName, getBean(beanName)) }
Expand All @@ -67,7 +75,7 @@ class SpringContextWrapper(override val context: ConfigurableApplicationContext)
val repositoryClass = repositoryBean.bean::class.java
val repositoryClassName = repositoryClass
.interfaces
.filterNot { it.name.startsWith(springClassPrefix) }
.filter { clazz -> userSourcesClassLoader.canLoad(clazz.name) }
.filter { CrudRepository::class.java.isAssignableFrom(it) }
.map { it.name }
.firstOrNull() ?: CrudRepository::class.java.name
Expand Down