Skip to content

Commit a3f8d90

Browse files
Introduce fuzzing in Spring unit tests #2321 (#2529)
* Extract `JavaFuzzingContext` into separate interface * Add mock and inject mocks value providers * Add `StateBeforeAwareIdGenerator` * Properly handle `canHaveRedundantOrMissingMocks` in instrumentation * Properly create `void` models (for mocking `void` methods) * Add utils to ease configuring `ApplicationContext` * Configure Spring unit tests to use fuzzer with mocks * Refactor to only transform `JavaValueProvider` via `applicationContext` (avoid redundant lambdas) * Fix test compilation * Improve default mock answers of unmockable types (arrays and sealed interfaces) * Set default fuzzing value to 0.3 for Spring * Avoid creating too deep dynamic mocks * Avoid recording multiple mock answers if same answer is reused over and over again * Fix JS compilation --------- Co-authored-by: Egor Kulikov <[email protected]>
1 parent ca7df7c commit a3f8d90

File tree

35 files changed

+776
-201
lines changed

35 files changed

+776
-201
lines changed

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

+13-2
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,8 @@ data class UtClassRefModel(
498498
* - isMock flag
499499
* - calculated field values (models)
500500
* - mocks for methods with return values
501+
* - [canHaveRedundantOrMissingMocks] flag, which is set to `true` for mocks
502+
* created by fuzzer without knowing which methods will actually be called
501503
*
502504
* [fields] contains non-static fields
503505
*/
@@ -507,6 +509,7 @@ data class UtCompositeModel(
507509
val isMock: Boolean,
508510
val fields: MutableMap<FieldId, UtModel> = mutableMapOf(),
509511
val mocks: MutableMap<ExecutableId, List<UtModel>> = mutableMapOf(),
512+
val canHaveRedundantOrMissingMocks: Boolean = false,
510513
) : UtReferenceModel(id, classId) {
511514
//TODO: SAT-891 - rewrite toString() method
512515
override fun toString() = withToStringThreadLocalReentrancyGuard {
@@ -763,13 +766,17 @@ abstract class UtCustomModel(
763766
classId: ClassId,
764767
modelName: String = id.toString(),
765768
override val origin: UtCompositeModel? = null,
766-
) : UtModelWithCompositeOrigin(id, classId, modelName, origin)
769+
) : UtModelWithCompositeOrigin(id, classId, modelName, origin) {
770+
abstract val dependencies: Collection<UtModel>
771+
}
767772

768773
object UtSpringContextModel : UtCustomModel(
769774
id = null,
770775
classId = SpringModelUtils.applicationContextClassId,
771776
modelName = "applicationContext"
772777
) {
778+
override val dependencies: Collection<UtModel> get() = emptySet()
779+
773780
// NOTE that overriding equals is required just because without it
774781
// we will lose equality for objects after deserialization
775782
override fun equals(other: Any?): Boolean = other is UtSpringContextModel
@@ -782,6 +789,8 @@ class UtSpringEntityManagerModel : UtCustomModel(
782789
classId = SpringModelUtils.entityManagerClassIds.first(),
783790
modelName = "entityManager"
784791
) {
792+
override val dependencies: Collection<UtModel> get() = emptySet()
793+
785794
// NOTE that overriding equals is required just because without it
786795
// we will lose equality for objects after deserialization
787796
override fun equals(other: Any?): Boolean = other is UtSpringEntityManagerModel
@@ -810,7 +819,9 @@ data class UtSpringMockMvcResultActionsModel(
810819
classId = SpringModelUtils.resultActionsClassId,
811820
id = id,
812821
modelName = "mockMvcResultActions@$id"
813-
)
822+
) {
823+
override val dependencies: Collection<UtModel> get() = emptySet()
824+
}
814825

815826
/**
816827
* Model for a step to obtain [UtAssembleModel].

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

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import org.utbot.framework.plugin.api.MethodId
1111
import org.utbot.framework.plugin.api.UtModel
1212
import org.utbot.framework.plugin.api.UtNullModel
1313
import org.utbot.framework.plugin.api.UtPrimitiveModel
14+
import org.utbot.framework.plugin.api.UtVoidModel
1415
import org.utbot.framework.plugin.api.id
1516
import soot.SootField
1617
import java.lang.reflect.Constructor
@@ -449,6 +450,7 @@ fun ClassId.defaultValueModel(): UtModel = when (this) {
449450
doubleClassId -> UtPrimitiveModel(0.0)
450451
booleanClassId -> UtPrimitiveModel(false)
451452
charClassId -> UtPrimitiveModel('\u0000')
453+
voidClassId -> UtVoidModel
452454
else -> UtNullModel(this)
453455
}
454456

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

+8-7
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ class UtBotSymbolicEngine(
425425
* @param until is used by fuzzer to cancel all tasks if the current time is over this value
426426
* @param transform provides model values for a method
427427
*/
428-
fun fuzzing(until: Long = Long.MAX_VALUE, transform: (JavaValueProvider) -> JavaValueProvider = { it }) = flow {
428+
fun fuzzing(until: Long = Long.MAX_VALUE) = flow {
429429
val isFuzzable = methodUnderTest.parameters.all { classId ->
430430
classId != Method::class.java.id && // causes the instrumented process crash at invocation
431431
classId != Class::class.java.id // causes java.lang.IllegalAccessException: java.lang.Class at sun.misc.Unsafe.allocateInstance(Native Method)
@@ -439,12 +439,12 @@ class UtBotSymbolicEngine(
439439
val names = graph.body.method.tags.filterIsInstance<ParamNamesTag>().firstOrNull()?.names ?: emptyList()
440440
var testEmittedByFuzzer = 0
441441

442-
val valueProviders = try {
443-
concreteExecutionContext.tryCreateValueProvider(concreteExecutor, classUnderTest, defaultIdGenerator)
442+
val fuzzingContext = try {
443+
concreteExecutionContext.tryCreateFuzzingContext(concreteExecutor, classUnderTest, defaultIdGenerator)
444444
} catch (e: Exception) {
445445
emit(UtError(e.message ?: "Failed to create ValueProvider", e))
446446
return@flow
447-
}.let(transform)
447+
}
448448

449449
val coverageToMinStateBeforeSize = mutableMapOf<Trie.Node<Instruction>, Int>()
450450

@@ -453,7 +453,7 @@ class UtBotSymbolicEngine(
453453
methodUnderTest,
454454
constants = collectConstantsForFuzzer(graph),
455455
names = names,
456-
providers = listOf(valueProviders),
456+
providers = listOf(fuzzingContext.valueProvider),
457457
) { thisInstance, descr, values ->
458458
val diff = until - System.currentTimeMillis()
459459
val thresholdMillisForFuzzingOperation = 0 // may be better use 10-20 millis as it might not be possible
@@ -474,12 +474,11 @@ class UtBotSymbolicEngine(
474474
return@runJavaFuzzing BaseFeedback(Trie.emptyNode(), Control.PASS)
475475
}
476476

477-
val stateBefore = concreteExecutionContext.createStateBefore(
477+
val stateBefore = fuzzingContext.createStateBefore(
478478
thisInstance = thisInstance?.model,
479479
parameters = values.map { it.model },
480480
statics = emptyMap(),
481481
executableToCall = methodUnderTest,
482-
idGenerator = defaultIdGenerator
483482
)
484483

485484
val concreteExecutionResult: UtConcreteExecutionResult? = try {
@@ -496,6 +495,8 @@ class UtBotSymbolicEngine(
496495
// in case an exception occurred from the concrete execution
497496
concreteExecutionResult ?: return@runJavaFuzzing BaseFeedback(result = Trie.emptyNode(), control = Control.PASS)
498497

498+
fuzzingContext.handleFuzzedConcreteExecutionResult(concreteExecutionResult)
499+
499500
// in case of processed failure in the concrete execution
500501
concreteExecutionResult.processedFailure()?.let { failure ->
501502
logger.debug { "Instrumented process failed with exception ${failure.exception} before concrete execution started" }

utbot-framework/src/main/kotlin/org/utbot/external/api/UtBotJavaApi.kt

+12-6
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import org.utbot.framework.codegen.domain.TestFramework
1212
import org.utbot.framework.codegen.generator.CodeGenerator
1313
import org.utbot.framework.codegen.generator.CodeGeneratorParams
1414
import org.utbot.framework.codegen.services.language.CgLanguageAssistant
15+
import org.utbot.framework.context.simple.SimpleApplicationContext
16+
import org.utbot.framework.context.utils.transformValueProvider
1517
import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionData
1618
import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult
1719
import org.utbot.instrumentation.instrumentation.execution.UtExecutionInstrumentation
@@ -181,19 +183,23 @@ object UtBotJavaApi {
181183

182184
return withUtContext(UtContext(classUnderTest.classLoader)) {
183185
val buildPath = FileUtil.isolateClassFiles(classUnderTest).toPath()
184-
TestCaseGenerator(listOf(buildPath), classpath, dependencyClassPath, jdkInfo = JdkInfoDefaultProvider().info)
186+
TestCaseGenerator(
187+
listOf(buildPath),
188+
classpath,
189+
dependencyClassPath,
190+
jdkInfo = JdkInfoDefaultProvider().info,
191+
applicationContext = SimpleApplicationContext().transformValueProvider { defaultModelProvider ->
192+
customModelProvider.withFallback(defaultModelProvider)
193+
}
194+
)
185195
.generate(
186196
methodsForAutomaticGeneration.map {
187197
it.methodToBeTestedFromUserInput.executableId
188198
},
189199
mockStrategyApi,
190200
chosenClassesToMockAlways = emptySet(),
191201
generationTimeoutInMillis,
192-
generate = { symbolicEngine ->
193-
symbolicEngine.fuzzing { defaultModelProvider ->
194-
customModelProvider.withFallback(defaultModelProvider)
195-
}
196-
}
202+
generate = { symbolicEngine -> symbolicEngine.fuzzing() }
197203
)
198204
}.toMutableList()
199205
}

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

+2-16
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,8 @@ package org.utbot.framework.context
22

33
import org.utbot.framework.plugin.api.ClassId
44
import org.utbot.framework.plugin.api.ConcreteContextLoadingResult
5-
import org.utbot.framework.plugin.api.EnvironmentModels
6-
import org.utbot.framework.plugin.api.ExecutableId
7-
import org.utbot.framework.plugin.api.FieldId
85
import org.utbot.framework.plugin.api.UtExecution
9-
import org.utbot.framework.plugin.api.UtModel
10-
import org.utbot.fuzzer.IdGenerator
116
import org.utbot.fuzzer.IdentityPreservingIdGenerator
12-
import org.utbot.fuzzing.JavaValueProvider
137
import org.utbot.instrumentation.ConcreteExecutor
148
import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult
159
import org.utbot.instrumentation.instrumentation.execution.UtExecutionInstrumentation
@@ -26,17 +20,9 @@ interface ConcreteExecutionContext {
2620
classUnderTestId: ClassId
2721
): List<UtExecution>
2822

29-
fun tryCreateValueProvider(
23+
fun tryCreateFuzzingContext(
3024
concreteExecutor: ConcreteExecutor<UtConcreteExecutionResult, UtExecutionInstrumentation>,
3125
classUnderTest: ClassId,
3226
idGenerator: IdentityPreservingIdGenerator<Int>,
33-
): JavaValueProvider
34-
35-
fun createStateBefore(
36-
thisInstance: UtModel?,
37-
parameters: List<UtModel>,
38-
statics: Map<FieldId, UtModel>,
39-
executableToCall: ExecutableId,
40-
idGenerator: IdGenerator<Int>
41-
): EnvironmentModels
27+
): JavaFuzzingContext
4228
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package org.utbot.framework.context
2+
3+
import org.utbot.framework.plugin.api.ClassId
4+
import org.utbot.framework.plugin.api.EnvironmentModels
5+
import org.utbot.framework.plugin.api.ExecutableId
6+
import org.utbot.framework.plugin.api.FieldId
7+
import org.utbot.framework.plugin.api.UtModel
8+
import org.utbot.fuzzer.IdentityPreservingIdGenerator
9+
import org.utbot.fuzzing.JavaValueProvider
10+
import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult
11+
12+
interface JavaFuzzingContext {
13+
val classUnderTest: ClassId
14+
val idGenerator: IdentityPreservingIdGenerator<Int>
15+
val valueProvider: JavaValueProvider
16+
17+
fun createStateBefore(
18+
thisInstance: UtModel?,
19+
parameters: List<UtModel>,
20+
statics: Map<FieldId, UtModel>,
21+
executableToCall: ExecutableId,
22+
): EnvironmentModels
23+
24+
fun handleFuzzedConcreteExecutionResult(concreteExecutionResult: UtConcreteExecutionResult)
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package org.utbot.framework.context.custom
2+
3+
import org.utbot.framework.context.JavaFuzzingContext
4+
import org.utbot.fuzzing.JavaValueProvider
5+
import org.utbot.fuzzing.providers.MapValueProvider
6+
import org.utbot.fuzzing.spring.unit.MockValueProvider
7+
import org.utbot.fuzzing.providers.NullValueProvider
8+
import org.utbot.fuzzing.providers.ObjectValueProvider
9+
import org.utbot.fuzzing.providers.StringValueProvider
10+
import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult
11+
12+
/**
13+
* Makes fuzzer mock all types that don't have *specific* [JavaValueProvider],
14+
* like [MapValueProvider] or [StringValueProvider].
15+
*
16+
* NOTE: the caller is responsible for providing some *specific* [JavaValueProvider]
17+
* that can create values for class under test (otherwise it will be mocked),
18+
* [ObjectValueProvider] and [NullValueProvider] do not count as *specific*.
19+
*/
20+
fun JavaFuzzingContext.mockAllTypesWithoutSpecificValueProvider() =
21+
MockingJavaFuzzingContext(delegateContext = this)
22+
23+
class MockingJavaFuzzingContext(
24+
val delegateContext: JavaFuzzingContext
25+
) : JavaFuzzingContext by delegateContext {
26+
private val mockValueProvider = MockValueProvider(delegateContext.idGenerator)
27+
28+
override val valueProvider: JavaValueProvider =
29+
// NOTE: we first remove `NullValueProvider` from `delegateContext.valueProvider` and then
30+
// add it back as a part of our `withFallback` so it has the same priority as
31+
// `mockValueProvider`, otherwise mocks will never be used where `null` can be used.
32+
delegateContext.valueProvider
33+
.except { it is NullValueProvider }
34+
.except { it is ObjectValueProvider }
35+
.withFallback(
36+
mockValueProvider
37+
.with(NullValueProvider)
38+
)
39+
40+
override fun handleFuzzedConcreteExecutionResult(concreteExecutionResult: UtConcreteExecutionResult) =
41+
mockValueProvider.addMockingCandidates(concreteExecutionResult.detectedMockingCandidates)
42+
}

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

+4-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ import org.utbot.framework.context.TypeReplacer
1313
* A context to use when no specific data is required.
1414
*/
1515
class SimpleApplicationContext(
16-
override val mockerContext: MockerContext,
16+
override val mockerContext: MockerContext = SimpleMockerContext(
17+
mockFrameworkInstalled = true,
18+
staticsMockingIsConfigured = true
19+
),
1720
override val typeReplacer: TypeReplacer = SimpleTypeReplacer(),
1821
override val nonNullSpeculator: NonNullSpeculator = SimpleNonNullSpeculator()
1922
) : ApplicationContext {
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,11 @@
11
package org.utbot.framework.context.simple
22

33
import org.utbot.framework.context.ConcreteExecutionContext
4+
import org.utbot.framework.context.JavaFuzzingContext
45
import org.utbot.framework.plugin.api.ClassId
56
import org.utbot.framework.plugin.api.ConcreteContextLoadingResult
6-
import org.utbot.framework.plugin.api.EnvironmentModels
7-
import org.utbot.framework.plugin.api.ExecutableId
8-
import org.utbot.framework.plugin.api.FieldId
97
import org.utbot.framework.plugin.api.UtExecution
10-
import org.utbot.framework.plugin.api.UtModel
11-
import org.utbot.fuzzer.IdGenerator
128
import org.utbot.fuzzer.IdentityPreservingIdGenerator
13-
import org.utbot.fuzzing.JavaValueProvider
14-
import org.utbot.fuzzing.ValueProvider
15-
import org.utbot.fuzzing.defaultValueProviders
169
import org.utbot.instrumentation.ConcreteExecutor
1710
import org.utbot.instrumentation.instrumentation.execution.SimpleUtExecutionInstrumentation
1811
import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult
@@ -32,22 +25,9 @@ class SimpleConcreteExecutionContext(fullClassPath: String) : ConcreteExecutionC
3225
classUnderTestId: ClassId
3326
): List<UtExecution> = executions
3427

35-
override fun tryCreateValueProvider(
28+
override fun tryCreateFuzzingContext(
3629
concreteExecutor: ConcreteExecutor<UtConcreteExecutionResult, UtExecutionInstrumentation>,
3730
classUnderTest: ClassId,
3831
idGenerator: IdentityPreservingIdGenerator<Int>
39-
): JavaValueProvider = ValueProvider.of(defaultValueProviders(idGenerator))
40-
41-
override fun createStateBefore(
42-
thisInstance: UtModel?,
43-
parameters: List<UtModel>,
44-
statics: Map<FieldId, UtModel>,
45-
executableToCall: ExecutableId,
46-
idGenerator: IdGenerator<Int>
47-
): EnvironmentModels = EnvironmentModels(
48-
thisInstance = thisInstance,
49-
parameters = parameters,
50-
statics = statics,
51-
executableToCall = executableToCall
52-
)
32+
): JavaFuzzingContext = SimpleJavaFuzzingContext(classUnderTest, idGenerator)
5333
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package org.utbot.framework.context.simple
2+
3+
import org.utbot.framework.context.JavaFuzzingContext
4+
import org.utbot.framework.plugin.api.ClassId
5+
import org.utbot.framework.plugin.api.EnvironmentModels
6+
import org.utbot.framework.plugin.api.ExecutableId
7+
import org.utbot.framework.plugin.api.FieldId
8+
import org.utbot.framework.plugin.api.UtModel
9+
import org.utbot.fuzzer.IdentityPreservingIdGenerator
10+
import org.utbot.fuzzing.JavaValueProvider
11+
import org.utbot.fuzzing.ValueProvider
12+
import org.utbot.fuzzing.defaultValueProviders
13+
import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult
14+
15+
class SimpleJavaFuzzingContext(
16+
override val classUnderTest: ClassId,
17+
override val idGenerator: IdentityPreservingIdGenerator<Int>,
18+
) : JavaFuzzingContext {
19+
override val valueProvider: JavaValueProvider =
20+
ValueProvider.of(defaultValueProviders(idGenerator))
21+
22+
override fun createStateBefore(
23+
thisInstance: UtModel?,
24+
parameters: List<UtModel>,
25+
statics: Map<FieldId, UtModel>,
26+
executableToCall: ExecutableId,
27+
): EnvironmentModels = EnvironmentModels(
28+
thisInstance = thisInstance,
29+
parameters = parameters,
30+
statics = statics,
31+
executableToCall = executableToCall
32+
)
33+
34+
override fun handleFuzzedConcreteExecutionResult(concreteExecutionResult: UtConcreteExecutionResult) =
35+
Unit
36+
}

0 commit comments

Comments
 (0)