Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Other class is not mocked as required #747
  • Loading branch information
Markoutte committed Oct 11, 2022
commit 14f4b94bc25f6c7eb2040d63852e3589132e8e9d
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ class UtBotSymbolicEngine(
private val methodUnderTest: ExecutableId,
classpath: String,
dependencyPaths: String,
mockStrategy: MockStrategy = NO_MOCKS,
val mockStrategy: MockStrategy = NO_MOCKS,
chosenClassesToMockAlways: Set<ClassId>,
private val solverTimeoutInMillis: Int = checkSolverTimeoutMillis
) : UtContextInitializer() {
Expand Down Expand Up @@ -429,6 +429,7 @@ class UtBotSymbolicEngine(
val names = graph.body.method.tags.filterIsInstance<ParamNamesTag>().firstOrNull()?.names
parameterNameMap = { index -> names?.getOrNull(index) }
fuzzerType = { try { toFuzzerType(methodUnderTest.executable.genericParameterTypes[it]) } catch (_: Throwable) { null } }
shouldMock = { mockStrategy.eligibleToMock(it, classUnderTest) }
}
val coveredInstructionTracker = Trie(Instruction::id)
val coveredInstructionValues = linkedMapOf<Trie.Node<Instruction>, List<FuzzedValue>>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ class FuzzedMethodDescription(
*/
var fuzzerType: (Int) -> FuzzedType? = { null }

/**
* Returns true if class should be mocked.
*/
var shouldMock: (ClassId) -> Boolean = { false }

/**
* Map class id to indices of this class in parameters list.
*/
Expand Down
6 changes: 5 additions & 1 deletion utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import org.utbot.framework.plugin.api.util.voidClassId
import org.utbot.fuzzer.mutators.NumberRandomMutator
import org.utbot.fuzzer.mutators.RegexStringModelMutator
import org.utbot.fuzzer.mutators.StringRandomMutator
import org.utbot.fuzzer.objects.replaceToMock
import org.utbot.fuzzer.providers.ArrayModelProvider
import org.utbot.fuzzer.providers.CharToStringModelProvider
import org.utbot.fuzzer.providers.CollectionWithEmptyStatesModelProvider
Expand Down Expand Up @@ -117,7 +118,10 @@ fun fuzz(description: FuzzedMethodDescription, vararg modelProviders: ModelProvi
val values = List<MutableList<FuzzedValue>>(description.parameters.size) { mutableListOf() }
modelProviders.forEach { fuzzingProvider ->
fuzzingProvider.generate(description).forEach { (index, model) ->
values[index].add(model)
val mock = replaceToMock(model.model, description.shouldMock)
values[index].add(FuzzedValue(mock, model.createdBy).apply {
summary = model.summary
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it not a part of the constructor?

})
}
}
description.parameters.forEachIndexed { index, classId ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ package org.utbot.fuzzer.objects
import org.utbot.framework.plugin.api.ClassId
import org.utbot.framework.plugin.api.ConstructorId
import org.utbot.framework.plugin.api.ExecutableId
import org.utbot.framework.plugin.api.FieldId
import org.utbot.framework.plugin.api.MethodId
import org.utbot.framework.plugin.api.UtAssembleModel
import org.utbot.framework.plugin.api.UtCompositeModel
import org.utbot.framework.plugin.api.UtDirectSetFieldModel
import org.utbot.framework.plugin.api.UtExecutableCallModel
import org.utbot.framework.plugin.api.UtModel
import org.utbot.framework.plugin.api.UtStatementModel
Expand All @@ -25,6 +28,43 @@ fun ModelProvider.assembleModel(id: Int, constructorId: ConstructorId, params: L
}
}

fun replaceToMock(assembleModel: UtModel, shouldMock: (ClassId) -> Boolean): UtModel {
if (assembleModel !is UtAssembleModel) return assembleModel
if (shouldMock(assembleModel.classId)) {
return UtCompositeModel(assembleModel.id, assembleModel.classId, true).apply {
assembleModel.modificationsChain.forEach {
if (it is UtDirectSetFieldModel) {
fields[it.fieldId] = replaceToMock(it.fieldModel, shouldMock)
}
if (it is UtExecutableCallModel && it.executable is FuzzerMockableMethodId) {
(it.executable as FuzzerMockableMethodId).mock().forEach { (executionId, models) ->
mocks[executionId] = models.map { p -> replaceToMock(p, shouldMock) }
}
}
}
}
} else {
val models = assembleModel.modificationsChain.map { call ->
var mockedStatementModel: UtStatementModel? = null
if (call is UtDirectSetFieldModel) {
val mock = replaceToMock(call.fieldModel, shouldMock)
if (mock != call.fieldModel) {
mockedStatementModel = UtDirectSetFieldModel(call.instance, call.fieldId, mock)
}
} else if (call is UtExecutableCallModel) {
val params = call.params.map { m -> replaceToMock(m, shouldMock) }
if (params != call.params) {
mockedStatementModel = UtExecutableCallModel(call.instance, call.executable, params)
}
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As for me, it is hard to read because of missing spaces. My suggestion is something like:

fun replaceToMock(
    assembleModel: UtModel,
    shouldMock: (ClassId) -> Boolean
): UtModel = with(assembleModel) {
    if (this !is UtAssembleModel) return this

    if (shouldMock(classId)) {
        UtCompositeModel(id, classId, isMock = true).apply {
            modificationsChain.forEach {
                if (it is UtDirectSetFieldModel) {
                    fields[it.fieldId] = replaceToMock(it.fieldModel, shouldMock)
                }

                if (it is UtExecutableCallModel && it.executable is FuzzerMockableMethodId) {
                    (it.executable as FuzzerMockableMethodId).mock().forEach { (executionId, models) ->
                        mocks[executionId] = models.map { p -> replaceToMock(p, shouldMock) }
                    }
                }
            }
        }
    } else {
        val models = modificationsChain.map { call ->
            var mockedStatementModel: UtStatementModel? = null

            if (call is UtDirectSetFieldModel) {
                val mock = replaceToMock(call.fieldModel, shouldMock)

                if (mock != call.fieldModel) {
                    mockedStatementModel = UtDirectSetFieldModel(call.instance, call.fieldId, mock)
                }
            } else if (call is UtExecutableCallModel) {
                val params = call.params.map { m -> replaceToMock(m, shouldMock) }

                if (params != call.params) {
                    mockedStatementModel = UtExecutableCallModel(call.instance, call.executable, params)
                }
            }

            mockedStatementModel ?: call
        }

        with(assembleModel) {
            UtAssembleModel(id, classId, modelName, instantiationCall, origin) { models }
        }
    }
}

mockedStatementModel ?: call
}
return with(assembleModel) {
UtAssembleModel(id, classId, modelName, instantiationCall, origin) { models }
}
}
}

fun ClassId.create(
block: AssembleModelDsl.() -> Unit
): UtAssembleModel {
Expand All @@ -38,6 +78,7 @@ class AssembleModelDsl internal constructor(
val call = KeyWord.Call
val constructor = KeyWord.Constructor(classId)
val method = KeyWord.Method(classId)
val field = KeyWord.Field(classId)

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

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

infix fun <T : FieldId> KeyWord.Call.instance(field: T) = FieldDsl(field, false)

infix fun <T : ExecutableId> KeyWord.Using.static(executableId: T) = UsingDsl(executableId)

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

infix fun <T : FieldId> KeyWord.Call.static(field: T) = FieldDsl(field, true)

@Suppress("UNUSED_PARAMETER")
infix fun KeyWord.Using.empty(ignored: KeyWord.Constructor) {
initialization = { UtExecutableCallModel(null, ConstructorId(classId, emptyList()), emptyList()) }
}
Expand All @@ -73,6 +119,10 @@ class AssembleModelDsl internal constructor(
modChain += { UtExecutableCallModel(it, executableId, models.toList()) }
}

infix fun FieldDsl.with(model: UtModel) {
modChain += { UtDirectSetFieldModel(it, fieldId, model) }
}

internal fun build(): UtAssembleModel {
val objectId = id()
return UtAssembleModel(
Expand Down Expand Up @@ -102,8 +152,14 @@ class AssembleModelDsl internal constructor(
return MethodId(classId, name, returns, params)
}
}
class Field(val classId: ClassId) : KeyWord() {
operator fun invoke(name: String): FieldId {
return FieldId(classId, name)
}
}
}

class UsingDsl(val executableId: ExecutableId)
class CallDsl(val executableId: ExecutableId, val isStatic: Boolean)
class FieldDsl(val fieldId: FieldId, val isStatic: Boolean)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.utbot.fuzzer.objects

import org.utbot.framework.plugin.api.ClassId
import org.utbot.framework.plugin.api.ExecutableId
import org.utbot.framework.plugin.api.MethodId
import org.utbot.framework.plugin.api.UtModel

/**
* Implements [MethodId] but also can supply a mock for this execution.
*
* Simplest example: setter and getter,
* when this methodId is a setter, getter can be used for a mock to supply correct value.
*/
internal class FuzzerMockableMethodId(
classId: ClassId,
name: String,
returnType: ClassId,
parameters: List<ClassId>,
val mock: () -> Map<ExecutableId, List<UtModel>> = { emptyMap() },
) : MethodId(classId, name, returnType, parameters) {

constructor(copyOf: MethodId, mock: () -> Map<ExecutableId, List<UtModel>> = { emptyMap() }) : this(
copyOf.classId, copyOf.name, copyOf.returnType, copyOf.parameters, mock
)

}

internal fun MethodId.toFuzzerMockable(block: suspend SequenceScope<Pair<MethodId, List<UtModel>>>.() -> Unit): FuzzerMockableMethodId {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you make a data class for these pairs?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Pair" is used as convenient and well-known construction like "key to value". Because this method is used by dsl I'd prefer to keep it this way.

Also, I have some doubt about using data classes for such small pairs. Maybe, typealias is more proper way in these cases?

return FuzzerMockableMethodId(this) {
sequence { block() }.toMap()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import org.utbot.fuzzer.FuzzedMethodDescription
import org.utbot.fuzzer.FuzzedType
import org.utbot.fuzzer.FuzzedValue
import org.utbot.fuzzer.IdentityPreservingIdGenerator
import org.utbot.fuzzer.objects.FuzzerMockableMethodId
import org.utbot.fuzzer.objects.assembleModel

/**
Expand Down Expand Up @@ -101,11 +102,22 @@ class ObjectModelProvider(
)
field.setter != null -> UtExecutableCallModel(
fuzzedModel.model,
MethodId(
FuzzerMockableMethodId(
constructorId.classId,
field.setter.name,
field.setter.returnType.id,
listOf(field.classId)
listOf(field.classId),
mock = {
field.getter?.let { g ->
val getterMethodID = MethodId(
constructorId.classId,
g.name,
g.returnType.id,
emptyList()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, use a named argument here

)
mapOf(getterMethodID to listOf(value.model))
} ?: emptyMap()
}
),
listOf(value.model)
)
Expand Down Expand Up @@ -144,16 +156,18 @@ class ObjectModelProvider(
private fun findSuitableFields(classId: ClassId, description: FuzzedMethodDescription): List<FieldDescription> {
val jClass = classId.jClass
return jClass.declaredFields.map { field ->
val setterAndGetter = jClass.findPublicSetterGetterIfHasPublicGetter(field, description)
FieldDescription(
field.name,
field.type.id,
isAccessible(field, description.packageName) && !isFinal(field.modifiers) && !isStatic(field.modifiers),
jClass.findPublicSetterIfHasPublicGetter(field, description)
setterAndGetter?.first,
setterAndGetter?.second,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here (a data class). From a browser, it's not obvious which one is what.

)
}
}

private fun Class<*>.findPublicSetterIfHasPublicGetter(field: Field, description: FuzzedMethodDescription): Method? {
private fun Class<*>.findPublicSetterGetterIfHasPublicGetter(field: Field, description: FuzzedMethodDescription): Pair<Method, Method>? {
val postfixName = field.name.capitalize()
val setterName = "set$postfixName"
val getterName = "get$postfixName"
Expand All @@ -164,7 +178,7 @@ class ObjectModelProvider(
it.name == setterName &&
it.parameterCount == 1 &&
it.parameterTypes[0] == field.type
}
}?.let { it to getter }
} else {
null
}
Expand All @@ -184,6 +198,7 @@ class ObjectModelProvider(
val classId: ClassId,
val canBeSetDirectly: Boolean,
val setter: Method?,
val getter: Method?
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package org.utbot.framework.plugin.api

import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test
import org.utbot.framework.plugin.api.util.UtContext
import org.utbot.framework.plugin.api.util.doubleWrapperClassId
import org.utbot.framework.plugin.api.util.id
import org.utbot.framework.plugin.api.util.voidClassId
import org.utbot.framework.plugin.api.util.withUtContext
import org.utbot.fuzzer.FuzzedMethodDescription
import org.utbot.fuzzer.objects.create
import org.utbot.fuzzer.objects.replaceToMock
import org.utbot.fuzzer.objects.toFuzzerMockable
import org.utbot.fuzzer.providers.ObjectModelProvider

class MockOfObjectModelProviderTest {

class Some {
@Suppress("unused")
var another: Some? = null
}

@Test
fun `no mock is generated by default`() = withContext {
val description = FuzzedMethodDescription("test", voidClassId, listOf(Some::class.id))
val provider = ObjectModelProvider(TestIdentityPreservingIdGenerator)
val results = provider.generate(description).map { it.value.model }.map {
replaceToMock(it) { m -> description.shouldMock(m) }
}.toList()
assertEquals(2, results.size)
results.forEach { model ->
assertInstanceOf(UtAssembleModel::class.java, model)
}
assertEquals(0, (results[0] as UtAssembleModel).modificationsChain.size)
assertEquals(1, (results[1] as UtAssembleModel).modificationsChain.size)
}

@Test
fun `mock is generated`() = withContext {
val description = FuzzedMethodDescription("test", voidClassId, listOf(Some::class.id)).apply {
shouldMock = { true }
}
val provider = ObjectModelProvider(TestIdentityPreservingIdGenerator)
val results = provider.generate(description).map { it.value.model }.map {
replaceToMock(it) { m -> description.shouldMock(m) }
}.toList()
assertEquals(2, results.size)
results.forEach { model ->
assertInstanceOf(UtCompositeModel::class.java, model)
assertTrue((model as UtCompositeModel).isMock)
}
}

@Test
fun `mock is generated for several recursion level`() = withContext {
val description = FuzzedMethodDescription("test", voidClassId, listOf(Some::class.id)).apply {
shouldMock = { true }
}
val provider = ObjectModelProvider(TestIdentityPreservingIdGenerator, recursionDepthLeft = 2)
val results = provider.generate(description).map { it.value.model }.map {
replaceToMock(it) { m -> description.shouldMock(m) }
}.toList()
assertEquals(2, results.size)
results.forEach { model ->
assertInstanceOf(UtCompositeModel::class.java, model)
assertTrue((model as UtCompositeModel).isMock)
}
val modelWithFieldChanger = results[1] as UtCompositeModel
assertEquals(1, modelWithFieldChanger.mocks.size)
val entry = modelWithFieldChanger.mocks.entries.single()
assertEquals("getAnother", entry.key.name)
assertEquals(Some::class.id, entry.key.returnType)
assertEquals(1, entry.value.size)
assertInstanceOf(UtCompositeModel::class.java, entry.value.single())
}

@Test
fun `check field replaced with concrete values`() {
val customModel = Any::class.id.create {
using empty constructor
call instance field("some") with UtNullModel(Nothing::class.id)
}
val replacedModel = replaceToMock(customModel) { true }
assertInstanceOf(UtCompositeModel::class.java, replacedModel)
replacedModel as UtCompositeModel
assertEquals(0, replacedModel.mocks.size)
val fields = replacedModel.fields
assertEquals(1, fields.size)
val entry = fields.entries.single()
assertEquals("some", entry.key.name)
assertEquals(UtNullModel(Nothing::class.id), entry.value)
}

@Test
fun `check method replaced with mock values`() {
val customModel = Any::class.id.create {
using empty constructor
call instance method("some").toFuzzerMockable {
yield(MethodId(classId, "another", doubleWrapperClassId, emptyList()) to listOf(UtPrimitiveModel(2.0)))
} with values(UtNullModel(Nothing::class.id))
}
val replacedModel = replaceToMock(customModel) { true }
assertInstanceOf(UtCompositeModel::class.java, replacedModel)
replacedModel as UtCompositeModel
assertEquals(0, replacedModel.fields.size)
val mocks = replacedModel.mocks
assertEquals(1, replacedModel.mocks.size)
val (executableId, models) = mocks.entries.single()
assertEquals("another", executableId.name)
assertEquals(doubleWrapperClassId, executableId.returnType)
assertEquals(0, executableId.parameters.size)
assertEquals(1, models.size)
assertInstanceOf(UtPrimitiveModel::class.java, models.single())
assertEquals(2.0, (models.single() as UtPrimitiveModel).value)
}

private fun <T> withContext(block: () -> T) {
withUtContext(UtContext(this::class.java.classLoader)) {
block()
}
}

}