diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt index 86083c30b3..03f56a598f 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt @@ -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, private val solverTimeoutInMillis: Int = checkSolverTimeoutMillis ) : UtContextInitializer() { @@ -429,6 +429,7 @@ class UtBotSymbolicEngine( val names = graph.body.method.tags.filterIsInstance().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, List>() diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedMethodDescription.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedMethodDescription.kt index 054a564e1a..0d87b21210 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedMethodDescription.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedMethodDescription.kt @@ -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. */ diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedValue.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedValue.kt index 55e78704a9..1da79b63e8 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedValue.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedValue.kt @@ -5,22 +5,19 @@ import org.utbot.framework.plugin.api.UtModel /** * Fuzzed Value stores information about concrete UtModel, reference to [ModelProvider] * and reasons about why this value was generated. + * + * [summary] is a piece of useful information that clarify why this value has a concrete value. + * + * It supports a special character `%var%` that is used as a placeholder for parameter name. + * + * For example: + * 1. `%var% = 2` for a value that have value 2 + * 2. `%var% >= 4` for a value that shouldn't be less than 4 + * 3. `foo(%var%) returns true` for values that should be passed as a function parameter + * 4. `%var% has special characters` to describe content */ open class FuzzedValue( val model: UtModel, val createdBy: ModelProvider? = null, -) { - - /** - * Summary is a piece of useful information that clarify why this value has a concrete value. - * - * It supports a special character `%var%` that is used as a placeholder for parameter name. - * - * For example: - * 1. `%var% = 2` for a value that have value 2 - * 2. `%var% >= 4` for a value that shouldn't be less than 4 - * 3. `foo(%var%) returns true` for values that should be passed as a function parameter - * 4. `%var% has special characters` to describe content - */ - var summary: String? = null -} \ No newline at end of file + var summary: String? = null, +) \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt index 7f0f699511..e9ebfca2e9 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt @@ -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.replaceWithMock import org.utbot.fuzzer.providers.ArrayModelProvider import org.utbot.fuzzer.providers.CharToStringModelProvider import org.utbot.fuzzer.providers.CollectionWithEmptyStatesModelProvider @@ -117,7 +118,10 @@ fun fuzz(description: FuzzedMethodDescription, vararg modelProviders: ModelProvi val values = List>(description.parameters.size) { mutableListOf() } modelProviders.forEach { fuzzingProvider -> fuzzingProvider.generate(description).forEach { (index, model) -> - values[index].add(model) + val mock = replaceWithMock(model.model, description.shouldMock) + values[index].add(FuzzedValue(mock, model.createdBy).apply { + summary = model.summary + }) } } description.parameters.forEachIndexed { index, classId -> diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/objects/AssembleModelUtils.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/objects/AssembleModelUtils.kt index 0f05bacf8e..d17292fe55 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/objects/AssembleModelUtils.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/objects/AssembleModelUtils.kt @@ -3,8 +3,10 @@ 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.UtDirectSetFieldModel import org.utbot.framework.plugin.api.UtExecutableCallModel import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtStatementModel @@ -38,6 +40,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 = { "" } @@ -53,10 +56,15 @@ class AssembleModelDsl internal constructor( infix fun KeyWord.Call.instance(executableId: T) = CallDsl(executableId, false) + infix fun KeyWord.Call.instance(field: T) = FieldDsl(field, false) + infix fun KeyWord.Using.static(executableId: T) = UsingDsl(executableId) infix fun KeyWord.Call.static(executableId: T) = CallDsl(executableId, true) + infix fun 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()) } } @@ -73,6 +81,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( @@ -102,8 +114,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) } \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/objects/FuzzerMockUtils.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/objects/FuzzerMockUtils.kt new file mode 100644 index 0000000000..e2c97d0e43 --- /dev/null +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/objects/FuzzerMockUtils.kt @@ -0,0 +1,82 @@ +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.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 + +/** + * 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, + val mock: () -> Map> = { emptyMap() }, +) : MethodId(classId, name, returnType, parameters) { + + constructor(copyOf: MethodId, mock: () -> Map> = { emptyMap() }) : this( + copyOf.classId, copyOf.name, copyOf.returnType, copyOf.parameters, mock + ) + +} + +internal fun MethodId.toFuzzerMockable(block: suspend SequenceScope>>.() -> Unit): FuzzerMockableMethodId { + return FuzzerMockableMethodId(this) { + sequence { block() }.toMap() + } +} + +internal fun replaceWithMock(assembleModel: UtModel, shouldMock: (ClassId) -> Boolean): UtModel = when { + assembleModel !is UtAssembleModel -> assembleModel + shouldMock(assembleModel.classId) -> createMockModelFromFuzzerMockable(assembleModel, shouldMock) + else -> updateInnerModels(assembleModel, shouldMock) +} + +private fun createMockModelFromFuzzerMockable(model: UtAssembleModel, shouldMock: (ClassId) -> Boolean): UtCompositeModel { + val mock = UtCompositeModel(model.id, model.classId, true) + for (mutator in model.modificationsChain) { + if (mutator is UtDirectSetFieldModel) { + mock.fields[mutator.fieldId] = replaceWithMock(mutator.fieldModel, shouldMock) + } + if (mutator is UtExecutableCallModel && mutator.executable is FuzzerMockableMethodId) { + (mutator.executable as FuzzerMockableMethodId).mock().forEach { (executionId, models) -> + mock.mocks[executionId] = models.map { p -> replaceWithMock(p, shouldMock) } + } + } + } + return mock +} + +private fun updateInnerModels(model: UtAssembleModel, shouldMock: (ClassId) -> Boolean): UtAssembleModel { + val models = model.modificationsChain.map { call -> + var mockedStatementModel: UtStatementModel? = null + when (call) { + is UtDirectSetFieldModel -> { + val mock = replaceWithMock(call.fieldModel, shouldMock) + if (mock != call.fieldModel) { + mockedStatementModel = UtDirectSetFieldModel(call.instance, call.fieldId, mock) + } + } + is UtExecutableCallModel -> { + val params = call.params.map { m -> replaceWithMock(m, shouldMock) } + if (params != call.params) { + mockedStatementModel = UtExecutableCallModel(call.instance, call.executable, params) + } + } + } + mockedStatementModel ?: call + } + return with(model) { + UtAssembleModel(id, classId, modelName, instantiationCall, origin) { models } + } +} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ObjectModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ObjectModelProvider.kt index 8c612e3866..10b329d8f1 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ObjectModelProvider.kt +++ b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ObjectModelProvider.kt @@ -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 /** @@ -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( + classId = constructorId.classId, + name = g.name, + returnType = g.returnType.id, + parameters = emptyList() + ) + mapOf(getterMethodID to listOf(value.model)) + } ?: emptyMap() + } ), listOf(value.model) ) @@ -144,16 +156,23 @@ class ObjectModelProvider( private fun findSuitableFields(classId: ClassId, description: FuzzedMethodDescription): List { 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) + name = field.name, + classId = field.type.id, + canBeSetDirectly = isAccessible(field, description.packageName) && !isFinal(field.modifiers) && !isStatic(field.modifiers), + setter = setterAndGetter?.setter, + getter = setterAndGetter?.getter, ) } } - private fun Class<*>.findPublicSetterIfHasPublicGetter(field: Field, description: FuzzedMethodDescription): Method? { + private class PublicSetterGetter( + val setter: Method, + val getter: Method, + ) + + private fun Class<*>.findPublicSetterGetterIfHasPublicGetter(field: Field, description: FuzzedMethodDescription): PublicSetterGetter? { val postfixName = field.name.capitalize() val setterName = "set$postfixName" val getterName = "get$postfixName" @@ -164,7 +183,7 @@ class ObjectModelProvider( it.name == setterName && it.parameterCount == 1 && it.parameterTypes[0] == field.type - } + }?.let { PublicSetterGetter(it, getter) } } else { null } @@ -184,6 +203,7 @@ class ObjectModelProvider( val classId: ClassId, val canBeSetDirectly: Boolean, val setter: Method?, + val getter: Method? ) } } \ No newline at end of file diff --git a/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/MockOfObjectModelProviderTest.kt b/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/MockOfObjectModelProviderTest.kt new file mode 100644 index 0000000000..d24e85e98e --- /dev/null +++ b/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/MockOfObjectModelProviderTest.kt @@ -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.replaceWithMock +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 { + replaceWithMock(it) { m -> description.shouldMock(m) } + }.toList() + assertEquals(2, results.size) + results.forEach { model -> + assertInstanceOf(UtAssembleModel::class.java, model) + } + assertEquals(0, (results[1] as UtAssembleModel).modificationsChain.size) + assertEquals(1, (results[0] 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 { + replaceWithMock(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 { + replaceWithMock(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[0] 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 = replaceWithMock(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 = replaceWithMock(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 withContext(block: () -> T) { + withUtContext(UtContext(this::class.java.classLoader)) { + block() + } + } + +} \ No newline at end of file