Skip to content

Commit 7a7eeb8

Browse files
committed
Other class is not mocked as required #747
1 parent 560ac03 commit 7a7eeb8

File tree

7 files changed

+243
-7
lines changed

7 files changed

+243
-7
lines changed

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ class UtBotSymbolicEngine(
158158
private val methodUnderTest: ExecutableId,
159159
classpath: String,
160160
dependencyPaths: String,
161-
mockStrategy: MockStrategy = NO_MOCKS,
161+
val mockStrategy: MockStrategy = NO_MOCKS,
162162
chosenClassesToMockAlways: Set<ClassId>,
163163
private val solverTimeoutInMillis: Int = checkSolverTimeoutMillis
164164
) : UtContextInitializer() {
@@ -429,6 +429,7 @@ class UtBotSymbolicEngine(
429429
val names = graph.body.method.tags.filterIsInstance<ParamNamesTag>().firstOrNull()?.names
430430
parameterNameMap = { index -> names?.getOrNull(index) }
431431
fuzzerType = { try { toFuzzerType(methodUnderTest.executable.genericParameterTypes[it]) } catch (_: Throwable) { null } }
432+
shouldMock = { mockStrategy.eligibleToMock(it, classUnderTest) }
432433
}
433434
val coveredInstructionTracker = Trie(Instruction::id)
434435
val coveredInstructionValues = linkedMapOf<Trie.Node<Instruction>, List<FuzzedValue>>()

utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedMethodDescription.kt

+5
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ class FuzzedMethodDescription(
5252
*/
5353
var fuzzerType: (Int) -> FuzzedType? = { null }
5454

55+
/**
56+
* Returns true if class should be mocked.
57+
*/
58+
var shouldMock: (ClassId) -> Boolean = { false }
59+
5560
/**
5661
* Map class id to indices of this class in parameters list.
5762
*/

utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt

+5-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import org.utbot.framework.plugin.api.util.voidClassId
77
import org.utbot.fuzzer.mutators.NumberRandomMutator
88
import org.utbot.fuzzer.mutators.RegexStringModelMutator
99
import org.utbot.fuzzer.mutators.StringRandomMutator
10+
import org.utbot.fuzzer.objects.replaceToMock
1011
import org.utbot.fuzzer.providers.ArrayModelProvider
1112
import org.utbot.fuzzer.providers.CharToStringModelProvider
1213
import org.utbot.fuzzer.providers.CollectionWithEmptyStatesModelProvider
@@ -117,7 +118,10 @@ fun fuzz(description: FuzzedMethodDescription, vararg modelProviders: ModelProvi
117118
val values = List<MutableList<FuzzedValue>>(description.parameters.size) { mutableListOf() }
118119
modelProviders.forEach { fuzzingProvider ->
119120
fuzzingProvider.generate(description).forEach { (index, model) ->
120-
values[index].add(model)
121+
val mock = replaceToMock(model.model, description.shouldMock)
122+
values[index].add(FuzzedValue(mock, model.createdBy).apply {
123+
summary = model.summary
124+
})
121125
}
122126
}
123127
description.parameters.forEachIndexed { index, classId ->

utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/objects/AssembleModelUtils.kt

+56
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ package org.utbot.fuzzer.objects
33
import org.utbot.framework.plugin.api.ClassId
44
import org.utbot.framework.plugin.api.ConstructorId
55
import org.utbot.framework.plugin.api.ExecutableId
6+
import org.utbot.framework.plugin.api.FieldId
67
import org.utbot.framework.plugin.api.MethodId
78
import org.utbot.framework.plugin.api.UtAssembleModel
9+
import org.utbot.framework.plugin.api.UtCompositeModel
10+
import org.utbot.framework.plugin.api.UtDirectSetFieldModel
811
import org.utbot.framework.plugin.api.UtExecutableCallModel
912
import org.utbot.framework.plugin.api.UtModel
1013
import org.utbot.framework.plugin.api.UtStatementModel
@@ -25,6 +28,43 @@ fun ModelProvider.assembleModel(id: Int, constructorId: ConstructorId, params: L
2528
}
2629
}
2730

31+
fun replaceToMock(assembleModel: UtModel, shouldMock: (ClassId) -> Boolean): UtModel {
32+
if (assembleModel !is UtAssembleModel) return assembleModel
33+
if (shouldMock(assembleModel.classId)) {
34+
return UtCompositeModel(assembleModel.id, assembleModel.classId, true).apply {
35+
assembleModel.modificationsChain.forEach {
36+
if (it is UtDirectSetFieldModel) {
37+
fields[it.fieldId] = replaceToMock(it.fieldModel, shouldMock)
38+
}
39+
if (it is UtExecutableCallModel && it.executable is FuzzerMockableMethodId) {
40+
(it.executable as FuzzerMockableMethodId).mock().forEach { (executionId, models) ->
41+
mocks[executionId] = models.map { p -> replaceToMock(p, shouldMock) }
42+
}
43+
}
44+
}
45+
}
46+
} else {
47+
val models = assembleModel.modificationsChain.map { call ->
48+
var mockedStatementModel: UtStatementModel? = null
49+
if (call is UtDirectSetFieldModel) {
50+
val mock = replaceToMock(call.fieldModel, shouldMock)
51+
if (mock != call.fieldModel) {
52+
mockedStatementModel = UtDirectSetFieldModel(call.instance, call.fieldId, mock)
53+
}
54+
} else if (call is UtExecutableCallModel) {
55+
val params = call.params.map { m -> replaceToMock(m, shouldMock) }
56+
if (params != call.params) {
57+
mockedStatementModel = UtExecutableCallModel(call.instance, call.executable, params)
58+
}
59+
}
60+
mockedStatementModel ?: call
61+
}
62+
return with(assembleModel) {
63+
UtAssembleModel(id, classId, modelName, instantiationCall, origin) { models }
64+
}
65+
}
66+
}
67+
2868
fun ClassId.create(
2969
block: AssembleModelDsl.() -> Unit
3070
): UtAssembleModel {
@@ -38,6 +78,7 @@ class AssembleModelDsl internal constructor(
3878
val call = KeyWord.Call
3979
val constructor = KeyWord.Constructor(classId)
4080
val method = KeyWord.Method(classId)
81+
val field = KeyWord.Field(classId)
4182

4283
var id: () -> Int? = { null }
4384
var name: (Int?) -> String = { "<dsl generated model>" }
@@ -53,10 +94,15 @@ class AssembleModelDsl internal constructor(
5394

5495
infix fun <T : ExecutableId> KeyWord.Call.instance(executableId: T) = CallDsl(executableId, false)
5596

97+
infix fun <T : FieldId> KeyWord.Call.instance(field: T) = FieldDsl(field, false)
98+
5699
infix fun <T : ExecutableId> KeyWord.Using.static(executableId: T) = UsingDsl(executableId)
57100

58101
infix fun <T : ExecutableId> KeyWord.Call.static(executableId: T) = CallDsl(executableId, true)
59102

103+
infix fun <T : FieldId> KeyWord.Call.static(field: T) = FieldDsl(field, true)
104+
105+
@Suppress("UNUSED_PARAMETER")
60106
infix fun KeyWord.Using.empty(ignored: KeyWord.Constructor) {
61107
initialization = { UtExecutableCallModel(null, ConstructorId(classId, emptyList()), emptyList()) }
62108
}
@@ -73,6 +119,10 @@ class AssembleModelDsl internal constructor(
73119
modChain += { UtExecutableCallModel(it, executableId, models.toList()) }
74120
}
75121

122+
infix fun FieldDsl.with(model: UtModel) {
123+
modChain += { UtDirectSetFieldModel(it, fieldId, model) }
124+
}
125+
76126
internal fun build(): UtAssembleModel {
77127
val objectId = id()
78128
return UtAssembleModel(
@@ -102,8 +152,14 @@ class AssembleModelDsl internal constructor(
102152
return MethodId(classId, name, returns, params)
103153
}
104154
}
155+
class Field(val classId: ClassId) : KeyWord() {
156+
operator fun invoke(name: String): FieldId {
157+
return FieldId(classId, name)
158+
}
159+
}
105160
}
106161

107162
class UsingDsl(val executableId: ExecutableId)
108163
class CallDsl(val executableId: ExecutableId, val isStatic: Boolean)
164+
class FieldDsl(val fieldId: FieldId, val isStatic: Boolean)
109165
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package org.utbot.fuzzer.objects
2+
3+
import org.utbot.framework.plugin.api.ClassId
4+
import org.utbot.framework.plugin.api.ExecutableId
5+
import org.utbot.framework.plugin.api.MethodId
6+
import org.utbot.framework.plugin.api.UtModel
7+
8+
/**
9+
* Implements [MethodId] but also can supply a mock for this execution.
10+
*
11+
* Simplest example: setter and getter,
12+
* when this methodId is a setter, getter can be used for a mock to supply correct value.
13+
*/
14+
internal class FuzzerMockableMethodId(
15+
classId: ClassId,
16+
name: String,
17+
returnType: ClassId,
18+
parameters: List<ClassId>,
19+
val mock: () -> Map<ExecutableId, List<UtModel>> = { emptyMap() },
20+
) : MethodId(classId, name, returnType, parameters) {
21+
22+
constructor(copyOf: MethodId, mock: () -> Map<ExecutableId, List<UtModel>> = { emptyMap() }) : this(
23+
copyOf.classId, copyOf.name, copyOf.returnType, copyOf.parameters, mock
24+
)
25+
26+
}
27+
28+
internal fun MethodId.toFuzzerMockable(block: suspend SequenceScope<Pair<MethodId, List<UtModel>>>.() -> Unit): FuzzerMockableMethodId {
29+
return FuzzerMockableMethodId(this) {
30+
sequence { block() }.toMap()
31+
}
32+
}

utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ObjectModelProvider.kt

+20-5
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import org.utbot.fuzzer.FuzzedMethodDescription
2727
import org.utbot.fuzzer.FuzzedType
2828
import org.utbot.fuzzer.FuzzedValue
2929
import org.utbot.fuzzer.IdentityPreservingIdGenerator
30+
import org.utbot.fuzzer.objects.FuzzerMockableMethodId
3031
import org.utbot.fuzzer.objects.assembleModel
3132

3233
/**
@@ -98,11 +99,22 @@ class ObjectModelProvider(
9899
)
99100
field.setter != null -> UtExecutableCallModel(
100101
fuzzedModel.model,
101-
MethodId(
102+
FuzzerMockableMethodId(
102103
constructorId.classId,
103104
field.setter.name,
104105
field.setter.returnType.id,
105-
listOf(field.classId)
106+
listOf(field.classId),
107+
mock = {
108+
field.getter?.let { g ->
109+
val getterMethodID = MethodId(
110+
constructorId.classId,
111+
g.name,
112+
g.returnType.id,
113+
emptyList()
114+
)
115+
mapOf(getterMethodID to listOf(value.model))
116+
} ?: emptyMap()
117+
}
106118
),
107119
listOf(value.model)
108120
)
@@ -141,16 +153,18 @@ class ObjectModelProvider(
141153
private fun findSuitableFields(classId: ClassId, description: FuzzedMethodDescription): List<FieldDescription> {
142154
val jClass = classId.jClass
143155
return jClass.declaredFields.map { field ->
156+
val setterAndGetter = jClass.findPublicSetterGetterIfHasPublicGetter(field, description)
144157
FieldDescription(
145158
field.name,
146159
field.type.id,
147160
isAccessible(field, description.packageName) && !isFinal(field.modifiers) && !isStatic(field.modifiers),
148-
jClass.findPublicSetterIfHasPublicGetter(field, description)
161+
setterAndGetter?.first,
162+
setterAndGetter?.second,
149163
)
150164
}
151165
}
152166

153-
private fun Class<*>.findPublicSetterIfHasPublicGetter(field: Field, description: FuzzedMethodDescription): Method? {
167+
private fun Class<*>.findPublicSetterGetterIfHasPublicGetter(field: Field, description: FuzzedMethodDescription): Pair<Method, Method>? {
154168
val postfixName = field.name.capitalize()
155169
val setterName = "set$postfixName"
156170
val getterName = "get$postfixName"
@@ -161,7 +175,7 @@ class ObjectModelProvider(
161175
it.name == setterName &&
162176
it.parameterCount == 1 &&
163177
it.parameterTypes[0] == field.type
164-
}
178+
}?.let { it to getter }
165179
} else {
166180
null
167181
}
@@ -181,6 +195,7 @@ class ObjectModelProvider(
181195
val classId: ClassId,
182196
val canBeSetDirectly: Boolean,
183197
val setter: Method?,
198+
val getter: Method?
184199
)
185200
}
186201
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package org.utbot.framework.plugin.api
2+
3+
import org.junit.jupiter.api.Assertions.*
4+
import org.junit.jupiter.api.Test
5+
import org.utbot.framework.plugin.api.util.UtContext
6+
import org.utbot.framework.plugin.api.util.doubleWrapperClassId
7+
import org.utbot.framework.plugin.api.util.id
8+
import org.utbot.framework.plugin.api.util.voidClassId
9+
import org.utbot.framework.plugin.api.util.withUtContext
10+
import org.utbot.fuzzer.FuzzedMethodDescription
11+
import org.utbot.fuzzer.objects.create
12+
import org.utbot.fuzzer.objects.replaceToMock
13+
import org.utbot.fuzzer.objects.toFuzzerMockable
14+
import org.utbot.fuzzer.providers.ObjectModelProvider
15+
16+
class MockOfObjectModelProviderTest {
17+
18+
class Some {
19+
@Suppress("unused")
20+
var another: Some? = null
21+
}
22+
23+
@Test
24+
fun `no mock is generated by default`() = withContext {
25+
val description = FuzzedMethodDescription("test", voidClassId, listOf(Some::class.id))
26+
val provider = ObjectModelProvider(TestIdentityPreservingIdGenerator)
27+
val results = provider.generate(description).map { it.value.model }.map {
28+
replaceToMock(it) { m -> description.shouldMock(m) }
29+
}.toList()
30+
assertEquals(2, results.size)
31+
results.forEach { model ->
32+
assertInstanceOf(UtAssembleModel::class.java, model)
33+
}
34+
assertEquals(0, (results[0] as UtAssembleModel).modificationsChain.size)
35+
assertEquals(1, (results[1] as UtAssembleModel).modificationsChain.size)
36+
}
37+
38+
@Test
39+
fun `mock is generated`() = withContext {
40+
val description = FuzzedMethodDescription("test", voidClassId, listOf(Some::class.id)).apply {
41+
shouldMock = { true }
42+
}
43+
val provider = ObjectModelProvider(TestIdentityPreservingIdGenerator)
44+
val results = provider.generate(description).map { it.value.model }.map {
45+
replaceToMock(it) { m -> description.shouldMock(m) }
46+
}.toList()
47+
assertEquals(2, results.size)
48+
results.forEach { model ->
49+
assertInstanceOf(UtCompositeModel::class.java, model)
50+
assertTrue((model as UtCompositeModel).isMock)
51+
}
52+
}
53+
54+
@Test
55+
fun `mock is generated for several recursion level`() = withContext {
56+
val description = FuzzedMethodDescription("test", voidClassId, listOf(Some::class.id)).apply {
57+
shouldMock = { true }
58+
}
59+
val provider = ObjectModelProvider(TestIdentityPreservingIdGenerator, recursionDepthLeft = 2)
60+
val results = provider.generate(description).map { it.value.model }.map {
61+
replaceToMock(it) { m -> description.shouldMock(m) }
62+
}.toList()
63+
assertEquals(2, results.size)
64+
results.forEach { model ->
65+
assertInstanceOf(UtCompositeModel::class.java, model)
66+
assertTrue((model as UtCompositeModel).isMock)
67+
}
68+
val modelWithFieldChanger = results[1] as UtCompositeModel
69+
assertEquals(1, modelWithFieldChanger.mocks.size)
70+
val entry = modelWithFieldChanger.mocks.entries.single()
71+
assertEquals("getAnother", entry.key.name)
72+
assertEquals(Some::class.id, entry.key.returnType)
73+
assertEquals(1, entry.value.size)
74+
assertInstanceOf(UtCompositeModel::class.java, entry.value.single())
75+
}
76+
77+
@Test
78+
fun `check field replaced with concrete values`() {
79+
val customModel = Any::class.id.create {
80+
using empty constructor
81+
call instance field("some") with UtNullModel(Nothing::class.id)
82+
}
83+
val replacedModel = replaceToMock(customModel) { true }
84+
assertInstanceOf(UtCompositeModel::class.java, replacedModel)
85+
replacedModel as UtCompositeModel
86+
assertEquals(0, replacedModel.mocks.size)
87+
val fields = replacedModel.fields
88+
assertEquals(1, fields.size)
89+
val entry = fields.entries.single()
90+
assertEquals("some", entry.key.name)
91+
assertEquals(UtNullModel(Nothing::class.id), entry.value)
92+
}
93+
94+
@Test
95+
fun `check method replaced with mock values`() {
96+
val customModel = Any::class.id.create {
97+
using empty constructor
98+
call instance method("some").toFuzzerMockable {
99+
yield(MethodId(classId, "another", doubleWrapperClassId, emptyList()) to listOf(UtPrimitiveModel(2.0)))
100+
} with values(UtNullModel(Nothing::class.id))
101+
}
102+
val replacedModel = replaceToMock(customModel) { true }
103+
assertInstanceOf(UtCompositeModel::class.java, replacedModel)
104+
replacedModel as UtCompositeModel
105+
assertEquals(0, replacedModel.fields.size)
106+
val mocks = replacedModel.mocks
107+
assertEquals(1, replacedModel.mocks.size)
108+
val (executableId, models) = mocks.entries.single()
109+
assertEquals("another", executableId.name)
110+
assertEquals(doubleWrapperClassId, executableId.returnType)
111+
assertEquals(0, executableId.parameters.size)
112+
assertEquals(1, models.size)
113+
assertInstanceOf(UtPrimitiveModel::class.java, models.single())
114+
assertEquals(2.0, (models.single() as UtPrimitiveModel).value)
115+
}
116+
117+
private fun <T> withContext(block: () -> T) {
118+
withUtContext(UtContext(this::class.java.classLoader)) {
119+
block()
120+
}
121+
}
122+
123+
}

0 commit comments

Comments
 (0)