Skip to content

Commit

Permalink
Support mock strategies and type replacement in Spring unit test fuzzing
Browse files Browse the repository at this point in the history
  • Loading branch information
IlyaMuravjov committed Aug 28, 2023
1 parent 0587302 commit 701fb39
Show file tree
Hide file tree
Showing 19 changed files with 161 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1397,7 +1397,7 @@ class Traverser(
// from Spring bean definitions, for example), we can just create a symbolic object
// with hard constraint on the mentioned type.
val replacedClassId = when (typeReplacer.typeReplacementMode) {
KnownImplementor -> typeReplacer.replaceTypeIfNeeded(type)
KnownImplementor -> typeReplacer.replaceTypeIfNeeded(type.id)
AnyImplementor,
NoImplementors -> null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,6 @@ class UtBotSymbolicEngine(
* Run fuzzing flow.
*
* @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) = flow {
val isFuzzable = methodUnderTest.parameters.all { classId ->
Expand All @@ -440,7 +439,12 @@ class UtBotSymbolicEngine(
var testEmittedByFuzzer = 0

val fuzzingContext = try {
concreteExecutionContext.tryCreateFuzzingContext(concreteExecutor, classUnderTest, defaultIdGenerator)
concreteExecutionContext.tryCreateFuzzingContext(
concreteExecutor,
classUnderTest,
mockStrategy,
defaultIdGenerator,
)
} catch (e: Exception) {
emit(UtError(e.message ?: "Failed to create ValueProvider", e))
return@flow
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.utbot.framework.context

import org.utbot.engine.MockStrategy
import org.utbot.framework.plugin.api.ClassId
import org.utbot.framework.plugin.api.ConcreteContextLoadingResult
import org.utbot.framework.plugin.api.UtExecution
Expand All @@ -23,6 +24,7 @@ interface ConcreteExecutionContext {
fun tryCreateFuzzingContext(
concreteExecutor: ConcreteExecutor<UtConcreteExecutionResult, UtExecutionInstrumentation>,
classUnderTest: ClassId,
mockStrategy: MockStrategy,
idGenerator: IdentityPreservingIdGenerator<Int>,
): JavaFuzzingContext
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.utbot.framework.context

import org.utbot.engine.MockStrategy
import org.utbot.framework.plugin.api.ClassId
import org.utbot.framework.plugin.api.EnvironmentModels
import org.utbot.framework.plugin.api.ExecutableId
Expand All @@ -11,6 +12,7 @@ import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionRe

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package org.utbot.framework.context

import org.utbot.framework.plugin.api.ClassId
import org.utbot.framework.plugin.api.TypeReplacementMode
import soot.RefType

interface TypeReplacer {
/**
Expand All @@ -14,5 +13,5 @@ interface TypeReplacer {
* Finds a type to replace the original abstract type
* if it is guided with some additional information.
*/
fun replaceTypeIfNeeded(type: RefType): ClassId?
fun replaceTypeIfNeeded(classId: ClassId): ClassId?
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,48 @@ package org.utbot.framework.context.custom

import org.utbot.framework.context.JavaFuzzingContext
import org.utbot.fuzzing.JavaValueProvider
import org.utbot.fuzzing.providers.AnyDepthNullValueProvider
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.fuzzing.providers.anyObjectValueProvider
import org.utbot.fuzzing.spring.decorators.filterTypes
import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult

/**
* Makes fuzzer mock all types that don't have *specific* [JavaValueProvider],
* like [MapValueProvider] or [StringValueProvider].
* Allows fuzzer to use mocks in accordance with [JavaFuzzingContext.mockStrategy].
*
* 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*.
* NOTE:
* - fuzzer won't mock types, that have *specific* value providers (e.g. [MapValueProvider] and [StringValueProvider])
* - [ObjectValueProvider] and [NullValueProvider] do not count as *specific* value providers
*/
fun JavaFuzzingContext.mockAllTypesWithoutSpecificValueProvider() =
fun JavaFuzzingContext.allowMocks() =
MockingJavaFuzzingContext(delegateContext = this)

class MockingJavaFuzzingContext(
val delegateContext: JavaFuzzingContext
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.
// NOTE: we first remove `NullValueProvider` and `ObjectValueProvider` from `delegateContext.valueProvider`
// and then add them back as a part of our `withFallback` so they have the same priority as
// `mockValueProvider`, otherwise mocks will never be used where `null` or new object can be used.
delegateContext.valueProvider
.except { it is NullValueProvider }
.except { it is ObjectValueProvider }
.withFallback(
mockValueProvider
.filterTypes { type ->
mockStrategy.eligibleToMock(
classToMock = type.classId,
classUnderTest = classUnderTest
)
}
.with(anyObjectValueProvider(idGenerator))
.withFallback(mockValueProvider.with(AnyDepthNullValueProvider))
.with(NullValueProvider)
)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.utbot.framework.context.simple

import org.utbot.engine.MockStrategy
import org.utbot.framework.context.ConcreteExecutionContext
import org.utbot.framework.context.JavaFuzzingContext
import org.utbot.framework.plugin.api.ClassId
Expand Down Expand Up @@ -28,6 +29,7 @@ class SimpleConcreteExecutionContext(fullClassPath: String) : ConcreteExecutionC
override fun tryCreateFuzzingContext(
concreteExecutor: ConcreteExecutor<UtConcreteExecutionResult, UtExecutionInstrumentation>,
classUnderTest: ClassId,
mockStrategy: MockStrategy,
idGenerator: IdentityPreservingIdGenerator<Int>
): JavaFuzzingContext = SimpleJavaFuzzingContext(classUnderTest, idGenerator)
): JavaFuzzingContext = SimpleJavaFuzzingContext(classUnderTest, mockStrategy, idGenerator)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.utbot.framework.context.simple

import org.utbot.engine.MockStrategy
import org.utbot.framework.context.JavaFuzzingContext
import org.utbot.framework.plugin.api.ClassId
import org.utbot.framework.plugin.api.EnvironmentModels
Expand All @@ -14,6 +15,7 @@ import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionRe

class SimpleJavaFuzzingContext(
override val classUnderTest: ClassId,
override val mockStrategy: MockStrategy,
override val idGenerator: IdentityPreservingIdGenerator<Int>,
) : JavaFuzzingContext {
override val valueProvider: JavaValueProvider =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ package org.utbot.framework.context.simple
import org.utbot.framework.context.TypeReplacer
import org.utbot.framework.plugin.api.ClassId
import org.utbot.framework.plugin.api.TypeReplacementMode
import soot.RefType

class SimpleTypeReplacer : TypeReplacer {
override val typeReplacementMode: TypeReplacementMode = TypeReplacementMode.AnyImplementor

override fun replaceTypeIfNeeded(type: RefType): ClassId? = null
override fun replaceTypeIfNeeded(classId: ClassId): ClassId? = null
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import org.utbot.framework.context.ConcreteExecutionContext
import org.utbot.framework.context.NonNullSpeculator
import org.utbot.framework.context.TypeReplacer
import org.utbot.framework.context.custom.CoverageFilteringConcreteExecutionContext
import org.utbot.framework.context.custom.mockAllTypesWithoutSpecificValueProvider
import org.utbot.framework.context.custom.allowMocks
import org.utbot.framework.context.utils.transformJavaFuzzingContext
import org.utbot.framework.context.utils.withValueProvider
import org.utbot.framework.context.utils.transformValueProvider
import org.utbot.framework.plugin.api.BeanDefinitionData
import org.utbot.framework.plugin.api.ClassId
import org.utbot.framework.plugin.api.ConcreteContextLoadingResult
Expand All @@ -24,7 +24,9 @@ import org.utbot.framework.plugin.api.util.allSuperTypes
import org.utbot.framework.plugin.api.util.id
import org.utbot.framework.plugin.api.util.jClass
import org.utbot.framework.plugin.api.util.utContext
import org.utbot.fuzzing.spring.decorators.replaceTypes
import org.utbot.fuzzing.spring.unit.InjectMockValueProvider
import org.utbot.fuzzing.toFuzzerType

class SpringApplicationContextImpl(
private val delegateContext: ApplicationContext,
Expand Down Expand Up @@ -64,13 +66,20 @@ class SpringApplicationContextImpl(
return when (springTestType) {
SpringTestType.UNIT_TEST -> delegateConcreteExecutionContext.transformJavaFuzzingContext { fuzzingContext ->
fuzzingContext
.withValueProvider(
.allowMocks()
.transformValueProvider { origValueProvider ->
InjectMockValueProvider(
idGenerator = fuzzingContext.idGenerator,
classToUseCompositeModelFor = fuzzingContext.classUnderTest
)
)
.mockAllTypesWithoutSpecificValueProvider()
.withFallback(origValueProvider)
.replaceTypes { description, type ->
typeReplacer.replaceTypeIfNeeded(type.classId)?.let { replacement ->
// TODO infer generic type
toFuzzerType(replacement.jClass, description.typeCache)
} ?: type
}
}
}
SpringTestType.INTEGRATION_TEST -> SpringIntegrationTestConcreteExecutionContext(
delegateConcreteExecutionContext,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.utbot.framework.context.spring

import mu.KotlinLogging
import org.utbot.engine.MockStrategy
import org.utbot.framework.context.ConcreteExecutionContext
import org.utbot.framework.context.JavaFuzzingContext
import org.utbot.framework.plugin.api.ClassId
Expand Down Expand Up @@ -55,6 +56,7 @@ class SpringIntegrationTestConcreteExecutionContext(
override fun tryCreateFuzzingContext(
concreteExecutor: ConcreteExecutor<UtConcreteExecutionResult, UtExecutionInstrumentation>,
classUnderTest: ClassId,
mockStrategy: MockStrategy,
idGenerator: IdentityPreservingIdGenerator<Int>
): JavaFuzzingContext {
if (springApplicationContext.getBeansAssignableTo(classUnderTest).isEmpty())
Expand All @@ -68,7 +70,12 @@ class SpringIntegrationTestConcreteExecutionContext(
logger.info { "Detected relevant repositories for class $classUnderTest: $relevantRepositories" }

return SpringIntegrationTestJavaFuzzingContext(
delegateContext = delegateContext.tryCreateFuzzingContext(concreteExecutor, classUnderTest, idGenerator),
delegateContext = delegateContext.tryCreateFuzzingContext(
concreteExecutor,
classUnderTest,
mockStrategy,
idGenerator,
),
relevantRepositories = relevantRepositories,
springApplicationContext = springApplicationContext,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@ package org.utbot.framework.context.spring
import org.utbot.framework.context.TypeReplacer
import org.utbot.framework.plugin.api.ClassId
import org.utbot.framework.plugin.api.TypeReplacementMode
import org.utbot.framework.plugin.api.id
import org.utbot.framework.plugin.api.isAbstractType
import org.utbot.framework.plugin.api.util.isAbstract
import org.utbot.framework.plugin.api.util.isSubtypeOf
import soot.RefType

class SpringTypeReplacer(
private val delegateTypeReplacer: TypeReplacer,
Expand All @@ -19,7 +17,7 @@ class SpringTypeReplacer(
else
TypeReplacementMode.NoImplementors

override fun replaceTypeIfNeeded(type: RefType): ClassId? =
if (type.isAbstractType) springApplicationContext.injectedTypes.singleOrNull { it.isSubtypeOf(type.id) }
else delegateTypeReplacer.replaceTypeIfNeeded(type)
override fun replaceTypeIfNeeded(classId: ClassId): ClassId? =
if (classId.isAbstract) springApplicationContext.injectedTypes.singleOrNull { it.isSubtypeOf(classId) }
else delegateTypeReplacer.replaceTypeIfNeeded(classId)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.utbot.framework.context.utils

import org.utbot.engine.MockStrategy
import org.utbot.framework.context.ConcreteExecutionContext
import org.utbot.framework.context.JavaFuzzingContext
import org.utbot.framework.plugin.api.ClassId
Expand All @@ -15,9 +16,15 @@ fun ConcreteExecutionContext.transformJavaFuzzingContext(
override fun tryCreateFuzzingContext(
concreteExecutor: ConcreteExecutor<UtConcreteExecutionResult, UtExecutionInstrumentation>,
classUnderTest: ClassId,
mockStrategy: MockStrategy,
idGenerator: IdentityPreservingIdGenerator<Int>
): JavaFuzzingContext = transformer(
this@transformJavaFuzzingContext.tryCreateFuzzingContext(concreteExecutor, classUnderTest, idGenerator)
this@transformJavaFuzzingContext.tryCreateFuzzingContext(
concreteExecutor,
classUnderTest,
mockStrategy,
idGenerator,
)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ internal fun FuzzedType.traverseHierarchy(typeCache: MutableMap<Type, FuzzedType
* @param type to be resolved
* @param cache is used to store same [FuzzedType] for same java types
*/
internal fun toFuzzerType(type: Type, cache: MutableMap<Type, FuzzedType>): FuzzedType {
fun toFuzzerType(type: Type, cache: MutableMap<Type, FuzzedType>): FuzzedType {
return toFuzzerType(
type = type,
classId = { t -> toClassId(t, cache) },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import org.utbot.fuzzer.FuzzedValue
import org.utbot.fuzzing.FuzzedDescription
import org.utbot.fuzzing.JavaValueProvider
import org.utbot.fuzzing.Routine
import org.utbot.fuzzing.Scope
import org.utbot.fuzzing.Seed
import org.utbot.fuzzing.spring.decorators.ValueProviderDecorator

/**
* Value provider that is a buddy for another provider
Expand All @@ -22,8 +22,11 @@ import org.utbot.fuzzing.Seed
*/
class ModifyingWithMethodsProviderWrapper(
private val classUnderTest: ClassId,
private val delegate: JavaValueProvider
) : JavaValueProvider by delegate {
delegate: JavaValueProvider
) : ValueProviderDecorator<FuzzedType, FuzzedValue, FuzzedDescription>(delegate) {

override fun wrap(provider: JavaValueProvider): JavaValueProvider =
ModifyingWithMethodsProviderWrapper(classUnderTest, provider)

override fun generate(description: FuzzedDescription, type: FuzzedType): Sequence<Seed<FuzzedType, FuzzedValue>> =
delegate
Expand All @@ -50,9 +53,4 @@ class ModifyingWithMethodsProviderWrapper(
)
} else seed
}

override fun enrich(description: FuzzedDescription, type: FuzzedType, scope: Scope) =
delegate.enrich(description, type, scope)

override fun accept(type: FuzzedType): Boolean = delegate.accept(type)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import org.utbot.fuzzer.FuzzedValue
import org.utbot.fuzzing.FuzzedDescription
import org.utbot.fuzzing.JavaValueProvider
import org.utbot.fuzzing.Routine
import org.utbot.fuzzing.Scope
import org.utbot.fuzzing.Seed
import org.utbot.fuzzing.spring.decorators.ValueProviderDecorator

/**
* @see preserveProperties
Expand All @@ -25,14 +25,14 @@ interface PreservableFuzzedTypeProperty<T> : FuzzedTypeProperty<T>
fun JavaValueProvider.preserveProperties() : JavaValueProvider =
PropertyPreservingValueProvider(this)

class PropertyPreservingValueProvider(private val delegateProvider: JavaValueProvider) : JavaValueProvider {
override fun enrich(description: FuzzedDescription, type: FuzzedType, scope: Scope) =
delegateProvider.enrich(description, type, scope)

override fun accept(type: FuzzedType): Boolean = delegateProvider.accept(type)
class PropertyPreservingValueProvider(
delegate: JavaValueProvider
) : ValueProviderDecorator<FuzzedType, FuzzedValue, FuzzedDescription>(delegate) {
override fun wrap(provider: JavaValueProvider): JavaValueProvider =
provider.preserveProperties()

override fun generate(description: FuzzedDescription, type: FuzzedType): Sequence<Seed<FuzzedType, FuzzedValue>> {
val delegateSeeds = delegateProvider.generate(description, type)
val delegateSeeds = delegate.generate(description, type)

val preservedProperties = type.properties.entries
.filter { it.property is PreservableFuzzedTypeProperty }
Expand Down Expand Up @@ -67,10 +67,4 @@ class PropertyPreservingValueProvider(private val delegateProvider: JavaValuePro
}
}
}

override fun map(transform: (JavaValueProvider) -> JavaValueProvider): JavaValueProvider =
delegateProvider.map(transform).preserveProperties()

override fun except(filter: (JavaValueProvider) -> Boolean): JavaValueProvider =
delegateProvider.except(filter).preserveProperties()
}
Loading

0 comments on commit 701fb39

Please sign in to comment.