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

Support mock strategies and type replacement in Spring unit test fuzzing #2561

Merged
merged 10 commits into from
Sep 19, 2023
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
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
@@ -1,41 +1,51 @@
package org.utbot.framework.context.custom

import org.utbot.framework.context.JavaFuzzingContext
import org.utbot.framework.plugin.api.UtNullModel
import org.utbot.framework.plugin.api.ExecutableId
import org.utbot.fuzzer.FuzzedType
import org.utbot.fuzzing.JavaValueProvider
import org.utbot.fuzzing.providers.MapValueProvider
import org.utbot.fuzzing.Seed
import org.utbot.fuzzing.providers.AnyDepthNullValueProvider
import org.utbot.fuzzing.providers.AnyObjectValueProvider
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.spring.decorators.filterSeeds
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].
* Makes fuzzer to use mocks in accordance with [mockPredicate].
*
* 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
* (i.e. ones that do not implement [AnyObjectValueProvider])
* - fuzzer may still resort to mocks despite [mockPredicate] and *specific*
* value providers if it can't create other non-null values or at runtime
*/
fun JavaFuzzingContext.mockAllTypesWithoutSpecificValueProvider() =
MockingJavaFuzzingContext(delegateContext = this)
fun JavaFuzzingContext.useMocks(mockPredicate: (FuzzedType) -> Boolean) =
MockingJavaFuzzingContext(delegateContext = this, mockPredicate)

class MockingJavaFuzzingContext(
val delegateContext: JavaFuzzingContext
val delegateContext: JavaFuzzingContext,
val mockPredicate: (FuzzedType) -> Boolean,
) : 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 }
// NOTE: we first remove `AnyObjectValueProvider` and `NullValueProvider` 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.
.except { it is AnyObjectValueProvider }
.withFallback(
mockValueProvider
.with(NullValueProvider)
mockValueProvider.filterTypes(mockPredicate)
.with(
delegateContext.valueProvider
.filterTypes { !mockPredicate(it) }
.filterSeeds { (it as? Seed.Simple)?.value?.model !is UtNullModel }
)
.withFallback(mockValueProvider.with(AnyDepthNullValueProvider))
)

override fun handleFuzzedConcreteExecutionResult(
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
}
25 changes: 10 additions & 15 deletions utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Providers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -92,26 +92,16 @@ fun interface ValueProvider<T, R, D : Description<T>> {
}

/**
* Removes `anotherValueProviders<T, R` from current one.
* Removes providers matching [filter] from the current one.
*/
fun except(filter: (ValueProvider<T, R, D>) -> Boolean): ValueProvider<T, R, D> {
return if (this is Combined) {
Combined(providers.map { it.unwrapIfFallback() }.filterNot(filter))
} else {
Combined(if (filter(unwrapIfFallback())) emptyList() else listOf(this))
}
}
fun except(filter: (ValueProvider<T, R, D>) -> Boolean): ValueProvider<T, R, D> =
map { if (filter(it)) Combined(emptyList()) else it }

/**
* Applies [transform] for current provider
*/
fun map(transform: (ValueProvider<T, R, D>) -> ValueProvider<T, R, D>): ValueProvider<T, R, D> {
return if (this is Combined) {
Combined(providers.map(transform))
} else {
transform(this)
}
}
fun map(transform: (ValueProvider<T, R, D>) -> ValueProvider<T, R, D>): ValueProvider<T, R, D> =
transform(this)

/**
* Uses fallback value provider in case when 'this' one failed to generate any value.
Expand Down Expand Up @@ -167,6 +157,8 @@ fun interface ValueProvider<T, R, D : Description<T>> {
}
}

override fun map(transform: (ValueProvider<T, R, D>) -> ValueProvider<T, R, D>): ValueProvider<T, R, D> =
transform(Fallback(provider.map(transform), fallback.map(transform)))
}

/**
Expand Down Expand Up @@ -200,6 +192,9 @@ fun interface ValueProvider<T, R, D : Description<T>> {
}
}
}

override fun map(transform: (ValueProvider<T, R, D>) -> ValueProvider<T, R, D>): ValueProvider<T, R, D> =
transform(Combined(providers.map { it.map(transform) }))
}

companion object {
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 @@ -70,9 +70,16 @@ fun anyObjectValueProvider(idGenerator: IdentityPreservingIdGenerator<Int>) =
ovp.withFallback(AbstractsObjectValueProvider(idGenerator))
}

/**
* Marker interface that shows that this [JavaValueProvider] can potentially provide values of
* arbitrary types, unlike type-specific value providers that were designed to provide values of
* few specific popular types (e.g. `List`, `String`, etc.).
*/
interface AnyObjectValueProvider

class ObjectValueProvider(
val idGenerator: IdGenerator<Int>,
) : JavaValueProvider {
) : JavaValueProvider, AnyObjectValueProvider {

override fun accept(type: FuzzedType) = !isIgnored(type.classId)

Expand Down Expand Up @@ -140,7 +147,7 @@ class ObjectValueProvider(
}

@Suppress("unused")
object NullValueProvider : JavaValueProvider {
object NullValueProvider : JavaValueProvider, AnyObjectValueProvider {

override fun enrich(description: FuzzedDescription, type: FuzzedType, scope: Scope) {
// any value in static function is ok to fuzz
Expand Down Expand Up @@ -170,7 +177,7 @@ object NullValueProvider : JavaValueProvider {
*
* Intended to be used as a last fallback.
*/
object AnyDepthNullValueProvider : JavaValueProvider {
object AnyDepthNullValueProvider : JavaValueProvider, AnyObjectValueProvider {

override fun accept(type: FuzzedType) = type.classId.isRefType

Expand All @@ -185,7 +192,7 @@ object AnyDepthNullValueProvider : JavaValueProvider {
*/
class AbstractsObjectValueProvider(
val idGenerator: IdGenerator<Int>,
) : JavaValueProvider {
) : JavaValueProvider, AnyObjectValueProvider {

override fun accept(type: FuzzedType) = type.classId.isRefType && !isKnownTypes(type.classId)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.utbot.fuzzing.providers
package org.utbot.fuzzing.spring.decorators

import org.utbot.framework.plugin.api.ClassId
import org.utbot.framework.plugin.api.UtAssembleModel
Expand All @@ -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.providers.findMethodsToModifyWith

/**
* 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
@@ -1,13 +1,15 @@
package org.utbot.fuzzing.spring
package org.utbot.fuzzing.spring.decorators

import org.utbot.common.toDynamicProperties
import org.utbot.fuzzer.FuzzedType
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.FuzzedTypeProperty
import org.utbot.fuzzing.spring.addProperties
import org.utbot.fuzzing.spring.properties

/**
* @see preserveProperties
Expand All @@ -25,14 +27,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 +69,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,19 @@
package org.utbot.fuzzing.spring.decorators

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

fun <T, R, D : Description<T>> ValueProvider<T, R, D>.filterSeeds(predicate: (Seed<T, R>) -> Boolean) =
SeedFilteringValueProvider(delegate = this, predicate)

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

override fun generate(description: D, type: T): Sequence<Seed<T, R>> =
delegate.generate(description, type).filter(predicate)
}
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) =
TypeFilteringValueProvider(delegate = this, predicate)

class TypeFilteringValueProvider<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()
}
Loading