Skip to content

Commit c17b0a7

Browse files
Improve dependent beans calculation (#2310)
1 parent 68cbef1 commit c17b0a7

File tree

11 files changed

+68
-42
lines changed

11 files changed

+68
-42
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package org.utbot.common
2+
3+
import java.net.URLClassLoader
4+
5+
/**
6+
* Checks that the class given by its binary name is on classpath of this classloader.
7+
*
8+
* Note: if the specified class is on classpath, `true` is returned even when
9+
* superclass (or implemented interfaces) aren't on the classpath.
10+
*/
11+
fun URLClassLoader.hasOnClasspath(classBinaryName: String): Boolean {
12+
val classFqn = classBinaryName.replace('.', '/').plus(".class")
13+
return this.findResource(classFqn) != null
14+
}

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,21 @@ package org.utbot.framework.plugin.api
33
/**
44
* Represents a covered bytecode instruction.
55
*
6-
* @param className the fqn in internal form, i.e. com/rest/order/services/OrderService$InnerClass.
6+
* @param internalName the fqn in internal form, i.e. com/rest/order/services/OrderService$InnerClass.
77
* @param methodSignature the signature of the method.
88
* @param lineNumber a number of the line in the source file.
99
* @param id a unique identifier among all instructions in all classes.
1010
*
1111
* @see <a href="CONFLUENCE:Test+Minimization">Test minimization</a>
1212
*/
1313
data class Instruction(
14-
val className: String,
14+
val internalName: String,
1515
val methodSignature: String,
1616
val lineNumber: Int,
1717
val id: Long
18-
)
18+
) {
19+
val className: String get() = internalName.replace('/', '.')
20+
}
1921

2022
/**
2123
* Represents coverage information. Some other

utbot-framework-test/src/test/kotlin/org/utbot/sarif/SarifReportTest.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,7 @@ class SarifReportTest {
383383

384384
private fun mockCoverage(mockExecution: UtExecution, lineNumber: Int, className: String) {
385385
Mockito.`when`(mockExecution.coverage?.coveredInstructions?.lastOrNull()?.lineNumber).thenReturn(1)
386+
Mockito.`when`(mockExecution.coverage?.coveredInstructions?.lastOrNull()?.internalName).thenReturn("Main")
386387
Mockito.`when`(mockExecution.coverage?.coveredInstructions?.lastOrNull()?.className).thenReturn("Main")
387388
(mockExecution as? UtSymbolicExecution)?.let { mockSymbolicSteps(it, lineNumber, className) }
388389
}

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

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,8 @@ import kotlinx.coroutines.launch
1010
import kotlinx.coroutines.yield
1111
import mu.KLogger
1212
import mu.KotlinLogging
13-
import org.utbot.common.measureTime
14-
import org.utbot.common.runBlockingWithCancellationPredicate
15-
import org.utbot.common.runIgnoringCancellationException
16-
import org.utbot.common.trace
13+
import org.utbot.common.*
1714
import org.utbot.common.PathUtil.toURL
18-
import org.utbot.common.tryLoadClass
1915
import org.utbot.engine.EngineController
2016
import org.utbot.engine.Mocker
2117
import org.utbot.engine.UtBotSymbolicEngine
@@ -81,6 +77,7 @@ open class TestCaseGenerator(
8177
UtExecutionInstrumentation,
8278
approach.config,
8379
applicationContext.beanDefinitions,
80+
buildDirs.map { it.toURL() }.toTypedArray(),
8481
)
8582
}
8683
is TypeReplacementApproach.DoNotReplace -> UtExecutionInstrumentation
@@ -385,23 +382,18 @@ open class TestCaseGenerator(
385382
val filteredCoveredInstructions =
386383
coverage.coveredInstructions
387384
.filter { instruction ->
388-
val instrClassFqn =
389-
instruction.className
390-
.also {
391-
val isInstrClassOnClassPath =
392-
isClassOnUserClasspathCache.getOrPut(it) {
393-
buildDirsClassLoader.findResource(it.plus(".class")) != null
394-
}
395-
if (!isInstrClassOnClassPath) return@filter false
396-
}
397-
.replace('/', '.')
385+
val instrClassName = instruction.className
398386

399-
// We do not want to filter out instructions that are in class under test
400-
if (instrClassFqn == classUnderTestId.name) return@filter true
387+
val isInstrClassOnClassPath =
388+
isClassOnUserClasspathCache.getOrPut(instrClassName) {
389+
buildDirsClassLoader.hasOnClasspath(instrClassName)
390+
}
401391

402-
// We do not want to take instructions in classes
403-
// marked with annotations from [annotationsToIgnore]
404-
return@filter !hasAnnotations(instrClassFqn, annotationsToIgnoreCoverage)
392+
// We want to
393+
// - always keep instructions that are in class under test
394+
// - ignore instructions in classes marked with annotations from [annotationsToIgnore]
395+
return@filter instrClassName == classUnderTestId.name ||
396+
(isInstrClassOnClassPath && !hasAnnotations(instrClassName, annotationsToIgnoreCoverage))
405397
}
406398
.ifEmpty {
407399
coverage.coveredInstructions

utbot-framework/src/main/kotlin/org/utbot/sarif/SarifReport.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -360,14 +360,14 @@ class SarifReport(
360360
?: return listOf()
361361

362362
val executionTrace = coveredInstructions.groupBy { instruction ->
363-
instruction.className to instruction.methodSignature // group by method
363+
instruction.internalName to instruction.methodSignature // group by method
364364
}.map { (_, instructionsForOneMethod) ->
365365
instructionsForOneMethod.last() // we need only last to construct the stack trace
366366
}
367367

368368
val sarifExecutionTrace = executionTrace.map { instruction ->
369369
resolveStackTraceElementByNames(
370-
classFqn = instruction.className.replace('/', '.'),
370+
classFqn = instruction.className,
371371
methodName = instruction.methodSignature.substringBefore('('),
372372
lineNumber = instruction.lineNumber
373373
)
@@ -508,7 +508,7 @@ class SarifReport(
508508
if (lastCoveredInstruction != null) {
509509
return Pair(
510510
lastCoveredInstruction.lineNumber, // .lineNumber is one-based
511-
lastCoveredInstruction.className.replace('/', '.')
511+
lastCoveredInstruction.className
512512
)
513513
}
514514

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ import org.utbot.instrumentation.instrumentation.execution.mock.SpringInstrument
1313
import org.utbot.instrumentation.process.HandlerClassesLoader
1414
import org.utbot.spring.api.context.ContextWrapper
1515
import org.utbot.spring.api.repositoryWrapper.RepositoryInteraction
16+
import java.net.URL
17+
import java.net.URLClassLoader
1618
import java.security.ProtectionDomain
17-
import java.util.IdentityHashMap
1819

1920
/**
2021
* UtExecutionInstrumentation wrapper that is aware of Spring config and initialises Spring context
@@ -23,8 +24,11 @@ class SpringUtExecutionInstrumentation(
2324
private val delegateInstrumentation: UtExecutionInstrumentation,
2425
private val springConfig: String,
2526
private val beanDefinitions: List<BeanDefinitionData>,
27+
private val buildDirs: Array<URL>,
2628
) : Instrumentation<UtConcreteExecutionResult> by delegateInstrumentation {
29+
2730
private lateinit var instrumentationContext: SpringInstrumentationContext
31+
private lateinit var userSourcesClassLoader: URLClassLoader
2832

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

@@ -47,6 +51,7 @@ class SpringUtExecutionInstrumentation(
4751
)
4852

4953
instrumentationContext = SpringInstrumentationContext(springConfig)
54+
userSourcesClassLoader = URLClassLoader(buildDirs, null)
5055
delegateInstrumentation.instrumentationContext = instrumentationContext
5156
delegateInstrumentation.init(pathsToUserClasses)
5257
}
@@ -60,7 +65,7 @@ class SpringUtExecutionInstrumentation(
6065
RepositoryInteraction.recordedInteractions.clear()
6166

6267
val beanNamesToReset: Set<String> = getRelevantBeanNames(clazz)
63-
val repositoryDefinitions = springContext.resolveRepositories(beanNamesToReset)
68+
val repositoryDefinitions = springContext.resolveRepositories(beanNamesToReset, userSourcesClassLoader)
6469

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

9398
fun getRepositoryDescriptions(classId: ClassId): Set<SpringRepositoryId> {
9499
val relevantBeanNames = getRelevantBeanNames(classId.jClass)
95-
val repositoryDescriptions = springContext.resolveRepositories(relevantBeanNames.toSet())
100+
val repositoryDescriptions = springContext.resolveRepositories(relevantBeanNames.toSet(), userSourcesClassLoader)
96101
return repositoryDescriptions.map { repositoryDescription ->
97102
SpringRepositoryId(
98103
repositoryDescription.beanName,

utbot-junit-contest/src/main/kotlin/org/utbot/contest/Statistics.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ data class CoverageStatistic(val covered: Int, val total: Int)
237237
private fun CoverageInstructionsSet?.getCoverageInfo(classNames: Set<String>): CoverageStatistic = this?.run {
238238
CoverageStatistic(
239239
coveredInstructions.filter {
240-
instr -> classNames.contains(instr.className)
240+
instr -> classNames.contains(instr.internalName)
241241
}.map { it.id }.distinct().size,
242242
totalInstructions.toInt()
243243
)

utbot-spring-commons-api/src/main/kotlin/org/utbot/spring/api/context/ContextWrapper.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
package org.utbot.spring.api.context
22

3+
import java.net.URLClassLoader
4+
5+
//TODO: `userSourcesClassLoader` must not be passed as a method argument, requires refactoring
36
interface ContextWrapper {
47
val context: Any
58

69
fun getBean(beanName: String): Any
710

8-
fun getDependenciesForBean(beanName: String): Set<String>
11+
fun getDependenciesForBean(beanName: String, userSourcesClassLoader: URLClassLoader): Set<String>
912

1013
fun resetBean(beanName: String): Any
1114

12-
fun resolveRepositories(beanNames: Set<String>): Set<RepositoryDescription>
15+
fun resolveRepositories(beanNames: Set<String>, userSourcesClassLoader: URLClassLoader): Set<RepositoryDescription>
1316
}
1417

1518
data class RepositoryDescription(

utbot-spring-commons-api/src/main/kotlin/org/utbot/spring/api/instantiator/InstantiationSettings.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ package org.utbot.spring.api.instantiator
33
class InstantiationSettings(
44
val configurationClasses: Array<Class<*>>,
55
val profileExpression: String?,
6-
)
6+
)

utbot-spring-commons/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ java {
1515

1616
dependencies {
1717
implementation(project(":utbot-spring-commons-api"))
18+
implementation(project(":utbot-core"))
1819

1920
// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot
2021
compileOnly("org.springframework.boot:spring-boot:$springBootVersion")

0 commit comments

Comments
 (0)