Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce fuzzing in Spring unit tests #2529

Merged
merged 14 commits into from
Aug 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,8 @@ data class UtClassRefModel(
* - isMock flag
* - calculated field values (models)
* - mocks for methods with return values
* - [canHaveRedundantOrMissingMocks] flag, which is set to `true` for mocks
* created by fuzzer without knowing which methods will actually be called
*
* [fields] contains non-static fields
*/
Expand All @@ -507,6 +509,7 @@ data class UtCompositeModel(
val isMock: Boolean,
val fields: MutableMap<FieldId, UtModel> = mutableMapOf(),
val mocks: MutableMap<ExecutableId, List<UtModel>> = mutableMapOf(),
val canHaveRedundantOrMissingMocks: Boolean = false,
) : UtReferenceModel(id, classId) {
//TODO: SAT-891 - rewrite toString() method
override fun toString() = withToStringThreadLocalReentrancyGuard {
Expand Down Expand Up @@ -763,13 +766,17 @@ abstract class UtCustomModel(
classId: ClassId,
modelName: String = id.toString(),
override val origin: UtCompositeModel? = null,
) : UtModelWithCompositeOrigin(id, classId, modelName, origin)
) : UtModelWithCompositeOrigin(id, classId, modelName, origin) {
abstract val dependencies: Collection<UtModel>
}

object UtSpringContextModel : UtCustomModel(
id = null,
classId = SpringModelUtils.applicationContextClassId,
modelName = "applicationContext"
) {
override val dependencies: Collection<UtModel> get() = emptySet()

// NOTE that overriding equals is required just because without it
// we will lose equality for objects after deserialization
override fun equals(other: Any?): Boolean = other is UtSpringContextModel
Expand All @@ -782,6 +789,8 @@ class UtSpringEntityManagerModel : UtCustomModel(
classId = SpringModelUtils.entityManagerClassIds.first(),
modelName = "entityManager"
) {
override val dependencies: Collection<UtModel> get() = emptySet()

// NOTE that overriding equals is required just because without it
// we will lose equality for objects after deserialization
override fun equals(other: Any?): Boolean = other is UtSpringEntityManagerModel
Expand Down Expand Up @@ -810,7 +819,9 @@ data class UtSpringMockMvcResultActionsModel(
classId = SpringModelUtils.resultActionsClassId,
id = id,
modelName = "mockMvcResultActions@$id"
)
) {
override val dependencies: Collection<UtModel> get() = emptySet()
}

/**
* Model for a step to obtain [UtAssembleModel].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import org.utbot.framework.plugin.api.MethodId
import org.utbot.framework.plugin.api.UtModel
import org.utbot.framework.plugin.api.UtNullModel
import org.utbot.framework.plugin.api.UtPrimitiveModel
import org.utbot.framework.plugin.api.UtVoidModel
import org.utbot.framework.plugin.api.id
import soot.SootField
import java.lang.reflect.Constructor
Expand Down Expand Up @@ -449,6 +450,7 @@ fun ClassId.defaultValueModel(): UtModel = when (this) {
doubleClassId -> UtPrimitiveModel(0.0)
booleanClassId -> UtPrimitiveModel(false)
charClassId -> UtPrimitiveModel('\u0000')
voidClassId -> UtVoidModel
else -> UtNullModel(this)
}

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

val valueProviders = try {
concreteExecutionContext.tryCreateValueProvider(concreteExecutor, classUnderTest, defaultIdGenerator)
val fuzzingContext = try {
concreteExecutionContext.tryCreateFuzzingContext(concreteExecutor, classUnderTest, defaultIdGenerator)
} catch (e: Exception) {
emit(UtError(e.message ?: "Failed to create ValueProvider", e))
return@flow
}.let(transform)
}

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

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

val stateBefore = concreteExecutionContext.createStateBefore(
val stateBefore = fuzzingContext.createStateBefore(
thisInstance = thisInstance?.model,
parameters = values.map { it.model },
statics = emptyMap(),
executableToCall = methodUnderTest,
idGenerator = defaultIdGenerator
)

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

fuzzingContext.handleFuzzedConcreteExecutionResult(concreteExecutionResult)

// in case of processed failure in the concrete execution
concreteExecutionResult.processedFailure()?.let { failure ->
logger.debug { "Instrumented process failed with exception ${failure.exception} before concrete execution started" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import org.utbot.framework.codegen.domain.TestFramework
import org.utbot.framework.codegen.generator.CodeGenerator
import org.utbot.framework.codegen.generator.CodeGeneratorParams
import org.utbot.framework.codegen.services.language.CgLanguageAssistant
import org.utbot.framework.context.simple.SimpleApplicationContext
import org.utbot.framework.context.utils.transformValueProvider
import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionData
import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult
import org.utbot.instrumentation.instrumentation.execution.UtExecutionInstrumentation
Expand Down Expand Up @@ -181,19 +183,23 @@ object UtBotJavaApi {

return withUtContext(UtContext(classUnderTest.classLoader)) {
val buildPath = FileUtil.isolateClassFiles(classUnderTest).toPath()
TestCaseGenerator(listOf(buildPath), classpath, dependencyClassPath, jdkInfo = JdkInfoDefaultProvider().info)
TestCaseGenerator(
listOf(buildPath),
classpath,
dependencyClassPath,
jdkInfo = JdkInfoDefaultProvider().info,
applicationContext = SimpleApplicationContext().transformValueProvider { defaultModelProvider ->
customModelProvider.withFallback(defaultModelProvider)
}
)
.generate(
methodsForAutomaticGeneration.map {
it.methodToBeTestedFromUserInput.executableId
},
mockStrategyApi,
chosenClassesToMockAlways = emptySet(),
generationTimeoutInMillis,
generate = { symbolicEngine ->
symbolicEngine.fuzzing { defaultModelProvider ->
customModelProvider.withFallback(defaultModelProvider)
}
}
generate = { symbolicEngine -> symbolicEngine.fuzzing() }
)
}.toMutableList()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,8 @@ package org.utbot.framework.context

import org.utbot.framework.plugin.api.ClassId
import org.utbot.framework.plugin.api.ConcreteContextLoadingResult
import org.utbot.framework.plugin.api.EnvironmentModels
import org.utbot.framework.plugin.api.ExecutableId
import org.utbot.framework.plugin.api.FieldId
import org.utbot.framework.plugin.api.UtExecution
import org.utbot.framework.plugin.api.UtModel
import org.utbot.fuzzer.IdGenerator
import org.utbot.fuzzer.IdentityPreservingIdGenerator
import org.utbot.fuzzing.JavaValueProvider
import org.utbot.instrumentation.ConcreteExecutor
import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult
import org.utbot.instrumentation.instrumentation.execution.UtExecutionInstrumentation
Expand All @@ -26,17 +20,9 @@ interface ConcreteExecutionContext {
classUnderTestId: ClassId
): List<UtExecution>

fun tryCreateValueProvider(
fun tryCreateFuzzingContext(
concreteExecutor: ConcreteExecutor<UtConcreteExecutionResult, UtExecutionInstrumentation>,
classUnderTest: ClassId,
idGenerator: IdentityPreservingIdGenerator<Int>,
): JavaValueProvider

fun createStateBefore(
thisInstance: UtModel?,
parameters: List<UtModel>,
statics: Map<FieldId, UtModel>,
executableToCall: ExecutableId,
idGenerator: IdGenerator<Int>
): EnvironmentModels
): JavaFuzzingContext
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.utbot.framework.context

import org.utbot.framework.plugin.api.ClassId
import org.utbot.framework.plugin.api.EnvironmentModels
import org.utbot.framework.plugin.api.ExecutableId
import org.utbot.framework.plugin.api.FieldId
import org.utbot.framework.plugin.api.UtModel
import org.utbot.fuzzer.IdentityPreservingIdGenerator
import org.utbot.fuzzing.JavaValueProvider
import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult

interface JavaFuzzingContext {
val classUnderTest: ClassId
val idGenerator: IdentityPreservingIdGenerator<Int>
val valueProvider: JavaValueProvider

fun createStateBefore(
thisInstance: UtModel?,
parameters: List<UtModel>,
statics: Map<FieldId, UtModel>,
executableToCall: ExecutableId,
): EnvironmentModels

fun handleFuzzedConcreteExecutionResult(concreteExecutionResult: UtConcreteExecutionResult)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.utbot.framework.context.custom

import org.utbot.framework.context.JavaFuzzingContext
import org.utbot.fuzzing.JavaValueProvider
import org.utbot.fuzzing.providers.MapValueProvider
import org.utbot.fuzzing.spring.unit.MockValueProvider
import org.utbot.fuzzing.providers.NullValueProvider
import org.utbot.fuzzing.providers.ObjectValueProvider
import org.utbot.fuzzing.providers.StringValueProvider
import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult

/**
* Makes fuzzer mock all types that don't have *specific* [JavaValueProvider],
* like [MapValueProvider] or [StringValueProvider].
*
* NOTE: the caller is responsible for providing some *specific* [JavaValueProvider]
* that can create values for class under test (otherwise it will be mocked),
* [ObjectValueProvider] and [NullValueProvider] do not count as *specific*.
*/
fun JavaFuzzingContext.mockAllTypesWithoutSpecificValueProvider() =
MockingJavaFuzzingContext(delegateContext = this)

class MockingJavaFuzzingContext(
val delegateContext: JavaFuzzingContext
) : JavaFuzzingContext by delegateContext {
private val mockValueProvider = MockValueProvider(delegateContext.idGenerator)

override val valueProvider: JavaValueProvider =
// NOTE: we first remove `NullValueProvider` from `delegateContext.valueProvider` and then
// add it back as a part of our `withFallback` so it has the same priority as
// `mockValueProvider`, otherwise mocks will never be used where `null` can be used.
delegateContext.valueProvider
.except { it is NullValueProvider }
.except { it is ObjectValueProvider }
.withFallback(
mockValueProvider
.with(NullValueProvider)
)

override fun handleFuzzedConcreteExecutionResult(concreteExecutionResult: UtConcreteExecutionResult) =
mockValueProvider.addMockingCandidates(concreteExecutionResult.detectedMockingCandidates)
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ import org.utbot.framework.context.TypeReplacer
* A context to use when no specific data is required.
*/
class SimpleApplicationContext(
override val mockerContext: MockerContext,
override val mockerContext: MockerContext = SimpleMockerContext(
mockFrameworkInstalled = true,
staticsMockingIsConfigured = true
),
override val typeReplacer: TypeReplacer = SimpleTypeReplacer(),
override val nonNullSpeculator: NonNullSpeculator = SimpleNonNullSpeculator()
) : ApplicationContext {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
package org.utbot.framework.context.simple

import org.utbot.framework.context.ConcreteExecutionContext
import org.utbot.framework.context.JavaFuzzingContext
import org.utbot.framework.plugin.api.ClassId
import org.utbot.framework.plugin.api.ConcreteContextLoadingResult
import org.utbot.framework.plugin.api.EnvironmentModels
import org.utbot.framework.plugin.api.ExecutableId
import org.utbot.framework.plugin.api.FieldId
import org.utbot.framework.plugin.api.UtExecution
import org.utbot.framework.plugin.api.UtModel
import org.utbot.fuzzer.IdGenerator
import org.utbot.fuzzer.IdentityPreservingIdGenerator
import org.utbot.fuzzing.JavaValueProvider
import org.utbot.fuzzing.ValueProvider
import org.utbot.fuzzing.defaultValueProviders
import org.utbot.instrumentation.ConcreteExecutor
import org.utbot.instrumentation.instrumentation.execution.SimpleUtExecutionInstrumentation
import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult
Expand All @@ -32,22 +25,9 @@ class SimpleConcreteExecutionContext(fullClassPath: String) : ConcreteExecutionC
classUnderTestId: ClassId
): List<UtExecution> = executions

override fun tryCreateValueProvider(
override fun tryCreateFuzzingContext(
concreteExecutor: ConcreteExecutor<UtConcreteExecutionResult, UtExecutionInstrumentation>,
classUnderTest: ClassId,
idGenerator: IdentityPreservingIdGenerator<Int>
): JavaValueProvider = ValueProvider.of(defaultValueProviders(idGenerator))

override fun createStateBefore(
thisInstance: UtModel?,
parameters: List<UtModel>,
statics: Map<FieldId, UtModel>,
executableToCall: ExecutableId,
idGenerator: IdGenerator<Int>
): EnvironmentModels = EnvironmentModels(
thisInstance = thisInstance,
parameters = parameters,
statics = statics,
executableToCall = executableToCall
)
): JavaFuzzingContext = SimpleJavaFuzzingContext(classUnderTest, idGenerator)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.utbot.framework.context.simple

import org.utbot.framework.context.JavaFuzzingContext
import org.utbot.framework.plugin.api.ClassId
import org.utbot.framework.plugin.api.EnvironmentModels
import org.utbot.framework.plugin.api.ExecutableId
import org.utbot.framework.plugin.api.FieldId
import org.utbot.framework.plugin.api.UtModel
import org.utbot.fuzzer.IdentityPreservingIdGenerator
import org.utbot.fuzzing.JavaValueProvider
import org.utbot.fuzzing.ValueProvider
import org.utbot.fuzzing.defaultValueProviders
import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult

class SimpleJavaFuzzingContext(
override val classUnderTest: ClassId,
override val idGenerator: IdentityPreservingIdGenerator<Int>,
) : JavaFuzzingContext {
override val valueProvider: JavaValueProvider =
ValueProvider.of(defaultValueProviders(idGenerator))

override fun createStateBefore(
thisInstance: UtModel?,
parameters: List<UtModel>,
statics: Map<FieldId, UtModel>,
executableToCall: ExecutableId,
): EnvironmentModels = EnvironmentModels(
thisInstance = thisInstance,
parameters = parameters,
statics = statics,
executableToCall = executableToCall
)

override fun handleFuzzedConcreteExecutionResult(concreteExecutionResult: UtConcreteExecutionResult) =
Unit
}
Loading
Loading