Skip to content

Commit 5672783

Browse files
committed
Refactor ConcreteExecutionContext interface, to avoid whens by ApplicationContext type
1 parent 6569628 commit 5672783

File tree

15 files changed

+357
-245
lines changed

15 files changed

+357
-245
lines changed

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

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1319,7 +1319,7 @@ interface CodeGenerationContext
13191319
interface SpringCodeGenerationContext : CodeGenerationContext {
13201320
val springTestType: SpringTestType
13211321
val springSettings: SpringSettings
1322-
val springContextLoadingResult: SpringContextLoadingResult?
1322+
val concreteContextLoadingResult: ConcreteContextLoadingResult?
13231323
}
13241324

13251325
sealed class SpringConfiguration(val fullDisplayName: String) {
@@ -1345,14 +1345,35 @@ sealed interface SpringSettings {
13451345
}
13461346

13471347
/**
1348-
* [contextLoaded] can be `true` while [exceptions] is not empty,
1349-
* if we failed to use most specific SpringApi available (e.g. SpringBoot), but
1350-
* were able to successfully fall back to less specific SpringApi (e.g. PureSpring).
1348+
* Result of loading concrete execution context (e.g. Spring application context).
1349+
*
1350+
* [contextLoaded] can be `true` while [exceptions] is not empty. For example, we may fail
1351+
* to load context with most specific SpringApi available (e.g. SpringBoot),
1352+
* but successfully fall back to less specific SpringApi (e.g. PureSpring).
13511353
*/
1352-
class SpringContextLoadingResult(
1354+
class ConcreteContextLoadingResult(
13531355
val contextLoaded: Boolean,
13541356
val exceptions: List<Throwable>
1355-
)
1357+
) {
1358+
val utErrors: List<UtError> get() =
1359+
exceptions.map { UtError(it.message ?: "Concrete context loading failed", it) }
1360+
1361+
fun andThen(onSuccess: () -> ConcreteContextLoadingResult) =
1362+
if (contextLoaded) {
1363+
val otherResult = onSuccess()
1364+
ConcreteContextLoadingResult(
1365+
contextLoaded = otherResult.contextLoaded,
1366+
exceptions = exceptions + otherResult.exceptions
1367+
)
1368+
} else this
1369+
1370+
companion object {
1371+
fun successWithoutExceptions() = ConcreteContextLoadingResult(
1372+
contextLoaded = true,
1373+
exceptions = emptyList()
1374+
)
1375+
}
1376+
}
13561377

13571378
enum class SpringTestType(
13581379
override val id: String,

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

Lines changed: 9 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import org.utbot.analytics.Predictors
1010
import org.utbot.api.exception.UtMockAssumptionViolatedException
1111
import org.utbot.common.debug
1212
import org.utbot.common.measureTime
13-
import org.utbot.common.tryLoadClass
1413
import org.utbot.engine.MockStrategy.NO_MOCKS
1514
import org.utbot.engine.pc.*
1615
import org.utbot.engine.selectors.*
@@ -34,7 +33,7 @@ import org.utbot.framework.UtSettings.pathSelectorType
3433
import org.utbot.framework.UtSettings.processUnknownStatesDuringConcreteExecution
3534
import org.utbot.framework.UtSettings.useDebugVisualization
3635
import org.utbot.framework.context.ApplicationContext
37-
import org.utbot.framework.context.spring.SpringApplicationContext
36+
import org.utbot.framework.context.ConcreteExecutionContext
3837
import org.utbot.framework.plugin.api.*
3938
import org.utbot.framework.plugin.api.Step
4039
import org.utbot.framework.plugin.api.util.*
@@ -44,17 +43,11 @@ import org.utbot.framework.util.graph
4443
import org.utbot.framework.util.sootMethod
4544
import org.utbot.fuzzer.*
4645
import org.utbot.fuzzing.*
47-
import org.utbot.fuzzing.providers.FieldValueProvider
48-
import org.utbot.fuzzing.providers.ObjectValueProvider
49-
import org.utbot.fuzzing.spring.SavedEntityValueProvider
50-
import org.utbot.fuzzing.spring.SpringBeanValueProvider
5146
import org.utbot.fuzzing.utils.Trie
5247
import org.utbot.instrumentation.ConcreteExecutor
53-
import org.utbot.instrumentation.getRelevantSpringRepositories
5448
import org.utbot.instrumentation.instrumentation.Instrumentation
5549
import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionData
5650
import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult
57-
import org.utbot.instrumentation.instrumentation.execution.UtExecutionInstrumentation
5851
import org.utbot.taint.*
5952
import org.utbot.taint.model.TaintConfiguration
6053
import soot.jimple.Stmt
@@ -118,7 +111,7 @@ class UtBotSymbolicEngine(
118111
val mockStrategy: MockStrategy = NO_MOCKS,
119112
chosenClassesToMockAlways: Set<ClassId>,
120113
val applicationContext: ApplicationContext,
121-
executionInstrumentationFactory: UtExecutionInstrumentation.Factory<*>,
114+
val concreteExecutionContext: ConcreteExecutionContext,
122115
userTaintConfigurationProvider: TaintConfigurationProvider? = null,
123116
private val solverTimeoutInMillis: Int = checkSolverTimeoutMillis,
124117
) : UtContextInitializer() {
@@ -191,7 +184,7 @@ class UtBotSymbolicEngine(
191184

192185
private val concreteExecutor =
193186
ConcreteExecutor(
194-
executionInstrumentationFactory,
187+
concreteExecutionContext.instrumentationFactory,
195188
classpath,
196189
).apply { this.classLoader = utContext.classLoader }
197190

@@ -393,57 +386,15 @@ class UtBotSymbolicEngine(
393386
val names = graph.body.method.tags.filterIsInstance<ParamNamesTag>().firstOrNull()?.names ?: emptyList()
394387
var testEmittedByFuzzer = 0
395388

396-
if (applicationContext is SpringApplicationContext &&
397-
applicationContext.springTestType == SpringTestType.INTEGRATION_TEST &&
398-
applicationContext.getBeansAssignableTo(methodUnderTest.classId).isEmpty()) {
399-
val fullConfigDisplayName = (applicationContext.springSettings as? SpringSettings.PresentSpringSettings)
400-
?.configuration?.fullDisplayName
401-
val errorDescription = "No beans of type ${methodUnderTest.classId.name} are found. " +
402-
"Try choosing different Spring configuration or adding beans to $fullConfigDisplayName"
403-
emit(UtError(
404-
errorDescription,
405-
IllegalStateException(errorDescription)
406-
))
389+
val valueProviders = try {
390+
concreteExecutionContext.tryCreateValueProvider(concreteExecutor, classUnderTest, defaultIdGenerator)
391+
} catch (e: Exception) {
392+
emit(UtError(e.message ?: "Failed to create ValueProvider", e))
407393
return@flow
408-
}
409-
410-
val valueProviders = ValueProvider.of(defaultValueProviders(defaultIdGenerator))
411-
.letIf(applicationContext is SpringApplicationContext
412-
&& applicationContext.springTestType == SpringTestType.INTEGRATION_TEST
413-
) { provider ->
414-
val relevantRepositories = concreteExecutor.getRelevantSpringRepositories(methodUnderTest.classId)
415-
logger.info { "Detected relevant repositories for class ${methodUnderTest.classId}: $relevantRepositories" }
394+
}.let(transform)
416395

417-
val generatedValueAnnotationClasses = SpringModelUtils.generatedValueClassIds.mapNotNull {
418-
@Suppress("UNCHECKED_CAST") // type system fails to understand that GeneratedValue is indeed an annotation
419-
utContext.classLoader.tryLoadClass(it.name) as Class<out Annotation>?
420-
}
421-
422-
val generatedValueFieldIds =
423-
relevantRepositories
424-
.map { it.entityClassId.jClass }
425-
.flatMap { entityClass -> generateSequence(entityClass) { it.superclass } }
426-
.flatMap { it.declaredFields.toList() }
427-
.filter { field -> generatedValueAnnotationClasses.any { field.isAnnotationPresent(it) } }
428-
.map { it.fieldId }
429-
logger.info { "Detected @GeneratedValue fields: $generatedValueFieldIds" }
430-
431-
// spring should try to generate bean values, but if it fails, then object value provider is used for it
432-
val springBeanValueProvider = SpringBeanValueProvider(
433-
defaultIdGenerator,
434-
beanNameProvider = { classId ->
435-
(applicationContext as SpringApplicationContext).getBeansAssignableTo(classId)
436-
.map { it.beanName }
437-
},
438-
relevantRepositories = relevantRepositories
439-
).withFallback(ObjectValueProvider(defaultIdGenerator))
440-
provider
441-
.except { p -> p is ObjectValueProvider }
442-
.with(springBeanValueProvider)
443-
.with(ValueProvider.of(relevantRepositories.map { SavedEntityValueProvider(defaultIdGenerator, it) }))
444-
.with(ValueProvider.of(generatedValueFieldIds.map { FieldValueProvider(defaultIdGenerator, it) }))
445-
}.let(transform)
446396
val coverageToMinStateBeforeSize = mutableMapOf<Trie.Node<Instruction>, Int>()
397+
447398
runJavaFuzzing(
448399
defaultIdGenerator,
449400
methodUnderTest,

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgSpringIntegrationTestClassConstructor.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ class CgSpringIntegrationTestClassConstructor(
5858
)
5959

6060
private fun constructContextLoadsMethod() : CgTestMethod {
61-
val contextLoadingResult = springCodeGenerationContext.springContextLoadingResult
61+
val contextLoadingResult = springCodeGenerationContext.concreteContextLoadingResult
6262
if (contextLoadingResult == null)
6363
logger.error { "Missing contextLoadingResult" }
6464
val exception = contextLoadingResult?.exceptions?.firstOrNull()
Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,29 @@
11
package org.utbot.framework.context
22

3-
import org.utbot.framework.plugin.api.UtError
3+
import org.utbot.framework.plugin.api.ClassId
4+
import org.utbot.framework.plugin.api.ConcreteContextLoadingResult
5+
import org.utbot.framework.plugin.api.UtExecution
6+
import org.utbot.fuzzer.IdentityPreservingIdGenerator
7+
import org.utbot.fuzzing.JavaValueProvider
8+
import org.utbot.instrumentation.ConcreteExecutor
9+
import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult
10+
import org.utbot.instrumentation.instrumentation.execution.UtExecutionInstrumentation
411

512
interface ConcreteExecutionContext {
6-
fun preventsFurtherTestGeneration(): Boolean
13+
val instrumentationFactory: UtExecutionInstrumentation.Factory<*>
714

8-
fun getErrors(): List<UtError>
15+
fun loadContext(
16+
concreteExecutor: ConcreteExecutor<UtConcreteExecutionResult, UtExecutionInstrumentation>,
17+
): ConcreteContextLoadingResult
918

10-
// TODO refactor, so this interface only includes the following:
11-
// val instrumentationFactory: UtExecutionInstrumentation.Factory<*>
12-
// fun createValueProviderOrThrow(classUnderTest: ClassId, idGenerator: IdentityPreservingIdGenerator<Int>): JavaValueProvider
13-
// fun loadContext(): ContextLoadingResult
14-
// fun Coverage.filterCoveredInstructions(classUnderTestId: ClassId): Coverage
19+
fun transformExecutionsBeforeMinimization(
20+
executions: List<UtExecution>,
21+
classUnderTestId: ClassId
22+
): List<UtExecution>
23+
24+
fun tryCreateValueProvider(
25+
concreteExecutor: ConcreteExecutor<UtConcreteExecutionResult, UtExecutionInstrumentation>,
26+
classUnderTest: ClassId,
27+
idGenerator: IdentityPreservingIdGenerator<Int>,
28+
): JavaValueProvider
1529
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package org.utbot.framework.context.custom
2+
3+
import mu.KotlinLogging
4+
import org.utbot.common.hasOnClasspath
5+
import org.utbot.common.tryLoadClass
6+
import org.utbot.framework.context.ConcreteExecutionContext
7+
import org.utbot.framework.plugin.api.ClassId
8+
import org.utbot.framework.plugin.api.UtExecution
9+
import org.utbot.framework.plugin.api.util.utContext
10+
import java.io.File
11+
import java.net.URLClassLoader
12+
13+
/**
14+
* Decorator of [delegateContext] that filters coverage before test set minimization
15+
* (see [transformExecutionsBeforeMinimization]) to avoid generating too many tests that
16+
* only increase coverage of third party libraries.
17+
*
18+
* This implementation:
19+
* - always keeps instructions that are in class under test (even if other rules say otherwise)
20+
* - filters out instructions in classes marked with annotations from [annotationsToIgnoreCoverage]
21+
* - filters out instructions from classes that are not found in `classpathToIncludeCoverageFrom`
22+
*
23+
* Finally, if [keepOriginalCoverageOnEmptyFilteredCoverage] is `true` we restore original coverage
24+
* for executions whose coverage becomes empty after filtering.
25+
*/
26+
class CoverageFilteringConcreteExecutionContext(
27+
private val delegateContext: ConcreteExecutionContext,
28+
classpathToIncludeCoverageFrom: String,
29+
private val annotationsToIgnoreCoverage: Set<ClassId>,
30+
private val keepOriginalCoverageOnEmptyFilteredCoverage: Boolean,
31+
) : ConcreteExecutionContext by delegateContext {
32+
private val urlsToIncludeCoverageFrom = classpathToIncludeCoverageFrom.split(File.pathSeparator)
33+
.map { File(it).toURI().toURL() }
34+
.toTypedArray()
35+
36+
companion object {
37+
private val logger = KotlinLogging.logger {}
38+
}
39+
40+
override fun transformExecutionsBeforeMinimization(
41+
executions: List<UtExecution>,
42+
classUnderTestId: ClassId
43+
): List<UtExecution> {
44+
val annotationsToIgnoreCoverage =
45+
annotationsToIgnoreCoverage.mapNotNull { utContext.classLoader.tryLoadClass(it.name) }
46+
47+
val classLoaderToIncludeCoverageFrom = URLClassLoader(urlsToIncludeCoverageFrom, null)
48+
49+
val classesToIncludeCoverageFromCache = mutableMapOf<String, Boolean>()
50+
51+
return executions.map { execution ->
52+
val coverage = execution.coverage ?: return@map execution
53+
54+
val filteredCoveredInstructions =
55+
coverage.coveredInstructions
56+
.filter { instruction ->
57+
val instrClassName = instruction.className
58+
59+
classesToIncludeCoverageFromCache.getOrPut(instrClassName) {
60+
instrClassName == classUnderTestId.name ||
61+
(classLoaderToIncludeCoverageFrom.hasOnClasspath(instrClassName) &&
62+
!hasAnnotations(instrClassName, annotationsToIgnoreCoverage))
63+
}
64+
}
65+
.ifEmpty {
66+
if (keepOriginalCoverageOnEmptyFilteredCoverage) {
67+
logger.warn("Execution covered instruction list became empty. Proceeding with not filtered instruction list.")
68+
coverage.coveredInstructions
69+
} else {
70+
logger.warn("Execution covered instruction list became empty. Proceeding with empty coverage.")
71+
emptyList()
72+
}
73+
}
74+
75+
execution.copy(
76+
coverage = coverage.copy(
77+
coveredInstructions = filteredCoveredInstructions
78+
)
79+
)
80+
}
81+
}
82+
83+
private fun hasAnnotations(className: String, annotations: List<Class<*>>): Boolean =
84+
utContext
85+
.classLoader
86+
.loadClass(className)
87+
.annotations
88+
.any { existingAnnotation ->
89+
annotations.any { annotation ->
90+
annotation.isInstance(existingAnnotation)
91+
}
92+
}
93+
}

utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleApplicationContext.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,5 @@ class SimpleApplicationContext(
1717
override fun createConcreteExecutionContext(
1818
fullClasspath: String,
1919
classpathWithoutDependencies: String
20-
): ConcreteExecutionContext = SimpleConcreteExecutionContext(fullClasspath, classpathWithoutDependencies)
20+
): ConcreteExecutionContext = SimpleConcreteExecutionContext(fullClasspath)
2121
}
Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,35 @@
11
package org.utbot.framework.context.simple
22

33
import org.utbot.framework.context.ConcreteExecutionContext
4-
import org.utbot.framework.plugin.api.UtError
4+
import org.utbot.framework.plugin.api.ClassId
5+
import org.utbot.framework.plugin.api.ConcreteContextLoadingResult
6+
import org.utbot.framework.plugin.api.UtExecution
7+
import org.utbot.fuzzer.IdentityPreservingIdGenerator
8+
import org.utbot.fuzzing.JavaValueProvider
9+
import org.utbot.fuzzing.ValueProvider
10+
import org.utbot.fuzzing.defaultValueProviders
11+
import org.utbot.instrumentation.ConcreteExecutor
12+
import org.utbot.instrumentation.instrumentation.execution.SimpleUtExecutionInstrumentation
13+
import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult
14+
import org.utbot.instrumentation.instrumentation.execution.UtExecutionInstrumentation
15+
import java.io.File
516

6-
class SimpleConcreteExecutionContext(
7-
// TODO these properties will be used later (to fulfill TODO in ConcreteExecutionContext)
8-
val fullClassPath: String,
9-
val classpathWithoutDependencies: String
10-
) : ConcreteExecutionContext {
11-
override fun preventsFurtherTestGeneration(): Boolean = false
17+
class SimpleConcreteExecutionContext(fullClassPath: String) : ConcreteExecutionContext {
18+
override val instrumentationFactory: UtExecutionInstrumentation.Factory<*> =
19+
SimpleUtExecutionInstrumentation.Factory(fullClassPath.split(File.pathSeparator).toSet())
1220

13-
override fun getErrors(): List<UtError> = emptyList()
21+
override fun loadContext(
22+
concreteExecutor: ConcreteExecutor<UtConcreteExecutionResult, UtExecutionInstrumentation>,
23+
): ConcreteContextLoadingResult = ConcreteContextLoadingResult.successWithoutExceptions()
24+
25+
override fun transformExecutionsBeforeMinimization(
26+
executions: List<UtExecution>,
27+
classUnderTestId: ClassId
28+
): List<UtExecution> = executions
29+
30+
override fun tryCreateValueProvider(
31+
concreteExecutor: ConcreteExecutor<UtConcreteExecutionResult, UtExecutionInstrumentation>,
32+
classUnderTest: ClassId,
33+
idGenerator: IdentityPreservingIdGenerator<Int>
34+
): JavaValueProvider = ValueProvider.of(defaultValueProviders(idGenerator))
1435
}

utbot-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringApplicationContext.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import org.utbot.framework.context.ApplicationContext
44
import org.utbot.framework.plugin.api.BeanDefinitionData
55
import org.utbot.framework.plugin.api.ClassId
66
import org.utbot.framework.plugin.api.SpringCodeGenerationContext
7-
import org.utbot.framework.plugin.api.SpringContextLoadingResult
7+
import org.utbot.framework.plugin.api.ConcreteContextLoadingResult
88

99
/**
1010
* Data we get from Spring application context
@@ -19,6 +19,6 @@ interface SpringApplicationContext : ApplicationContext, SpringCodeGenerationCon
1919
val injectedTypes: Set<ClassId>
2020
val allInjectedSuperTypes: Set<ClassId>
2121

22-
override var springContextLoadingResult: SpringContextLoadingResult?
22+
override var concreteContextLoadingResult: ConcreteContextLoadingResult?
2323
fun getBeansAssignableTo(classId: ClassId): List<BeanDefinitionData>
2424
}

0 commit comments

Comments
 (0)