Skip to content
Merged
Next Next commit
Support mock strategies and type replacement in Spring unit test fuzzing
  • Loading branch information
IlyaMuravjov committed Sep 19, 2023
commit c15e5548624fc07e1d11d6231632245479000f61
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 @@ -424,7 +424,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 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.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 @@ -3,38 +3,48 @@ package org.utbot.framework.context.custom
import org.utbot.framework.context.JavaFuzzingContext
import org.utbot.framework.plugin.api.ExecutableId
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.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 @@ -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()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.utbot.fuzzing.spring.decorators

import org.utbot.fuzzing.Description
import org.utbot.fuzzing.ValueProvider

fun <T, R, D : Description<T>> ValueProvider<T, R, D>.filterTypes(predicate: (T) -> Boolean) =
FilteredValueProvider(delegate = this, predicate)

class FilteredValueProvider<T, R, D : Description<T>>(
delegate: ValueProvider<T, R, D>,
private val predicate: (T) -> Boolean
) : ValueProviderDecorator<T, R, D>(delegate) {
override fun wrap(provider: ValueProvider<T, R, D>): ValueProvider<T, R, D> =
provider.filterTypes(predicate)

override fun accept(type: T): Boolean =
predicate(type) && super.accept(type)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.utbot.fuzzing.spring.decorators

import org.utbot.fuzzing.Description
import org.utbot.fuzzing.Scope
import org.utbot.fuzzing.Seed
import org.utbot.fuzzing.ValueProvider

fun <T, R, D : Description<T>> ValueProvider<T, R, D>.replaceTypes(typeReplacer: (D, T) -> T) =
TypeReplacingValueProvider(delegate = this, typeReplacer)

class TypeReplacingValueProvider<T, R, D : Description<T>>(
delegate: ValueProvider<T, R, D>,
private val typeReplacer: (D, T) -> T
) : ValueProviderDecorator<T, R, D>(delegate) {
override fun wrap(provider: ValueProvider<T, R, D>): ValueProvider<T, R, D> =
provider.replaceTypes(typeReplacer)

override fun enrich(description: D, type: T, scope: Scope) =
super.enrich(description, typeReplacer(description, type), scope)

override fun accept(type: T): Boolean = true

override fun generate(description: D, type: T): Sequence<Seed<T, R>> =
if (super.accept(typeReplacer(description, type)))
super.generate(description, typeReplacer(description, type))
else
emptySequence()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.utbot.fuzzing.spring.decorators

import org.utbot.fuzzing.Description
import org.utbot.fuzzing.Scope
import org.utbot.fuzzing.Seed
import org.utbot.fuzzing.ValueProvider

abstract class ValueProviderDecorator<T, R, D : Description<T>>(
protected val delegate: ValueProvider<T, R, D>
) : ValueProvider<T, R, D> {
protected abstract fun wrap(provider: ValueProvider<T, R, D>): ValueProvider<T, R, D>

override fun enrich(description: D, type: T, scope: Scope) =
delegate.enrich(description, type, scope)

override fun accept(type: T): Boolean =
delegate.accept(type)

override fun generate(description: D, type: T): Sequence<Seed<T, R>> =
delegate.generate(description, type)

override fun except(filter: (ValueProvider<T, R, D>) -> Boolean): ValueProvider<T, R, D> {
val res = wrap(delegate.except(filter))
return if (filter(res)) ValueProvider.of(emptyList()) else res
}

override fun map(transform: (ValueProvider<T, R, D>) -> ValueProvider<T, R, D>): ValueProvider<T, R, D> =
transform(wrap(delegate.map(transform)))
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ 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.RerunningConcreteExecutionContext
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 @@ -25,7 +25,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 @@ -65,13 +67,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 ->
RerunningConcreteExecutionContext(
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)
}