Skip to content

Commit 36c23ff

Browse files
authored
Support mock strategies and type replacement in Spring unit test fuzzing (#2561)
* Support mock strategies and type replacement in Spring unit test fuzzing * Avoid mocking replaced types * Only use `InjectMockValueProvider` for `thisInstance` * Disallow non-mocks when mock can be used * Move all value provider decorators to one package * Improve adding of `ReplacedFuzzedTypeFlag` * Make `map` and `except` apply transformer/filter to ALL value providers * Avoid using `anyObjectValueProvider` that is removed in #2583 * Make `AnyObjectValueProvider` not extend any interfaces, remove unrelated comment * Fix compilation after rebase
1 parent dd787d0 commit 36c23ff

File tree

19 files changed

+218
-85
lines changed

19 files changed

+218
-85
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -1397,7 +1397,7 @@ class Traverser(
13971397
// from Spring bean definitions, for example), we can just create a symbolic object
13981398
// with hard constraint on the mentioned type.
13991399
val replacedClassId = when (typeReplacer.typeReplacementMode) {
1400-
KnownImplementor -> typeReplacer.replaceTypeIfNeeded(type)
1400+
KnownImplementor -> typeReplacer.replaceTypeIfNeeded(type.id)
14011401
AnyImplementor,
14021402
NoImplementors -> null
14031403
}

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

-1
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,6 @@ class UtBotSymbolicEngine(
424424
* Run fuzzing flow.
425425
*
426426
* @param until is used by fuzzer to cancel all tasks if the current time is over this value
427-
* @param transform provides model values for a method
428427
*/
429428
fun fuzzing(until: Long = Long.MAX_VALUE) = flow {
430429
val isFuzzable = methodUnderTest.parameters.all { classId ->

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

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package org.utbot.framework.context
22

33
import org.utbot.framework.plugin.api.ClassId
44
import org.utbot.framework.plugin.api.TypeReplacementMode
5-
import soot.RefType
65

76
interface TypeReplacer {
87
/**
@@ -14,5 +13,5 @@ interface TypeReplacer {
1413
* Finds a type to replace the original abstract type
1514
* if it is guided with some additional information.
1615
*/
17-
fun replaceTypeIfNeeded(type: RefType): ClassId?
16+
fun replaceTypeIfNeeded(classId: ClassId): ClassId?
1817
}

utbot-framework/src/main/kotlin/org/utbot/framework/context/custom/MockingJavaFuzzingContext.kt

+29-19
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,51 @@
11
package org.utbot.framework.context.custom
22

33
import org.utbot.framework.context.JavaFuzzingContext
4+
import org.utbot.framework.plugin.api.UtNullModel
45
import org.utbot.framework.plugin.api.ExecutableId
6+
import org.utbot.fuzzer.FuzzedType
57
import org.utbot.fuzzing.JavaValueProvider
6-
import org.utbot.fuzzing.providers.MapValueProvider
8+
import org.utbot.fuzzing.Seed
9+
import org.utbot.fuzzing.providers.AnyDepthNullValueProvider
10+
import org.utbot.fuzzing.providers.AnyObjectValueProvider
711
import org.utbot.fuzzing.spring.unit.MockValueProvider
8-
import org.utbot.fuzzing.providers.NullValueProvider
9-
import org.utbot.fuzzing.providers.ObjectValueProvider
10-
import org.utbot.fuzzing.providers.StringValueProvider
12+
import org.utbot.fuzzing.spring.decorators.filterSeeds
13+
import org.utbot.fuzzing.spring.decorators.filterTypes
1114
import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult
1215

1316
/**
14-
* Makes fuzzer mock all types that don't have *specific* [JavaValueProvider],
15-
* like [MapValueProvider] or [StringValueProvider].
17+
* Makes fuzzer to use mocks in accordance with [mockPredicate].
1618
*
17-
* NOTE: the caller is responsible for providing some *specific* [JavaValueProvider]
18-
* that can create values for class under test (otherwise it will be mocked),
19-
* [ObjectValueProvider] and [NullValueProvider] do not count as *specific*.
19+
* NOTE:
20+
* - fuzzer won't mock types, that have *specific* value providers
21+
* (i.e. ones that do not implement [AnyObjectValueProvider])
22+
* - fuzzer may still resort to mocks despite [mockPredicate] and *specific*
23+
* value providers if it can't create other non-null values or at runtime
2024
*/
21-
fun JavaFuzzingContext.mockAllTypesWithoutSpecificValueProvider() =
22-
MockingJavaFuzzingContext(delegateContext = this)
25+
fun JavaFuzzingContext.useMocks(mockPredicate: (FuzzedType) -> Boolean) =
26+
MockingJavaFuzzingContext(delegateContext = this, mockPredicate)
2327

2428
class MockingJavaFuzzingContext(
25-
val delegateContext: JavaFuzzingContext
29+
val delegateContext: JavaFuzzingContext,
30+
val mockPredicate: (FuzzedType) -> Boolean,
2631
) : JavaFuzzingContext by delegateContext {
2732
private val mockValueProvider = MockValueProvider(delegateContext.idGenerator)
2833

2934
override val valueProvider: JavaValueProvider =
30-
// NOTE: we first remove `NullValueProvider` from `delegateContext.valueProvider` and then
31-
// add it back as a part of our `withFallback` so it has the same priority as
32-
// `mockValueProvider`, otherwise mocks will never be used where `null` can be used.
35+
3336
delegateContext.valueProvider
34-
.except { it is NullValueProvider }
35-
.except { it is ObjectValueProvider }
37+
// NOTE: we first remove `AnyObjectValueProvider` and `NullValueProvider` from `delegateContext.valueProvider`
38+
// and then add them back as a part of our `withFallback` so they have the same priority as
39+
// `mockValueProvider`, otherwise mocks will never be used where `null` or new object can be used.
40+
.except { it is AnyObjectValueProvider }
3641
.withFallback(
37-
mockValueProvider
38-
.with(NullValueProvider)
42+
mockValueProvider.filterTypes(mockPredicate)
43+
.with(
44+
delegateContext.valueProvider
45+
.filterTypes { !mockPredicate(it) }
46+
.filterSeeds { (it as? Seed.Simple)?.value?.model !is UtNullModel }
47+
)
48+
.withFallback(mockValueProvider.with(AnyDepthNullValueProvider))
3949
)
4050

4151
override fun handleFuzzedConcreteExecutionResult(

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

+1-2
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@ package org.utbot.framework.context.simple
33
import org.utbot.framework.context.TypeReplacer
44
import org.utbot.framework.plugin.api.ClassId
55
import org.utbot.framework.plugin.api.TypeReplacementMode
6-
import soot.RefType
76

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

11-
override fun replaceTypeIfNeeded(type: RefType): ClassId? = null
10+
override fun replaceTypeIfNeeded(classId: ClassId): ClassId? = null
1211
}

utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Providers.kt

+10-15
Original file line numberDiff line numberDiff line change
@@ -92,26 +92,16 @@ fun interface ValueProvider<T, R, D : Description<T>> {
9292
}
9393

9494
/**
95-
* Removes `anotherValueProviders<T, R` from current one.
95+
* Removes providers matching [filter] from the current one.
9696
*/
97-
fun except(filter: (ValueProvider<T, R, D>) -> Boolean): ValueProvider<T, R, D> {
98-
return if (this is Combined) {
99-
Combined(providers.map { it.unwrapIfFallback() }.filterNot(filter))
100-
} else {
101-
Combined(if (filter(unwrapIfFallback())) emptyList() else listOf(this))
102-
}
103-
}
97+
fun except(filter: (ValueProvider<T, R, D>) -> Boolean): ValueProvider<T, R, D> =
98+
map { if (filter(it)) Combined(emptyList()) else it }
10499

105100
/**
106101
* Applies [transform] for current provider
107102
*/
108-
fun map(transform: (ValueProvider<T, R, D>) -> ValueProvider<T, R, D>): ValueProvider<T, R, D> {
109-
return if (this is Combined) {
110-
Combined(providers.map(transform))
111-
} else {
112-
transform(this)
113-
}
114-
}
103+
fun map(transform: (ValueProvider<T, R, D>) -> ValueProvider<T, R, D>): ValueProvider<T, R, D> =
104+
transform(this)
115105

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

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

172164
/**
@@ -200,6 +192,9 @@ fun interface ValueProvider<T, R, D : Description<T>> {
200192
}
201193
}
202194
}
195+
196+
override fun map(transform: (ValueProvider<T, R, D>) -> ValueProvider<T, R, D>): ValueProvider<T, R, D> =
197+
transform(Combined(providers.map { it.map(transform) }))
203198
}
204199

205200
companion object {

utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/JavaLanguage.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ internal fun FuzzedType.traverseHierarchy(typeCache: MutableMap<Type, FuzzedType
171171
* @param type to be resolved
172172
* @param cache is used to store same [FuzzedType] for same java types
173173
*/
174-
internal fun toFuzzerType(type: Type, cache: MutableMap<Type, FuzzedType>): FuzzedType {
174+
fun toFuzzerType(type: Type, cache: MutableMap<Type, FuzzedType>): FuzzedType {
175175
return toFuzzerType(
176176
type = type,
177177
classId = { t -> toClassId(t, cache) },

utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt

+11-4
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,16 @@ fun anyObjectValueProvider(idGenerator: IdentityPreservingIdGenerator<Int>) =
7070
ovp.withFallback(AbstractsObjectValueProvider(idGenerator))
7171
}
7272

73+
/**
74+
* Marker interface that shows that this [JavaValueProvider] can potentially provide values of
75+
* arbitrary types, unlike type-specific value providers that were designed to provide values of
76+
* few specific popular types (e.g. `List`, `String`, etc.).
77+
*/
78+
interface AnyObjectValueProvider
79+
7380
class ObjectValueProvider(
7481
val idGenerator: IdGenerator<Int>,
75-
) : JavaValueProvider {
82+
) : JavaValueProvider, AnyObjectValueProvider {
7683

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

@@ -140,7 +147,7 @@ class ObjectValueProvider(
140147
}
141148

142149
@Suppress("unused")
143-
object NullValueProvider : JavaValueProvider {
150+
object NullValueProvider : JavaValueProvider, AnyObjectValueProvider {
144151

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

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

@@ -185,7 +192,7 @@ object AnyDepthNullValueProvider : JavaValueProvider {
185192
*/
186193
class AbstractsObjectValueProvider(
187194
val idGenerator: IdGenerator<Int>,
188-
) : JavaValueProvider {
195+
) : JavaValueProvider, AnyObjectValueProvider {
189196

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

utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/ModifyingWithMethodsProviderWrapper.kt renamed to utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/decorators/ModifyingWithMethodsProviderWrapper.kt

+7-9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package org.utbot.fuzzing.providers
1+
package org.utbot.fuzzing.spring.decorators
22

33
import org.utbot.framework.plugin.api.ClassId
44
import org.utbot.framework.plugin.api.UtAssembleModel
@@ -9,8 +9,8 @@ import org.utbot.fuzzer.FuzzedValue
99
import org.utbot.fuzzing.FuzzedDescription
1010
import org.utbot.fuzzing.JavaValueProvider
1111
import org.utbot.fuzzing.Routine
12-
import org.utbot.fuzzing.Scope
1312
import org.utbot.fuzzing.Seed
13+
import org.utbot.fuzzing.providers.findMethodsToModifyWith
1414

1515
/**
1616
* Value provider that is a buddy for another provider
@@ -22,8 +22,11 @@ import org.utbot.fuzzing.Seed
2222
*/
2323
class ModifyingWithMethodsProviderWrapper(
2424
private val classUnderTest: ClassId,
25-
private val delegate: JavaValueProvider
26-
) : JavaValueProvider by delegate {
25+
delegate: JavaValueProvider
26+
) : ValueProviderDecorator<FuzzedType, FuzzedValue, FuzzedDescription>(delegate) {
27+
28+
override fun wrap(provider: JavaValueProvider): JavaValueProvider =
29+
ModifyingWithMethodsProviderWrapper(classUnderTest, provider)
2730

2831
override fun generate(description: FuzzedDescription, type: FuzzedType): Sequence<Seed<FuzzedType, FuzzedValue>> =
2932
delegate
@@ -50,9 +53,4 @@ class ModifyingWithMethodsProviderWrapper(
5053
)
5154
} else seed
5255
}
53-
54-
override fun enrich(description: FuzzedDescription, type: FuzzedType, scope: Scope) =
55-
delegate.enrich(description, type, scope)
56-
57-
override fun accept(type: FuzzedType): Boolean = delegate.accept(type)
5856
}

utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/PropertyPreservingValueProvider.kt renamed to utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/decorators/PropertyPreservingValueProvider.kt

+10-14
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
package org.utbot.fuzzing.spring
1+
package org.utbot.fuzzing.spring.decorators
22

33
import org.utbot.common.toDynamicProperties
44
import org.utbot.fuzzer.FuzzedType
55
import org.utbot.fuzzer.FuzzedValue
66
import org.utbot.fuzzing.FuzzedDescription
77
import org.utbot.fuzzing.JavaValueProvider
88
import org.utbot.fuzzing.Routine
9-
import org.utbot.fuzzing.Scope
109
import org.utbot.fuzzing.Seed
10+
import org.utbot.fuzzing.spring.FuzzedTypeProperty
11+
import org.utbot.fuzzing.spring.addProperties
12+
import org.utbot.fuzzing.spring.properties
1113

1214
/**
1315
* @see preserveProperties
@@ -25,14 +27,14 @@ interface PreservableFuzzedTypeProperty<T> : FuzzedTypeProperty<T>
2527
fun JavaValueProvider.preserveProperties() : JavaValueProvider =
2628
PropertyPreservingValueProvider(this)
2729

28-
class PropertyPreservingValueProvider(private val delegateProvider: JavaValueProvider) : JavaValueProvider {
29-
override fun enrich(description: FuzzedDescription, type: FuzzedType, scope: Scope) =
30-
delegateProvider.enrich(description, type, scope)
31-
32-
override fun accept(type: FuzzedType): Boolean = delegateProvider.accept(type)
30+
class PropertyPreservingValueProvider(
31+
delegate: JavaValueProvider
32+
) : ValueProviderDecorator<FuzzedType, FuzzedValue, FuzzedDescription>(delegate) {
33+
override fun wrap(provider: JavaValueProvider): JavaValueProvider =
34+
provider.preserveProperties()
3335

3436
override fun generate(description: FuzzedDescription, type: FuzzedType): Sequence<Seed<FuzzedType, FuzzedValue>> {
35-
val delegateSeeds = delegateProvider.generate(description, type)
37+
val delegateSeeds = delegate.generate(description, type)
3638

3739
val preservedProperties = type.properties.entries
3840
.filter { it.property is PreservableFuzzedTypeProperty }
@@ -67,10 +69,4 @@ class PropertyPreservingValueProvider(private val delegateProvider: JavaValuePro
6769
}
6870
}
6971
}
70-
71-
override fun map(transform: (JavaValueProvider) -> JavaValueProvider): JavaValueProvider =
72-
delegateProvider.map(transform).preserveProperties()
73-
74-
override fun except(filter: (JavaValueProvider) -> Boolean): JavaValueProvider =
75-
delegateProvider.except(filter).preserveProperties()
7672
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package org.utbot.fuzzing.spring.decorators
2+
3+
import org.utbot.fuzzing.Description
4+
import org.utbot.fuzzing.Seed
5+
import org.utbot.fuzzing.ValueProvider
6+
7+
fun <T, R, D : Description<T>> ValueProvider<T, R, D>.filterSeeds(predicate: (Seed<T, R>) -> Boolean) =
8+
SeedFilteringValueProvider(delegate = this, predicate)
9+
10+
class SeedFilteringValueProvider<T, R, D : Description<T>>(
11+
delegate: ValueProvider<T, R, D>,
12+
private val predicate: (Seed<T, R>) -> Boolean
13+
) : ValueProviderDecorator<T, R, D>(delegate) {
14+
override fun wrap(provider: ValueProvider<T, R, D>): ValueProvider<T, R, D> =
15+
provider.filterSeeds(predicate)
16+
17+
override fun generate(description: D, type: T): Sequence<Seed<T, R>> =
18+
delegate.generate(description, type).filter(predicate)
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.utbot.fuzzing.spring.decorators
2+
3+
import org.utbot.fuzzing.Description
4+
import org.utbot.fuzzing.ValueProvider
5+
6+
fun <T, R, D : Description<T>> ValueProvider<T, R, D>.filterTypes(predicate: (T) -> Boolean) =
7+
TypeFilteringValueProvider(delegate = this, predicate)
8+
9+
class TypeFilteringValueProvider<T, R, D : Description<T>>(
10+
delegate: ValueProvider<T, R, D>,
11+
private val predicate: (T) -> Boolean
12+
) : ValueProviderDecorator<T, R, D>(delegate) {
13+
override fun wrap(provider: ValueProvider<T, R, D>): ValueProvider<T, R, D> =
14+
provider.filterTypes(predicate)
15+
16+
override fun accept(type: T): Boolean =
17+
predicate(type) && super.accept(type)
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package org.utbot.fuzzing.spring.decorators
2+
3+
import org.utbot.fuzzing.Description
4+
import org.utbot.fuzzing.Scope
5+
import org.utbot.fuzzing.Seed
6+
import org.utbot.fuzzing.ValueProvider
7+
8+
fun <T, R, D : Description<T>> ValueProvider<T, R, D>.replaceTypes(typeReplacer: (D, T) -> T) =
9+
TypeReplacingValueProvider(delegate = this, typeReplacer)
10+
11+
class TypeReplacingValueProvider<T, R, D : Description<T>>(
12+
delegate: ValueProvider<T, R, D>,
13+
private val typeReplacer: (D, T) -> T
14+
) : ValueProviderDecorator<T, R, D>(delegate) {
15+
override fun wrap(provider: ValueProvider<T, R, D>): ValueProvider<T, R, D> =
16+
provider.replaceTypes(typeReplacer)
17+
18+
override fun enrich(description: D, type: T, scope: Scope) =
19+
super.enrich(description, typeReplacer(description, type), scope)
20+
21+
override fun accept(type: T): Boolean = true
22+
23+
override fun generate(description: D, type: T): Sequence<Seed<T, R>> =
24+
if (super.accept(typeReplacer(description, type)))
25+
super.generate(description, typeReplacer(description, type))
26+
else
27+
emptySequence()
28+
}

0 commit comments

Comments
 (0)