Skip to content

Commit 2f05498

Browse files
EgorkaKulikovdenis-fokin
authored andcommitted
Prune branches with unexpected mocks #1560 (#1889)
(cherry picked from commit 511f13e)
1 parent 8644237 commit 2f05498

File tree

14 files changed

+212
-144
lines changed

14 files changed

+212
-144
lines changed

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

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1149,24 +1149,33 @@ open class TypeParameters(val parameters: List<ClassId> = emptyList())
11491149
class WildcardTypeParameter : TypeParameters(emptyList())
11501150

11511151
/**
1152-
* Additional data describing user project.
1153-
*/
1154-
interface ApplicationContext
1155-
1156-
/**
1157-
* A context to use when no additional data is required.
1152+
* A context to use when no specific data is required.
1153+
*
1154+
* @param mockFrameworkInstalled shows if we have installed framework dependencies
1155+
* @param staticsMockingIsConfigured shows if we have installed static mocking tools
11581156
*/
1159-
object EmptyApplicationContext: ApplicationContext
1157+
open class StandardApplicationContext(
1158+
val mockFrameworkInstalled: Boolean = true,
1159+
val staticsMockingIsConfigured: Boolean = true,
1160+
) {
1161+
init {
1162+
if (!mockFrameworkInstalled) {
1163+
require(!staticsMockingIsConfigured) { "Static mocking cannot be used without mock framework" }
1164+
}
1165+
}
1166+
}
11601167

11611168
/**
11621169
* Data we get from Spring application context
11631170
* to manage engine and code generator behaviour.
11641171
*
11651172
* @param beanQualifiedNames describes fqn of injected classes
11661173
*/
1167-
data class SpringApplicationContext(
1174+
class SpringApplicationContext(
1175+
mockInstalled: Boolean,
1176+
staticsMockingIsConfigured: Boolean,
11681177
val beanQualifiedNames: List<String> = emptyList(),
1169-
): ApplicationContext {
1178+
): StandardApplicationContext(mockInstalled, staticsMockingIsConfigured) {
11701179
private val springInjectedClasses: List<ClassId> by lazy {
11711180
beanQualifiedNames.map { fqn -> utContext.classLoader.loadClass(fqn).id }
11721181
}

utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/CommonMocksExampleTest.kt

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,33 @@ package org.utbot.examples.mock
33
import org.utbot.framework.plugin.api.MockStrategyApi
44
import org.junit.jupiter.api.Test
55
import org.utbot.testcheckers.eq
6-
import org.utbot.testing.DoNotCalculate
76
import org.utbot.testing.UtValueTestCaseChecker
7+
import org.utbot.testing.atLeast
88

99
internal class CommonMocksExampleTest: UtValueTestCaseChecker(testClass = CommonMocksExample::class) {
10+
11+
//TODO: coverage values here require further investigation by experts
12+
1013
@Test
11-
fun testMockInterfaceWithoutImplementors() {
14+
fun testMockInterfaceWithoutImplementorsWithNoMocksStrategy() {
15+
checkMocks(
16+
CommonMocksExample::mockInterfaceWithoutImplementors,
17+
eq(1),
18+
{ v, mocks, _ -> v == null && mocks.isEmpty() },
19+
mockStrategy = MockStrategyApi.NO_MOCKS,
20+
coverage = atLeast(75),
21+
)
22+
}
23+
24+
@Test
25+
fun testMockInterfaceWithoutImplementorsWithMockingStrategy() {
1226
checkMocks(
1327
CommonMocksExample::mockInterfaceWithoutImplementors,
1428
eq(2),
1529
{ v, mocks, _ -> v == null && mocks.isEmpty() },
1630
{ _, mocks, _ -> mocks.singleOrNull() != null },
17-
coverage = DoNotCalculate
31+
mockStrategy = MockStrategyApi.OTHER_CLASSES,
32+
coverage = atLeast(75),
1833
)
1934
}
2035

@@ -27,7 +42,7 @@ internal class CommonMocksExampleTest: UtValueTestCaseChecker(testClass = Common
2742
{ fst, _, mocks, _ -> fst == null && mocks.isEmpty() },
2843
{ _, _, mocks, _ -> mocks.isEmpty() }, // should be changed to not null fst when 1449 will be finished
2944
mockStrategy = MockStrategyApi.OTHER_PACKAGES,
30-
coverage = DoNotCalculate
45+
coverage = atLeast(75)
3146
)
3247
}
3348

@@ -42,7 +57,7 @@ internal class CommonMocksExampleTest: UtValueTestCaseChecker(testClass = Common
4257
// node == node.next
4358
// node.next.value == node.value + 1
4459
mockStrategy = MockStrategyApi.OTHER_CLASSES,
45-
coverage = DoNotCalculate
60+
coverage = atLeast(13)
4661
)
4762
}
4863

@@ -53,7 +68,7 @@ internal class CommonMocksExampleTest: UtValueTestCaseChecker(testClass = Common
5368
eq(1),
5469
{ r -> r == -420 },
5570
mockStrategy = MockStrategyApi.OTHER_CLASSES,
56-
coverage = DoNotCalculate
71+
coverage = atLeast(70),
5772
)
5873
}
5974

@@ -63,7 +78,7 @@ internal class CommonMocksExampleTest: UtValueTestCaseChecker(testClass = Common
6378
CommonMocksExample::mocksForNullOfDifferentTypes,
6479
eq(1),
6580
mockStrategy = MockStrategyApi.OTHER_PACKAGES,
66-
coverage = DoNotCalculate
81+
coverage = atLeast(75)
6782
)
6883
}
6984
}

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

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import kotlinx.collections.immutable.persistentListOf
1919
import org.utbot.common.nameOfPackage
2020
import org.utbot.engine.types.OBJECT_TYPE
2121
import org.utbot.engine.util.mockListeners.MockListenerController
22+
import org.utbot.framework.plugin.api.StandardApplicationContext
2223
import org.utbot.framework.plugin.api.util.isInaccessibleViaReflection
2324
import soot.BooleanType
2425
import soot.RefType
@@ -128,6 +129,36 @@ data class UtStaticMethodMockInfo(
128129
val methodId: MethodId
129130
) : UtMockInfo(methodId.classId, addr)
130131

132+
/**
133+
* A wrapper for [ObjectValue] to store additional info.
134+
*/
135+
sealed class MockedObjectInfo {
136+
abstract val value: ObjectValue?
137+
}
138+
139+
object NoMock: MockedObjectInfo() {
140+
override val value: ObjectValue? = null
141+
}
142+
143+
/**
144+
* Represents a mock that occurs when mock strategy allows it
145+
* or when an object type requires always requires mocking.
146+
*
147+
* See [Mocker.mockAlways] for more details.
148+
*/
149+
class ExpectedMock(objectValue: ObjectValue): MockedObjectInfo() {
150+
override val value: ObjectValue = objectValue
151+
}
152+
153+
/**
154+
* Represents a mock that occurs when it is not allowed.
155+
* E.g. mock framework is not installed or
156+
* mock strategy is [MockStrategy.NO_MOCKS] and class is not in [Mocker.mockAlways] set.
157+
*/
158+
class UnexpectedMock(objectValue: ObjectValue): MockedObjectInfo() {
159+
override val value: ObjectValue = objectValue
160+
}
161+
131162
/**
132163
* Service to mock things. Knows mock strategy, class under test and class hierarchy.
133164
*/
@@ -137,21 +168,30 @@ class Mocker(
137168
private val hierarchy: Hierarchy,
138169
chosenClassesToMockAlways: Set<ClassId>,
139170
internal val mockListenerController: MockListenerController? = null,
171+
private val applicationContext: StandardApplicationContext,
140172
) {
173+
private val mocksAreDesired: Boolean = strategy != MockStrategy.NO_MOCKS
174+
141175
/**
142-
* Creates mocked instance of the [type] using mock info if it should be mocked by the mocker,
143-
* otherwise returns null.
176+
* Creates mocked instance (if it should be mocked by the mocker) of the [type] using [mockInfo]
177+
* otherwise returns [NoMock].
144178
*
145179
* @see shouldMock
146180
*/
147-
fun mock(type: RefType, mockInfo: UtMockInfo): ObjectValue? =
148-
if (shouldMock(type, mockInfo)) createMockObject(type, mockInfo) else null
181+
fun mock(type: RefType, mockInfo: UtMockInfo): MockedObjectInfo {
182+
val objectValue = if (shouldMock(type, mockInfo)) createMockObject(type, mockInfo) else null
183+
return construct(objectValue, mockInfo)
184+
}
149185

150186
/**
151-
* Creates mocked instance of the [type] using mock info. Unlike to [mock], it does not
152-
* check anything and always returns the constructed mock.
187+
* Unlike to [mock], unconditionally creates a mocked instance of the [type] using [mockInfo].
153188
*/
154-
fun forceMock(type: RefType, mockInfo: UtMockInfo): ObjectValue = createMockObject(type, mockInfo)
189+
fun forceMock(type: RefType, mockInfo: UtMockInfo): MockedObjectInfo {
190+
mockListenerController?.onShouldMock(strategy, mockInfo)
191+
192+
val objectValue = createMockObject(type, mockInfo)
193+
return construct(objectValue, mockInfo)
194+
}
155195

156196
/**
157197
* Checks if Engine should mock objects of particular type with current mock strategy and mock type.
@@ -177,6 +217,30 @@ class Mocker(
177217
}
178218
}
179219

220+
/**
221+
* Constructs [MockedObjectInfo]: enriches given [mockedValue] with an information if mocking is expected or not.
222+
*/
223+
private fun construct(mockedValue: ObjectValue?, mockInfo: UtMockInfo): MockedObjectInfo {
224+
if (mockedValue == null) {
225+
return NoMock
226+
}
227+
228+
val mockingIsPossible = when (mockInfo) {
229+
is UtFieldMockInfo,
230+
is UtObjectMockInfo -> applicationContext.mockFrameworkInstalled
231+
is UtNewInstanceMockInfo,
232+
is UtStaticMethodMockInfo,
233+
is UtStaticObjectMockInfo -> applicationContext.staticsMockingIsConfigured
234+
}
235+
val mockingIsForcedAndPossible = mockAlways(mockedValue.type) && mockingIsPossible
236+
237+
return if (mocksAreDesired || mockingIsForcedAndPossible) {
238+
ExpectedMock(mockedValue)
239+
} else {
240+
UnexpectedMock(mockedValue)
241+
}
242+
}
243+
180244
private fun checkIfShouldMock(
181245
type: RefType,
182246
mockInfo: UtMockInfo

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

Lines changed: 63 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -116,12 +116,11 @@ import org.utbot.framework.UtSettings
116116
import org.utbot.framework.UtSettings.maximizeCoverageUsingReflection
117117
import org.utbot.framework.UtSettings.preferredCexOption
118118
import org.utbot.framework.UtSettings.substituteStaticsWithSymbolicVariable
119-
import org.utbot.framework.plugin.api.ApplicationContext
119+
import org.utbot.framework.plugin.api.StandardApplicationContext
120120
import org.utbot.framework.plugin.api.ClassId
121121
import org.utbot.framework.plugin.api.ExecutableId
122122
import org.utbot.framework.plugin.api.FieldId
123123
import org.utbot.framework.plugin.api.MethodId
124-
import org.utbot.framework.plugin.api.SpringApplicationContext
125124
import org.utbot.framework.plugin.api.classId
126125
import org.utbot.framework.plugin.api.id
127126
import org.utbot.framework.plugin.api.util.executable
@@ -240,7 +239,7 @@ class Traverser(
240239
internal val typeResolver: TypeResolver,
241240
private val globalGraph: InterProceduralUnitGraph,
242241
private val mocker: Mocker,
243-
private val applicationContext: ApplicationContext?,
242+
private val applicationContext: StandardApplicationContext?,
244243
) : UtContextInitializer() {
245244

246245
private val visitedStmts: MutableSet<Stmt> = mutableSetOf()
@@ -277,8 +276,6 @@ class Traverser(
277276

278277
internal val objectCounter = ObjectCounter(TypeRegistry.objectCounterInitialValue)
279278

280-
281-
282279
private fun findNewAddr(insideStaticInitializer: Boolean): UtAddrExpression {
283280
val newAddr = objectCounter.createNewAddr()
284281
// return negative address for objects created inside static initializer
@@ -1383,8 +1380,17 @@ class Traverser(
13831380

13841381
queuedSymbolicStateUpdates += MemoryUpdate(addrToMockInfo = persistentHashMapOf(addr to mockInfo))
13851382

1386-
val mockedObject = mocker.mock(type, mockInfo)
1383+
val mockedObjectInfo = mocker.mock(type, mockInfo)
1384+
1385+
if (mockedObjectInfo is UnexpectedMock) {
1386+
// if mock occurs, but it is unexpected due to some reasons
1387+
// (e.g. we do not have mock framework installed),
1388+
// we can only generate a test that uses null value for mocked object
1389+
queuedSymbolicStateUpdates += nullEqualityConstraint.asHardConstraint()
1390+
return mockedObjectInfo.value
1391+
}
13871392

1393+
val mockedObject = mockedObjectInfo.value
13881394
if (mockedObject != null) {
13891395
queuedSymbolicStateUpdates += MemoryUpdate(mockInfos = persistentListOf(MockInfoEnriched(mockInfo)))
13901396

@@ -1470,7 +1476,24 @@ class Traverser(
14701476
}
14711477

14721478
val mockInfo = mockInfoGenerator.generate(addr)
1473-
val mockedObject = mocker.forceMock(type, mockInfoGenerator.generate(addr))
1479+
val mockedObjectInfo = mocker.forceMock(type, mockInfoGenerator.generate(addr))
1480+
1481+
val mockedObject: ObjectValue = when (mockedObjectInfo) {
1482+
is NoMock -> error("Value must be mocked after the force mock")
1483+
is ExpectedMock -> mockedObjectInfo.value
1484+
is UnexpectedMock -> {
1485+
// if mock occurs, but it is unexpected due to some reasons
1486+
// (e.g. we do not have mock framework installed),
1487+
// we can only generate a test that uses null value for mocked object
1488+
queuedSymbolicStateUpdates += nullEqualityConstraint.asHardConstraint()
1489+
1490+
mockedObjectInfo.value
1491+
}
1492+
}
1493+
1494+
if (mockedObjectInfo is UnexpectedMock) {
1495+
return mockedObject
1496+
}
14741497

14751498
queuedSymbolicStateUpdates += MemoryUpdate(mockInfos = persistentListOf(MockInfoEnriched(mockInfo)))
14761499

@@ -2675,7 +2698,7 @@ class Traverser(
26752698
.map { (method, implementationClass, possibleTypes) ->
26762699
val typeStorage = typeResolver.constructTypeStorage(implementationClass, possibleTypes)
26772700
val mockInfo = memory.mockInfoByAddr(instance.addr)
2678-
val mockedObject = mockInfo?.let {
2701+
val mockedObjectInfo = mockInfo?.let {
26792702
// TODO rewrite to fix JIRA:1611
26802703
val type = Scene.v().getSootClass(mockInfo.classId.name).type
26812704
val ancestorTypes = typeResolver.findOrConstructAncestorsIncludingTypes(type)
@@ -2684,28 +2707,42 @@ class Traverser(
26842707
} else {
26852708
it.copyWithClassId(classId = implementationClass.id)
26862709
}
2687-
mocker.mock(implementationClass, updatedMockInfo)
2688-
}
2689-
2690-
if (mockedObject == null) {
2691-
// Above we might get implementationClass that has to be substituted.
2692-
// For example, for a call "Collection.size()" such classes will be produced.
2693-
val wrapperOrInstance = wrapper(implementationClass, instance.addr)
2694-
?: instance.copy(typeStorage = typeStorage)
26952710

2696-
val typeConstraint = typeRegistry.typeConstraint(instance.addr, wrapperOrInstance.typeStorage)
2697-
val constraints = setOf(typeConstraint.isOrNullConstraint())
2711+
mocker.mock(implementationClass, updatedMockInfo)
2712+
} ?: NoMock
26982713

2699-
// TODO add memory updated for types JIRA:1523
2714+
when (mockedObjectInfo) {
2715+
is NoMock -> {
2716+
// Above we might get implementationClass that has to be substituted.
2717+
// For example, for a call "Collection.size()" such classes will be produced.
2718+
val wrapperOrInstance = wrapper(implementationClass, instance.addr)
2719+
?: instance.copy(typeStorage = typeStorage)
27002720

2701-
InvocationTarget(wrapperOrInstance, method, constraints)
2702-
} else {
2703-
val typeConstraint = typeRegistry.typeConstraint(mockedObject.addr, mockedObject.typeStorage)
2704-
val constraints = setOf(typeConstraint.isOrNullConstraint())
2721+
val typeConstraint = typeRegistry.typeConstraint(instance.addr, wrapperOrInstance.typeStorage)
2722+
val constraints = setOf(typeConstraint.isOrNullConstraint())
27052723

2706-
// TODO add memory updated for types JIRA:1523
2707-
// TODO isMock????
2708-
InvocationTarget(mockedObject, method, constraints)
2724+
// TODO add memory updated for types JIRA:1523
2725+
InvocationTarget(wrapperOrInstance, method, constraints)
2726+
}
2727+
is ExpectedMock -> {
2728+
val mockedObject = mockedObjectInfo.value
2729+
val typeConstraint = typeRegistry.typeConstraint(mockedObject.addr, mockedObject.typeStorage)
2730+
val constraints = setOf(typeConstraint.isOrNullConstraint())
2731+
2732+
// TODO add memory updated for types JIRA:1523
2733+
// TODO isMock????
2734+
InvocationTarget(mockedObject, method, constraints)
2735+
}
2736+
/*
2737+
Currently, it is unclear how this could happen.
2738+
Perhaps, the answer is somewhere in the following situation:
2739+
you have an interface with an abstract method `foo`, and it has an abstract inheritor with the implementation of the method,
2740+
but this inheritor doesn't have any concrete inheritors. It looks like in this case we would mock this instance
2741+
(because it doesn't have any possible concrete type), but it is impossible since either this class cannot present
2742+
in possible types of the object on which we call `foo` (since they contain only concrete types),
2743+
or this class would be already mocked (since it doesn't contain any concrete implementors).
2744+
*/
2745+
is UnexpectedMock -> unreachableBranch("If it ever happens, it should be investigated")
27092746
}
27102747
}
27112748
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ class UtBotSymbolicEngine(
105105
dependencyPaths: String,
106106
val mockStrategy: MockStrategy = NO_MOCKS,
107107
chosenClassesToMockAlways: Set<ClassId>,
108-
applicationContext: ApplicationContext?,
108+
applicationContext: StandardApplicationContext,
109109
private val solverTimeoutInMillis: Int = checkSolverTimeoutMillis
110110
) : UtContextInitializer() {
111111
private val graph = methodUnderTest.sootMethod.jimpleBody().apply {
@@ -129,7 +129,8 @@ class UtBotSymbolicEngine(
129129
classUnderTest,
130130
hierarchy,
131131
chosenClassesToMockAlways,
132-
MockListenerController(controller)
132+
MockListenerController(controller),
133+
applicationContext = applicationContext,
133134
)
134135

135136
fun attachMockListener(mockListener: MockListener) = mocker.mockListenerController?.attach(mockListener)

0 commit comments

Comments
 (0)