From 47fd644c361ab7a94ded1de412246275fed385ce Mon Sep 17 00:00:00 2001 From: Kirill Shishin Date: Wed, 4 Oct 2023 03:01:43 +0300 Subject: [PATCH 1/3] Fix bug with injecting of different types of thisInstance fields using @Spy. --- .../tree/fieldmanager/CgSpiedFieldsManager.kt | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/CgSpiedFieldsManager.kt b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/CgSpiedFieldsManager.kt index 4fdab3e496..07528fdba3 100644 --- a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/CgSpiedFieldsManager.kt +++ b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/CgSpiedFieldsManager.kt @@ -1,5 +1,6 @@ package org.utbot.framework.codegen.tree.fieldmanager +import org.utbot.framework.codegen.domain.UtModelWrapper import org.utbot.framework.codegen.domain.builtin.spyClassId import org.utbot.framework.codegen.domain.context.CgContext import org.utbot.framework.codegen.domain.models.CgFieldDeclaration @@ -12,6 +13,7 @@ import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.canBeSpied import org.utbot.framework.plugin.api.isMockModel +import org.utbot.framework.plugin.api.util.jClass class CgSpiedFieldsManager(context: CgContext) : CgAbstractClassFieldManager(context) { @@ -38,9 +40,45 @@ class CgSpiedFieldsManager(context: CgContext) : CgAbstractClassFieldManager(con cgModel !in dependentMockModels } - return constructFieldsWithAnnotation(dependentSpyModels) + val suitableDependentSpyModels = getSuitableDependentSpyModels(dependentSpyModels) + return constructFieldsWithAnnotation(suitableDependentSpyModels) } + /* + * If we have models of different types implementing Collection, + * we should not construct fields of these models with @Spy annotation + * because in this case, Spring cannot inject fields. + * + * The situation is similar with Map. + */ + private fun getSuitableDependentSpyModels(dependentSpyModels: MutableSet): Set = + getSuitableDependentSpyModelsImplementing(Collection::class.java, dependentSpyModels) + + getSuitableDependentSpyModelsImplementing(Map::class.java, dependentSpyModels) + + + private fun getSuitableDependentSpyModelsImplementing(clazz: Class<*>, dependentSpyModels: MutableSet): Set { + return when{ + isSuitableSpyModelsImplementing(clazz, dependentSpyModels) -> dependentSpyModels.filter { clazz.isAssignableFrom(it.model.classId.jClass) }.toSet() + else -> emptySet() + } + } + + /* + * Models implementing Collection will be suitable if they are all the same type. + * + * The situation is similar with Map. + */ + private fun isSuitableSpyModelsImplementing(clazz: Class<*>, dependentSpyModels: MutableSet): Boolean { + val modelsClassIdsSet = HashSet() + dependentSpyModels.forEach { + val modelClassId = it.model.classId + if(clazz.isAssignableFrom(modelClassId.jClass)){ + modelsClassIdsSet.add(modelClassId) + } + } + + return modelsClassIdsSet.size == 1 + } private val spyFrameworkManager = SpyFrameworkManager(context) From d4cd1ad8fb3234d1ad0df14e2248cb88b8170af5 Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Wed, 4 Oct 2023 13:40:51 +0300 Subject: [PATCH 2/3] Some code cleanup --- .../org/utbot/framework/plugin/api/Api.kt | 9 +-- .../tree/fieldmanager/CgSpiedFieldsManager.kt | 70 +++++++++---------- 2 files changed, 35 insertions(+), 44 deletions(-) diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt index 755ad82928..6759311300 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt @@ -404,13 +404,10 @@ fun UtModel.isMockModel() = this is UtCompositeModel && isMock * Checks that this [UtModel] must be constructed with @Spy annotation in generated tests. * Used only for construct variables with @Spy annotation. */ -fun UtModel.canBeSpied(): Boolean { - val javaClass = this.classId.jClass +fun UtModel.canBeSpied(): Boolean = + this is UtAssembleModel && spiedTypes.any { type -> type.isAssignableFrom(this.classId.jClass)} - return this is UtAssembleModel && - (Collection::class.java.isAssignableFrom(javaClass) - || Map::class.java.isAssignableFrom(javaClass)) -} +val spiedTypes = setOf(Collection::class.java, Map::class.java) /** * Get model id (symbolic null value for UtNullModel) diff --git a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/CgSpiedFieldsManager.kt b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/CgSpiedFieldsManager.kt index 07528fdba3..976d8a161e 100644 --- a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/CgSpiedFieldsManager.kt +++ b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/CgSpiedFieldsManager.kt @@ -13,6 +13,7 @@ import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.canBeSpied import org.utbot.framework.plugin.api.isMockModel +import org.utbot.framework.plugin.api.spiedTypes import org.utbot.framework.plugin.api.util.jClass class CgSpiedFieldsManager(context: CgContext) : CgAbstractClassFieldManager(context) { @@ -40,44 +41,8 @@ class CgSpiedFieldsManager(context: CgContext) : CgAbstractClassFieldManager(con cgModel !in dependentMockModels } - val suitableDependentSpyModels = getSuitableDependentSpyModels(dependentSpyModels) - return constructFieldsWithAnnotation(suitableDependentSpyModels) - } - - /* - * If we have models of different types implementing Collection, - * we should not construct fields of these models with @Spy annotation - * because in this case, Spring cannot inject fields. - * - * The situation is similar with Map. - */ - private fun getSuitableDependentSpyModels(dependentSpyModels: MutableSet): Set = - getSuitableDependentSpyModelsImplementing(Collection::class.java, dependentSpyModels) + - getSuitableDependentSpyModelsImplementing(Map::class.java, dependentSpyModels) - - - private fun getSuitableDependentSpyModelsImplementing(clazz: Class<*>, dependentSpyModels: MutableSet): Set { - return when{ - isSuitableSpyModelsImplementing(clazz, dependentSpyModels) -> dependentSpyModels.filter { clazz.isAssignableFrom(it.model.classId.jClass) }.toSet() - else -> emptySet() - } - } - - /* - * Models implementing Collection will be suitable if they are all the same type. - * - * The situation is similar with Map. - */ - private fun isSuitableSpyModelsImplementing(clazz: Class<*>, dependentSpyModels: MutableSet): Boolean { - val modelsClassIdsSet = HashSet() - dependentSpyModels.forEach { - val modelClassId = it.model.classId - if(clazz.isAssignableFrom(modelClassId.jClass)){ - modelsClassIdsSet.add(modelClassId) - } - } - - return modelsClassIdsSet.size == 1 + val suitableSpyModels = getSuitableSpyModels(dependentSpyModels) + return constructFieldsWithAnnotation(suitableSpyModels) } private val spyFrameworkManager = SpyFrameworkManager(context) @@ -95,4 +60,33 @@ class CgSpiedFieldsManager(context: CgContext) : CgAbstractClassFieldManager(con classId.canBeInjectedByTypeInto(classUnderTest) override fun constructBaseVarName(model: UtModel): String = super.constructBaseVarName(model) + "Spy" + + private fun getSuitableSpyModels(potentialSpyModels: MutableSet): Set = + spiedTypes.fold(setOf()) { spyModels, type -> + spyModels + getSuitableSpyModelsOfType(type, potentialSpyModels) + } + + /* + * Detects if injecting models via @Spy is possible and behavior is transparent. + * + * Some limitations are reasoned by @InjectMocks behaviour. It can successfully + * inject a @Spy if it's type is unique in this class (original variable names + * are hidden in test class,so we can inject by type only). It may cause problems + * sometimes. For example, if there is more than one collection in original class + * having types List and List, we may try to construct two listSpy objects + * that clash and injection will be incorrect so on. + * + * So @Spy variable may be created only if there are no clashes as described. + */ + private fun getSuitableSpyModelsOfType( + clazz: Class<*>, + potentialSpyModels: MutableSet + ): Set { + val spyModelsAssignableFrom = potentialSpyModels + .filter { clazz.isAssignableFrom(it.model.classId.jClass) } + .toSet() + val spyModelsTypesCount = spyModelsAssignableFrom.map { it.model.classId }.toSet().size + + return if (spyModelsTypesCount == 1) spyModelsAssignableFrom else emptySet() + } } \ No newline at end of file From 674226ec6d710cdd4a9a8df22b87db169acc4d5f Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Wed, 4 Oct 2023 15:21:32 +0300 Subject: [PATCH 3/3] Documentation improved --- .../tree/fieldmanager/CgSpiedFieldsManager.kt | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/CgSpiedFieldsManager.kt b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/CgSpiedFieldsManager.kt index 976d8a161e..6c37ff3602 100644 --- a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/CgSpiedFieldsManager.kt +++ b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/CgSpiedFieldsManager.kt @@ -67,16 +67,12 @@ class CgSpiedFieldsManager(context: CgContext) : CgAbstractClassFieldManager(con } /* - * Detects if injecting models via @Spy is possible and behavior is transparent. + * Filters out cases when different tests use different [clazz] + * implementations and hence we need to inject different types. * - * Some limitations are reasoned by @InjectMocks behaviour. It can successfully - * inject a @Spy if it's type is unique in this class (original variable names - * are hidden in test class,so we can inject by type only). It may cause problems - * sometimes. For example, if there is more than one collection in original class - * having types List and List, we may try to construct two listSpy objects - * that clash and injection will be incorrect so on. - * - * So @Spy variable may be created only if there are no clashes as described. + * This limitation is reasoned by @InjectMocks behaviour. + * Otherwise, injection may be misleading: + * for example, several spies may be injected into one field. */ private fun getSuitableSpyModelsOfType( clazz: Class<*>,