Skip to content

Commit 63a7eda

Browse files
committed
Properly handle canHaveRedundantOrMissingMocks in instrumentation
1 parent 66470e0 commit 63a7eda

File tree

9 files changed

+167
-43
lines changed

9 files changed

+167
-43
lines changed

utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,6 @@ data class UtCompositeModel(
509509
val isMock: Boolean,
510510
val fields: MutableMap<FieldId, UtModel> = mutableMapOf(),
511511
val mocks: MutableMap<ExecutableId, List<UtModel>> = mutableMapOf(),
512-
// TODO handle it in instrumentation & codegen
513512
val canHaveRedundantOrMissingMocks: Boolean = false,
514513
) : UtReferenceModel(id, classId) {
515514
//TODO: SAT-891 - rewrite toString() method
@@ -767,13 +766,17 @@ abstract class UtCustomModel(
767766
classId: ClassId,
768767
modelName: String = id.toString(),
769768
override val origin: UtCompositeModel? = null,
770-
) : UtModelWithCompositeOrigin(id, classId, modelName, origin)
769+
) : UtModelWithCompositeOrigin(id, classId, modelName, origin) {
770+
abstract val dependencies: Collection<UtModel>
771+
}
771772

772773
object UtSpringContextModel : UtCustomModel(
773774
id = null,
774775
classId = SpringModelUtils.applicationContextClassId,
775776
modelName = "applicationContext"
776777
) {
778+
override val dependencies: Collection<UtModel> get() = emptySet()
779+
777780
// NOTE that overriding equals is required just because without it
778781
// we will lose equality for objects after deserialization
779782
override fun equals(other: Any?): Boolean = other is UtSpringContextModel
@@ -786,6 +789,8 @@ class UtSpringEntityManagerModel : UtCustomModel(
786789
classId = SpringModelUtils.entityManagerClassIds.first(),
787790
modelName = "entityManager"
788791
) {
792+
override val dependencies: Collection<UtModel> get() = emptySet()
793+
789794
// NOTE that overriding equals is required just because without it
790795
// we will lose equality for objects after deserialization
791796
override fun equals(other: Any?): Boolean = other is UtSpringEntityManagerModel
@@ -814,7 +819,9 @@ data class UtSpringMockMvcResultActionsModel(
814819
classId = SpringModelUtils.resultActionsClassId,
815820
id = id,
816821
modelName = "mockMvcResultActions@$id"
817-
)
822+
) {
823+
override val dependencies: Collection<UtModel> get() = emptySet()
824+
}
818825

819826
/**
820827
* Model for a step to obtain [UtAssembleModel].

utbot-framework/src/main/kotlin/org/utbot/framework/util/UtConcreteExecutionResultUtils.kt

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,9 @@ private fun UtConcreteExecutionResult.updateWithAssembleModels(
2121
val resolvedResult =
2222
(result as? UtExecutionSuccess)?.model?.let { UtExecutionSuccess(toAssemble(it)) } ?: result
2323

24-
return UtConcreteExecutionResult(
25-
stateBefore,
26-
resolvedStateAfter,
27-
resolvedResult,
28-
coverage,
29-
newInstrumentation
24+
return copy(
25+
stateAfter = resolvedStateAfter,
26+
result = resolvedResult,
3027
)
3128
}
3229

utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/SimpleUtExecutionInstrumentation.kt

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package org.utbot.instrumentation.instrumentation.execution
22

33
import org.utbot.framework.plugin.api.EnvironmentModels
44
import org.utbot.framework.plugin.api.FieldId
5+
import org.utbot.framework.plugin.api.MethodId
56
import org.utbot.framework.plugin.api.UtModel
67
import org.utbot.framework.plugin.api.util.executable
78
import org.utbot.framework.plugin.api.util.signature
@@ -11,6 +12,7 @@ import org.utbot.instrumentation.instrumentation.ArgumentList
1112
import org.utbot.instrumentation.instrumentation.InvokeInstrumentation
1213
import org.utbot.instrumentation.instrumentation.et.TraceHandler
1314
import org.utbot.instrumentation.instrumentation.execution.constructors.ConstructOnlyUserClassesOrCachedObjectsStrategy
15+
import org.utbot.instrumentation.instrumentation.execution.constructors.StateBeforeAwareIdGenerator
1416
import org.utbot.instrumentation.instrumentation.execution.constructors.UtModelConstructor
1517
import org.utbot.instrumentation.instrumentation.execution.context.InstrumentationContext
1618
import org.utbot.instrumentation.instrumentation.execution.context.SimpleInstrumentationContext
@@ -53,12 +55,17 @@ class SimpleUtExecutionInstrumentation(
5355
}
5456
val (stateBefore, instrumentations, timeout) = parameters // smart cast to UtConcreteExecutionData
5557

58+
lateinit var detectedMockingCandidates: Set<MethodId>
59+
5660
return PhasesController(
5761
instrumentationContext,
5862
traceHandler,
5963
delegateInstrumentation,
60-
timeout
64+
timeout,
65+
idGenerator = StateBeforeAwareIdGenerator.fromUtConcreteExecutionData(parameters)
6166
).computeConcreteExecutionResult {
67+
detectedMockingCandidates = valueConstructionPhase.detectedMockingCandidates
68+
6269
phasesWrapper {
6370
try {
6471
// some preparation actions for concrete execution
@@ -126,7 +133,10 @@ class SimpleUtExecutionInstrumentation(
126133
applyPostprocessing()
127134
}
128135
}
129-
}.toCompleteUtConcreteExecutionResult(stateBefore = stateBefore)
136+
}.toCompleteUtConcreteExecutionResult(
137+
stateBefore = stateBefore,
138+
detectedMockingCandidates = detectedMockingCandidates,
139+
)
130140
}
131141

132142
override fun getStaticField(fieldId: FieldId): Result<UtModel> =

utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/UtExecutionInstrumentation.kt

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,25 @@ data class UtConcreteExecutionData(
2424

2525
/**
2626
* [UtConcreteExecutionResult] that has not yet been populated with extra data, e.g.:
27-
* - updated `stateBefore: EnvironmentModels`
28-
* - `detectedMockingCandidates: Set<ExecutableId>` (not yet implemented, see #2321)
27+
* - updated [UtConcreteExecutionResult.stateBefore]
28+
* - [UtConcreteExecutionResult.detectedMockingCandidates]
2929
*/
3030
data class PreliminaryUtConcreteExecutionResult(
3131
val stateAfter: EnvironmentModels,
3232
val result: UtExecutionResult,
3333
val coverage: Coverage,
3434
val newInstrumentation: List<UtInstrumentation>? = null,
3535
) {
36-
fun toCompleteUtConcreteExecutionResult(stateBefore: EnvironmentModels) = UtConcreteExecutionResult(
37-
stateBefore,
38-
stateAfter,
39-
result,
40-
coverage,
41-
newInstrumentation,
36+
fun toCompleteUtConcreteExecutionResult(
37+
stateBefore: EnvironmentModels,
38+
detectedMockingCandidates: Set<MethodId>
39+
) = UtConcreteExecutionResult(
40+
stateBefore = stateBefore,
41+
stateAfter = stateAfter,
42+
result = result,
43+
coverage = coverage,
44+
newInstrumentation = newInstrumentation,
45+
detectedMockingCandidates = detectedMockingCandidates,
4246
)
4347
}
4448

@@ -48,6 +52,7 @@ data class UtConcreteExecutionResult(
4852
val result: UtExecutionResult,
4953
val coverage: Coverage,
5054
val newInstrumentation: List<UtInstrumentation>? = null,
55+
val detectedMockingCandidates: Set<MethodId>,
5156
) {
5257
override fun toString(): String = buildString {
5358
appendLine("UtConcreteExecutionResult(")

utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/InstrumentationContextAwareValueConstructor.kt

Lines changed: 101 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,25 +30,32 @@ import org.utbot.framework.plugin.api.UtReferenceModel
3030
import org.utbot.framework.plugin.api.UtStatementCallModel
3131
import org.utbot.framework.plugin.api.UtStaticMethodInstrumentation
3232
import org.utbot.framework.plugin.api.UtVoidModel
33+
import org.utbot.framework.plugin.api.isNull
3334
import org.utbot.framework.plugin.api.util.anyInstance
35+
import org.utbot.framework.plugin.api.util.classClassId
3436
import org.utbot.framework.plugin.api.util.constructor
3537
import org.utbot.framework.plugin.api.util.constructor.CapturedArgument
3638
import org.utbot.framework.plugin.api.util.constructor.constructLambda
3739
import org.utbot.framework.plugin.api.util.constructor.constructStaticLambda
40+
import org.utbot.framework.plugin.api.util.defaultValueModel
3841
import org.utbot.framework.plugin.api.util.executableId
42+
import org.utbot.framework.plugin.api.util.id
3943
import org.utbot.framework.plugin.api.util.isStatic
4044
import org.utbot.framework.plugin.api.util.jClass
4145
import org.utbot.framework.plugin.api.util.jField
4246
import org.utbot.framework.plugin.api.util.method
47+
import org.utbot.framework.plugin.api.util.stringClassId
4348
import org.utbot.framework.plugin.api.util.utContext
44-
import org.utbot.instrumentation.instrumentation.execution.mock.InstanceMockController
4549
import org.utbot.instrumentation.instrumentation.execution.context.InstrumentationContext
50+
import org.utbot.instrumentation.instrumentation.execution.mock.InstanceMockController
4651
import org.utbot.instrumentation.instrumentation.execution.mock.MethodMockController
4752
import org.utbot.instrumentation.instrumentation.execution.mock.MockController
4853
import org.utbot.instrumentation.process.runSandbox
54+
import java.lang.reflect.Method
4955
import java.lang.reflect.Modifier
56+
import java.security.AccessController
57+
import java.security.PrivilegedAction
5058
import java.util.*
51-
import org.utbot.framework.plugin.api.util.id
5259
import kotlin.reflect.KClass
5360

5461
/**
@@ -63,7 +70,8 @@ import kotlin.reflect.KClass
6370
*/
6471
// TODO: JIRA:1379 -- Refactor ValueConstructor and InstrumentationContextAwareValueConstructor
6572
class InstrumentationContextAwareValueConstructor(
66-
private val instrumentationContext: InstrumentationContext
73+
private val instrumentationContext: InstrumentationContext,
74+
private val idGenerator: StateBeforeAwareIdGenerator,
6775
) {
6876
private val classLoader: ClassLoader
6977
get() = utContext.classLoader
@@ -77,6 +85,8 @@ class InstrumentationContextAwareValueConstructor(
7785
return objectToModel
7886
}
7987

88+
val detectedMockingCandidates: MutableSet<MethodId> = mutableSetOf()
89+
8090
// TODO: JIRA:1379 -- replace UtReferenceModel with Int
8191
private val constructedObjects = HashMap<UtReferenceModel, Any>()
8292

@@ -146,7 +156,7 @@ class InstrumentationContextAwareValueConstructor(
146156
notMockInstance
147157
} else {
148158
val concreteValues = model.mocks.mapValues { mutableListOf<Any?>() }
149-
val mockInstance = generateMockitoMock(javaClass, concreteValues)
159+
val mockInstance = generateMockitoMock(javaClass, concreteValues, model)
150160

151161
constructedObjects[model] = mockInstance
152162

@@ -157,6 +167,12 @@ class InstrumentationContextAwareValueConstructor(
157167
valuesList.addAll(constructedValues)
158168
}
159169

170+
if (model.canHaveRedundantOrMissingMocks) {
171+
// we clear `mocks` to avoid redundant mocks,
172+
// actually useful mocks should be later added back as they are used
173+
model.mocks.clear()
174+
}
175+
160176
mockInstance
161177
}
162178

@@ -194,8 +210,87 @@ class InstrumentationContextAwareValueConstructor(
194210
}
195211
}
196212

197-
private fun generateMockitoMock(clazz: Class<*>, concreteValues: Map<ExecutableId, List<Any?>>): Any {
198-
val answer = generateMockitoAnswer(concreteValues)
213+
private fun generateMockitoAnswerHandlingRedundantAndMissingMocks(
214+
concreteValues: Map<ExecutableId, List<Any?>>,
215+
mockModel: UtCompositeModel
216+
): Answer<*> {
217+
class MockedExecutable(
218+
val executableId: ExecutableId,
219+
val answerValues: List<Any?>,
220+
val answerModels: List<UtModel>,
221+
) {
222+
private var pointer: Int = 0
223+
224+
fun nextAnswer(): Any? {
225+
val answerValue = answerValues[pointer]
226+
val answerModel = answerModels[pointer]
227+
pointer = (pointer + 1) % answerValues.size
228+
(mockModel.mocks.getOrPut(executableId) { mutableListOf() } as MutableList).add(answerModel)
229+
return answerValue
230+
}
231+
}
232+
233+
val mockedExecutables = concreteValues.mapValues { (executableId, values) ->
234+
MockedExecutable(
235+
executableId = executableId,
236+
answerValues = values,
237+
answerModels = mockModel.mocks.getValue(executableId),
238+
)
239+
}.toMutableMap()
240+
241+
return Answer { invocation ->
242+
with(invocation.method) {
243+
mockedExecutables.getOrPut(executableId) {
244+
detectedMockingCandidates.add(executableId)
245+
val answerModel = generateNewAnswerModel()
246+
247+
MockedExecutable(
248+
executableId = executableId,
249+
answerValues = listOf(
250+
// `construct()` heavily uses reflection and Mockito,
251+
// so it can't run in sandbox and we need to elevate permissions
252+
AccessController.doPrivileged(PrivilegedAction { construct(answerModel) })
253+
.value.takeUnless { it == Unit }
254+
),
255+
answerModels = listOf(answerModel)
256+
)
257+
}.nextAnswer()
258+
}
259+
}
260+
}
261+
262+
private fun Method.generateNewAnswerModel() =
263+
executableId.returnType.defaultValueModel().takeUnless { it.isNull() } ?: when (executableId.returnType) {
264+
// mockito can't mock `String` and `Class`
265+
stringClassId -> UtNullModel(stringClassId)
266+
classClassId -> UtClassRefModel(
267+
id = idGenerator.createId(),
268+
classId = classClassId,
269+
value = classClassId,
270+
)
271+
272+
else -> UtCompositeModel(
273+
id = idGenerator.createId(),
274+
// TODO mockito can't mock sealed interfaces,
275+
// we have to mock their implementations or use null
276+
classId = executableId.returnType,
277+
isMock = true,
278+
canHaveRedundantOrMissingMocks = true,
279+
)
280+
}
281+
282+
private fun generateMockitoMock(
283+
clazz: Class<*>,
284+
concreteValues: Map<ExecutableId, List<Any?>>,
285+
mockModel: UtCompositeModel
286+
): Any {
287+
val answer =
288+
if (mockModel.canHaveRedundantOrMissingMocks) {
289+
generateMockitoAnswerHandlingRedundantAndMissingMocks(concreteValues, mockModel)
290+
} else {
291+
generateMockitoAnswer(concreteValues)
292+
}
293+
199294
return Mockito.mock(clazz, answer)
200295
}
201296

utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/UtModelConstructor.kt

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,13 @@ interface UtModelConstructorInterface {
3333
*/
3434
class UtModelConstructor(
3535
private val objectToModelCache: IdentityHashMap<Any, UtModel>,
36+
private val idGenerator: StateBeforeAwareIdGenerator,
3637
private val utModelWithCompositeOriginConstructorFinder: (ClassId) -> UtModelWithCompositeOriginConstructor?,
3738
private val compositeModelStrategy: UtCompositeModelStrategy = AlwaysConstructStrategy,
3839
private val maxDepth: Long = DEFAULT_MAX_DEPTH
3940
) : UtModelConstructorInterface {
4041
private val constructedObjects = IdentityHashMap<Any, UtModel>()
4142

42-
private var unusedId = 0
43-
private val usedIds = objectToModelCache.values
44-
.filterIsInstance<UtReferenceModel>()
45-
.mapNotNull { it.id }
46-
.toMutableSet()
47-
4843
companion object {
4944
private const val DEFAULT_MAX_DEPTH = 7L
5045

@@ -56,16 +51,16 @@ class UtModelConstructor(
5651
val strategy = ConstructOnlyUserClassesOrCachedObjectsStrategy(
5752
pathsToUserClasses, cache
5853
)
59-
return UtModelConstructor(cache, utModelWithCompositeOriginConstructorFinder, strategy)
54+
return UtModelConstructor(
55+
objectToModelCache = cache,
56+
idGenerator = StateBeforeAwareIdGenerator(preExistingModels = emptySet()),
57+
utModelWithCompositeOriginConstructorFinder = utModelWithCompositeOriginConstructorFinder,
58+
compositeModelStrategy = strategy
59+
)
6060
}
6161
}
6262

63-
private fun computeUnusedIdAndUpdate(): Int {
64-
while (unusedId in usedIds) {
65-
unusedId++
66-
}
67-
return unusedId.also { usedIds += it }
68-
}
63+
private fun computeUnusedIdAndUpdate(): Int = idGenerator.createId()
6964

7065
private fun handleId(value: Any): Int {
7166
return objectToModelCache[value]?.let { (it as? UtReferenceModel)?.id } ?: computeUnusedIdAndUpdate()

utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/ModelConstructionPhase.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import org.utbot.framework.plugin.api.visible.UtStreamConsumingException
88
import org.utbot.instrumentation.instrumentation.et.ExplicitThrowInstruction
99
import org.utbot.instrumentation.instrumentation.et.TraceHandler
1010
import org.utbot.instrumentation.instrumentation.execution.PreliminaryUtConcreteExecutionResult
11+
import org.utbot.instrumentation.instrumentation.execution.constructors.StateBeforeAwareIdGenerator
1112
import org.utbot.instrumentation.instrumentation.execution.constructors.UtCompositeModelStrategy
1213
import org.utbot.instrumentation.instrumentation.execution.constructors.UtModelWithCompositeOriginConstructor
1314
import org.utbot.instrumentation.instrumentation.execution.constructors.UtModelConstructor
@@ -20,6 +21,7 @@ import java.util.*
2021
class ModelConstructionPhase(
2122
private val traceHandler: TraceHandler,
2223
private val utModelWithCompositeOriginConstructorFinder: (ClassId) -> UtModelWithCompositeOriginConstructor?,
24+
private val idGenerator: StateBeforeAwareIdGenerator,
2325
) : ExecutionPhase {
2426

2527
override fun wrapError(e: Throwable): ExecutionPhaseException {
@@ -52,6 +54,7 @@ class ModelConstructionPhase(
5254
objectToModelCache = cache,
5355
utModelWithCompositeOriginConstructorFinder = utModelWithCompositeOriginConstructorFinder,
5456
compositeModelStrategy = strategy,
57+
idGenerator = idGenerator,
5558
)
5659
}
5760
}

0 commit comments

Comments
 (0)