Skip to content

Commit dd787d0

Browse files
authored
Configure fallback from controller-specific integration tests to regular integration tests (#2605)
* Extract params of `tryCreateFuzzingContext` to `data class` * Configure `SpringIntegrationTestJavaFuzzingContext` to fallback to non-`mockMvc` tests * Avoid `mockMvc` and non-`mockMvc` tests with same execution path in method under test
1 parent 5dff7df commit dd787d0

File tree

10 files changed

+144
-47
lines changed

10 files changed

+144
-47
lines changed

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

+33
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,39 @@ object SpringModelUtils {
355355
returnType = resultMatcherClassId
356356
)
357357

358+
private val supportedControllerParameterAnnotations = setOf(
359+
pathVariableClassId,
360+
requestParamClassId,
361+
requestHeaderClassId,
362+
// cookieValueClassId, // TODO uncomment when #2542 is fixed
363+
requestAttributesClassId,
364+
sessionAttributesClassId,
365+
modelAttributesClassId,
366+
requestBodyClassId,
367+
)
368+
369+
/**
370+
* If a controller method has a parameter of one of these types, then we don't fully support conversion
371+
* of direct call of that controller method call to a request that can be done via `mockMvc` even if
372+
* said parameter is annotated with one of [supportedControllerParameterAnnotations].
373+
*/
374+
private val unsupportedControllerParameterTypes = setOf(
375+
dateClassId, // see #2505
376+
mapClassId, // e.g. `@RequestParam Map<String, Object>` is not yet properly handled
377+
)
378+
379+
/**
380+
* Returns `true` if for every parameter of [methodId] we have a mechanism of registering said
381+
* parameter in `requestBuilder` when calling controller method via `mockMvc.perform(requestBuilder)`.
382+
*/
383+
fun allControllerParametersAreSupported(methodId: MethodId): Boolean =
384+
methodId.parameters.none { it in unsupportedControllerParameterTypes } &&
385+
methodId.method.parameters.all { param ->
386+
param.annotations.any { annotation ->
387+
annotation.annotationClass.id in supportedControllerParameterAnnotations
388+
}
389+
}
390+
358391
fun createMockMvcModel(controller: UtModel?, idGenerator: () -> Int) =
359392
createBeanModel("mockMvc", idGenerator(), mockMvcClassId, modificationChainProvider = {
360393
// we need to keep controller modifications if there are any, so we add them to mockMvc

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

+14-4
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import org.utbot.framework.UtSettings.processUnknownStatesDuringConcreteExecutio
3434
import org.utbot.framework.UtSettings.useDebugVisualization
3535
import org.utbot.framework.context.ApplicationContext
3636
import org.utbot.framework.context.ConcreteExecutionContext
37+
import org.utbot.framework.context.ConcreteExecutionContext.FuzzingContextParams
3738
import org.utbot.framework.plugin.api.*
3839
import org.utbot.framework.plugin.api.Step
3940
import org.utbot.framework.plugin.api.util.*
@@ -119,7 +120,7 @@ class UtBotSymbolicEngine(
119120
userTaintConfigurationProvider: TaintConfigurationProvider? = null,
120121
private val solverTimeoutInMillis: Int = checkSolverTimeoutMillis,
121122
) : UtContextInitializer() {
122-
123+
123124
private val graph = methodUnderTest.sootMethod.jimpleBody().apply {
124125
logger.trace { "JIMPLE for $methodUnderTest:\n$this" }
125126
}.graph()
@@ -440,7 +441,16 @@ class UtBotSymbolicEngine(
440441
var testEmittedByFuzzer = 0
441442

442443
val fuzzingContext = try {
443-
concreteExecutionContext.tryCreateFuzzingContext(concreteExecutor, classUnderTest, defaultIdGenerator)
444+
concreteExecutionContext.tryCreateFuzzingContext(
445+
FuzzingContextParams(
446+
concreteExecutor = concreteExecutor,
447+
classUnderTest = classUnderTest,
448+
idGenerator = defaultIdGenerator,
449+
fuzzingStartTimeMillis = System.currentTimeMillis(),
450+
fuzzingEndTimeMillis = until,
451+
mockStrategy = mockStrategy,
452+
)
453+
)
444454
} catch (e: Exception) {
445455
emit(UtError(e.message ?: "Failed to create ValueProvider", e))
446456
return@flow
@@ -495,7 +505,7 @@ class UtBotSymbolicEngine(
495505
// in case an exception occurred from the concrete execution
496506
concreteExecutionResult ?: return@runJavaFuzzing BaseFeedback(result = Trie.emptyNode(), control = Control.PASS)
497507

498-
fuzzingContext.handleFuzzedConcreteExecutionResult(concreteExecutionResult)
508+
fuzzingContext.handleFuzzedConcreteExecutionResult(methodUnderTest, concreteExecutionResult)
499509

500510
// in case of processed failure in the concrete execution
501511
concreteExecutionResult.processedFailure()?.let { failure ->
@@ -855,7 +865,7 @@ private fun UtConcreteExecutionResult.violatesUtMockAssumption(): Boolean {
855865
}
856866

857867
private fun UtConcreteExecutionResult.processedFailure(): UtConcreteExecutionProcessedFailure?
858-
= result as? UtConcreteExecutionProcessedFailure
868+
= result as? UtConcreteExecutionProcessedFailure
859869

860870
private fun checkStaticMethodsMock(execution: UtSymbolicExecution) =
861871
execution.instrumentation.any { it is UtStaticMethodInstrumentation}

utbot-framework/src/main/kotlin/org/utbot/framework/context/ConcreteExecutionContext.kt

+11-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.utbot.framework.context
22

3+
import org.utbot.engine.MockStrategy
34
import org.utbot.framework.plugin.api.ClassId
45
import org.utbot.framework.plugin.api.ConcreteContextLoadingResult
56
import org.utbot.framework.plugin.api.ExecutableId
@@ -27,9 +28,14 @@ interface ConcreteExecutionContext {
2728
rerunExecutor: ConcreteExecutor<UtConcreteExecutionResult, UtExecutionInstrumentation>,
2829
): List<UtExecution>
2930

30-
fun tryCreateFuzzingContext(
31-
concreteExecutor: ConcreteExecutor<UtConcreteExecutionResult, UtExecutionInstrumentation>,
32-
classUnderTest: ClassId,
33-
idGenerator: IdentityPreservingIdGenerator<Int>,
34-
): JavaFuzzingContext
31+
fun tryCreateFuzzingContext(params: FuzzingContextParams): JavaFuzzingContext
32+
33+
data class FuzzingContextParams(
34+
val concreteExecutor: ConcreteExecutor<UtConcreteExecutionResult, UtExecutionInstrumentation>,
35+
val classUnderTest: ClassId,
36+
val idGenerator: IdentityPreservingIdGenerator<Int>,
37+
val fuzzingStartTimeMillis: Long,
38+
val fuzzingEndTimeMillis: Long,
39+
val mockStrategy: MockStrategy,
40+
)
3541
}

utbot-framework/src/main/kotlin/org/utbot/framework/context/JavaFuzzingContext.kt

+4-1
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,8 @@ interface JavaFuzzingContext {
2121
executableToCall: ExecutableId,
2222
): EnvironmentModels
2323

24-
fun handleFuzzedConcreteExecutionResult(concreteExecutionResult: UtConcreteExecutionResult)
24+
fun handleFuzzedConcreteExecutionResult(
25+
methodUnderTest: ExecutableId,
26+
concreteExecutionResult: UtConcreteExecutionResult,
27+
)
2528
}

utbot-framework/src/main/kotlin/org/utbot/framework/context/custom/MockingJavaFuzzingContext.kt

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.utbot.framework.context.custom
22

33
import org.utbot.framework.context.JavaFuzzingContext
4+
import org.utbot.framework.plugin.api.ExecutableId
45
import org.utbot.fuzzing.JavaValueProvider
56
import org.utbot.fuzzing.providers.MapValueProvider
67
import org.utbot.fuzzing.spring.unit.MockValueProvider
@@ -37,6 +38,11 @@ class MockingJavaFuzzingContext(
3738
.with(NullValueProvider)
3839
)
3940

40-
override fun handleFuzzedConcreteExecutionResult(concreteExecutionResult: UtConcreteExecutionResult) =
41+
override fun handleFuzzedConcreteExecutionResult(
42+
methodUnderTest: ExecutableId,
43+
concreteExecutionResult: UtConcreteExecutionResult
44+
) {
45+
delegateContext.handleFuzzedConcreteExecutionResult(methodUnderTest, concreteExecutionResult)
4146
mockValueProvider.addMockingCandidates(concreteExecutionResult.detectedMockingCandidates)
47+
}
4248
}

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

+3-7
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
package org.utbot.framework.context.simple
22

33
import org.utbot.framework.context.ConcreteExecutionContext
4+
import org.utbot.framework.context.ConcreteExecutionContext.FuzzingContextParams
45
import org.utbot.framework.context.JavaFuzzingContext
5-
import org.utbot.framework.plugin.api.ClassId
66
import org.utbot.framework.plugin.api.ConcreteContextLoadingResult
77
import org.utbot.framework.plugin.api.ExecutableId
88
import org.utbot.framework.plugin.api.UtExecution
9-
import org.utbot.fuzzer.IdentityPreservingIdGenerator
109
import org.utbot.instrumentation.ConcreteExecutor
1110
import org.utbot.instrumentation.instrumentation.execution.SimpleUtExecutionInstrumentation
1211
import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult
@@ -32,9 +31,6 @@ class SimpleConcreteExecutionContext(fullClassPath: String) : ConcreteExecutionC
3231
rerunExecutor: ConcreteExecutor<UtConcreteExecutionResult, UtExecutionInstrumentation>,
3332
): List<UtExecution> = executions
3433

35-
override fun tryCreateFuzzingContext(
36-
concreteExecutor: ConcreteExecutor<UtConcreteExecutionResult, UtExecutionInstrumentation>,
37-
classUnderTest: ClassId,
38-
idGenerator: IdentityPreservingIdGenerator<Int>
39-
): JavaFuzzingContext = SimpleJavaFuzzingContext(classUnderTest, idGenerator)
34+
override fun tryCreateFuzzingContext(params: FuzzingContextParams): JavaFuzzingContext =
35+
SimpleJavaFuzzingContext(params.classUnderTest, params.idGenerator)
4036
}

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

+4-2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ class SimpleJavaFuzzingContext(
3131
executableToCall = executableToCall
3232
)
3333

34-
override fun handleFuzzedConcreteExecutionResult(concreteExecutionResult: UtConcreteExecutionResult) =
35-
Unit
34+
override fun handleFuzzedConcreteExecutionResult(
35+
methodUnderTest: ExecutableId,
36+
concreteExecutionResult: UtConcreteExecutionResult
37+
) = Unit
3638
}

utbot-framework/src/main/kotlin/org/utbot/framework/context/utils/ConcreteExecutionContextUtils.kt

+4-12
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,15 @@
11
package org.utbot.framework.context.utils
22

33
import org.utbot.framework.context.ConcreteExecutionContext
4+
import org.utbot.framework.context.ConcreteExecutionContext.FuzzingContextParams
45
import org.utbot.framework.context.JavaFuzzingContext
5-
import org.utbot.framework.plugin.api.ClassId
6-
import org.utbot.fuzzer.IdentityPreservingIdGenerator
76
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
117

128
fun ConcreteExecutionContext.transformJavaFuzzingContext(
13-
transformer: (JavaFuzzingContext) -> JavaFuzzingContext
9+
transformer: FuzzingContextParams.(JavaFuzzingContext) -> JavaFuzzingContext
1410
) = object : ConcreteExecutionContext by this {
15-
override fun tryCreateFuzzingContext(
16-
concreteExecutor: ConcreteExecutor<UtConcreteExecutionResult, UtExecutionInstrumentation>,
17-
classUnderTest: ClassId,
18-
idGenerator: IdentityPreservingIdGenerator<Int>
19-
): JavaFuzzingContext = transformer(
20-
this@transformJavaFuzzingContext.tryCreateFuzzingContext(concreteExecutor, classUnderTest, idGenerator)
11+
override fun tryCreateFuzzingContext(params: FuzzingContextParams): JavaFuzzingContext = params.transformer(
12+
this@transformJavaFuzzingContext.tryCreateFuzzingContext(params)
2113
)
2214
}
2315

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

+36-12
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ package org.utbot.framework.context.spring
22

33
import mu.KotlinLogging
44
import org.utbot.framework.context.ConcreteExecutionContext
5+
import org.utbot.framework.context.ConcreteExecutionContext.FuzzingContextParams
56
import org.utbot.framework.context.JavaFuzzingContext
6-
import org.utbot.framework.plugin.api.ClassId
77
import org.utbot.framework.plugin.api.ConcreteContextLoadingResult
8+
import org.utbot.framework.plugin.api.ExecutableId
89
import org.utbot.framework.plugin.api.SpringSettings
9-
import org.utbot.fuzzer.IdentityPreservingIdGenerator
10+
import org.utbot.framework.plugin.api.UtExecution
11+
import org.utbot.framework.plugin.api.isSuccess
12+
import org.utbot.framework.plugin.api.util.SpringModelUtils
1013
import org.utbot.instrumentation.ConcreteExecutor
1114
import org.utbot.instrumentation.getRelevantSpringRepositories
1215
import org.utbot.instrumentation.instrumentation.execution.RemovingConstructFailsUtExecutionInstrumentation
@@ -49,25 +52,46 @@ class SpringIntegrationTestConcreteExecutionContext(
4952
}
5053
}
5154

52-
override fun tryCreateFuzzingContext(
53-
concreteExecutor: ConcreteExecutor<UtConcreteExecutionResult, UtExecutionInstrumentation>,
54-
classUnderTest: ClassId,
55-
idGenerator: IdentityPreservingIdGenerator<Int>
56-
): JavaFuzzingContext {
57-
if (springApplicationContext.getBeansAssignableTo(classUnderTest).isEmpty())
55+
override fun tryCreateFuzzingContext(params: FuzzingContextParams): JavaFuzzingContext {
56+
if (springApplicationContext.getBeansAssignableTo(params.classUnderTest).isEmpty())
5857
error(
59-
"No beans of type ${classUnderTest.name} are found. " +
58+
"No beans of type ${params.classUnderTest} are found. " +
6059
"Try choosing different Spring configuration or adding beans to " +
6160
springSettings.configuration.fullDisplayName
6261
)
6362

64-
val relevantRepositories = concreteExecutor.getRelevantSpringRepositories(classUnderTest)
65-
logger.info { "Detected relevant repositories for class $classUnderTest: $relevantRepositories" }
63+
val relevantRepositories = params.concreteExecutor.getRelevantSpringRepositories(params.classUnderTest)
64+
logger.info { "Detected relevant repositories for class ${params.classUnderTest}: $relevantRepositories" }
6665

6766
return SpringIntegrationTestJavaFuzzingContext(
68-
delegateContext = delegateContext.tryCreateFuzzingContext(concreteExecutor, classUnderTest, idGenerator),
67+
delegateContext = delegateContext.tryCreateFuzzingContext(params),
6968
relevantRepositories = relevantRepositories,
7069
springApplicationContext = springApplicationContext,
70+
fuzzingStartTimeMillis = params.fuzzingStartTimeMillis,
71+
fuzzingEndTimeMillis = params.fuzzingEndTimeMillis,
7172
)
7273
}
74+
75+
override fun transformExecutionsBeforeMinimization(
76+
executions: List<UtExecution>,
77+
methodUnderTest: ExecutableId
78+
): List<UtExecution> {
79+
val (mockMvcExecutions, regularExecutions) =
80+
delegateContext.transformExecutionsBeforeMinimization(executions, methodUnderTest)
81+
.partition { it.executableToCall == SpringModelUtils.mockMvcPerformMethodId }
82+
83+
val classUnderTestName = methodUnderTest.classId.name
84+
val methodUnderTestSignature = methodUnderTest.signature
85+
86+
fun UtExecution.getMethodUnderTestCoverage(): List<Long>? =
87+
coverage?.coveredInstructions?.filter {
88+
it.className == classUnderTestName && it.methodSignature == methodUnderTestSignature
89+
}?.map { it.id }
90+
91+
fun UtExecution.getKey() = getMethodUnderTestCoverage()?.let { it to result.isSuccess }
92+
93+
val mockMvcExecutionKeys = mockMvcExecutions.mapNotNullTo(mutableSetOf()) { it.getKey() }
94+
95+
return mockMvcExecutions + regularExecutions.filter { it.getKey() !in mockMvcExecutionKeys }
96+
}
7397
}

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

+28-3
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@ import org.utbot.framework.plugin.api.ExecutableId
99
import org.utbot.framework.plugin.api.FieldId
1010
import org.utbot.framework.plugin.api.MethodId
1111
import org.utbot.framework.plugin.api.SpringRepositoryId
12+
import org.utbot.framework.plugin.api.UtExecutionSuccess
1213
import org.utbot.framework.plugin.api.UtModel
14+
import org.utbot.framework.plugin.api.UtSpringMockMvcResultActionsModel
1315
import org.utbot.framework.plugin.api.util.SpringModelUtils
16+
import org.utbot.framework.plugin.api.util.SpringModelUtils.allControllerParametersAreSupported
1417
import org.utbot.framework.plugin.api.util.allDeclaredFieldIds
1518
import org.utbot.framework.plugin.api.util.jField
1619
import org.utbot.framework.plugin.api.util.utContext
@@ -27,11 +30,14 @@ import org.utbot.fuzzing.spring.valid.EmailValueProvider
2730
import org.utbot.fuzzing.spring.valid.NotBlankStringValueProvider
2831
import org.utbot.fuzzing.spring.valid.NotEmptyStringValueProvider
2932
import org.utbot.fuzzing.spring.valid.ValidEntityValueProvider
33+
import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult
3034

3135
class SpringIntegrationTestJavaFuzzingContext(
32-
val delegateContext: JavaFuzzingContext,
36+
private val delegateContext: JavaFuzzingContext,
3337
relevantRepositories: Set<SpringRepositoryId>,
3438
springApplicationContext: SpringApplicationContext,
39+
private val fuzzingStartTimeMillis: Long,
40+
private val fuzzingEndTimeMillis: Long,
3541
) : JavaFuzzingContext by delegateContext {
3642
companion object {
3743
private val logger = KotlinLogging.logger {}
@@ -93,11 +99,13 @@ class SpringIntegrationTestJavaFuzzingContext(
9399
})
94100
}
95101

102+
private val methodsSuccessfullyCalledViaMockMvc = mutableSetOf<ExecutableId>()
103+
96104
override fun createStateBefore(
97105
thisInstance: UtModel?,
98106
parameters: List<UtModel>,
99107
statics: Map<FieldId, UtModel>,
100-
executableToCall: ExecutableId,
108+
executableToCall: ExecutableId
101109
): EnvironmentModels {
102110
val delegateStateBefore = delegateContext.createStateBefore(thisInstance, parameters, statics, executableToCall)
103111
return when (executableToCall) {
@@ -107,7 +115,13 @@ class SpringIntegrationTestJavaFuzzingContext(
107115
methodId = executableToCall,
108116
arguments = parameters,
109117
idGenerator = { idGenerator.createId() }
110-
) ?: return delegateStateBefore
118+
)?.takeIf {
119+
val halfOfFuzzingTimePassed = System.currentTimeMillis() > (fuzzingStartTimeMillis + fuzzingEndTimeMillis) / 2
120+
val allParamsSupported = allControllerParametersAreSupported(executableToCall)
121+
val successfullyCalledViaMockMvc = executableToCall in methodsSuccessfullyCalledViaMockMvc
122+
!halfOfFuzzingTimePassed || allParamsSupported && successfullyCalledViaMockMvc
123+
} ?: return delegateStateBefore
124+
111125
delegateStateBefore.copy(
112126
thisInstance = SpringModelUtils.createMockMvcModel(controller = thisInstance) { idGenerator.createId() },
113127
parameters = listOf(requestBuilderModel),
@@ -116,4 +130,15 @@ class SpringIntegrationTestJavaFuzzingContext(
116130
}
117131
}
118132
}
133+
134+
override fun handleFuzzedConcreteExecutionResult(
135+
methodUnderTest: ExecutableId,
136+
concreteExecutionResult: UtConcreteExecutionResult
137+
) {
138+
delegateContext.handleFuzzedConcreteExecutionResult(methodUnderTest, concreteExecutionResult)
139+
((concreteExecutionResult.result as? UtExecutionSuccess)?.model as? UtSpringMockMvcResultActionsModel)?.let {
140+
if (it.status < 400)
141+
methodsSuccessfullyCalledViaMockMvc.add(methodUnderTest)
142+
}
143+
}
119144
}

0 commit comments

Comments
 (0)