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 3f34ccc805..4e1ccb2845 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 @@ -62,7 +62,6 @@ import org.utbot.framework.plugin.api.TypeReplacementMode.* import org.utbot.framework.plugin.api.util.allDeclaredFieldIds import org.utbot.framework.plugin.api.util.fieldId import org.utbot.framework.plugin.api.util.isSubtypeOf -import org.utbot.framework.plugin.api.util.objectClassId import org.utbot.framework.plugin.api.util.utContext import org.utbot.framework.process.OpenModulesContainer import soot.SootField @@ -1258,13 +1257,13 @@ open class ApplicationContext( * Data we get from Spring application context * to manage engine and code generator behaviour. * - * @param beanQualifiedNames describes fqn of types from bean definitions + * @param beanDefinitions describes bean definitions (bean name, type, some optional additional data) * @param shouldUseImplementors describes it we want to replace interfaces with injected types or not */ class SpringApplicationContext( mockInstalled: Boolean, staticsMockingIsConfigured: Boolean, - private val beanQualifiedNames: List = emptyList(), + private val beanDefinitions: List = emptyList(), private val shouldUseImplementors: Boolean, ): ApplicationContext(mockInstalled, staticsMockingIsConfigured) { @@ -1278,7 +1277,8 @@ class SpringApplicationContext( private val springInjectedClasses: Set get() { if (!areInjectedClassesInitialized) { - for (beanFqn in beanQualifiedNames) { + // TODO: use more info from SpringBeanDefinitionData than beanTypeFqn offers here + for (beanFqn in beanDefinitions.map { it.beanTypeFqn }) { try { val beanClass = utContext.classLoader.loadClass(beanFqn) if (!beanClass.isAbstract && !beanClass.isInterface && @@ -1341,6 +1341,29 @@ class SpringApplicationContext( ): Boolean = field.fieldId in classUnderTest.allDeclaredFieldIds && field.declaringClass.id !in springInjectedClasses } +/** + * Describes information about beans obtained from Spring analysis process. + * + * Contains the name of the bean, its type (class or interface) and optional additional data. + */ +data class BeanDefinitionData( + val beanName: String, + val beanTypeFqn: String, + val additionalData: BeanAdditionalData?, +) + +/** + * Describes some additional information about beans obtained from Spring analysis process. + * + * Sometimes the actual type of the bean can not be obtained from bean definition. + * Then we try to recover it by method and class defining bean (e.g. using Psi elements). + */ +data class BeanAdditionalData( + val factoryMethodName: String, + val configClassFqn: String, +) + + val RefType.isAbstractType get() = this.sootClass.isAbstract || this.sootClass.isInterface diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineProcessMain.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineProcessMain.kt index 167c48bd98..409efef0e6 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineProcessMain.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineProcessMain.kt @@ -24,6 +24,8 @@ import org.utbot.framework.plugin.api.util.jClass import org.utbot.framework.plugin.api.util.method import org.utbot.framework.plugin.services.JdkInfo import org.utbot.framework.process.generated.* +import org.utbot.framework.process.generated.BeanAdditionalData +import org.utbot.framework.process.generated.BeanDefinitionData import org.utbot.instrumentation.instrumentation.instrumenter.Instrumenter import org.utbot.instrumentation.util.KryoHelper import org.utbot.rd.IdleWatchdog @@ -72,21 +74,27 @@ private fun EngineProcessModel.setup(kryoHelper: KryoHelper, watchdog: IdleWatch File(it).toURI().toURL() }.toTypedArray()))) } - watchdog.measureTimeForActiveCall(getSpringBeanQualifiedNames, "Getting Spring bean definitions") { params -> + watchdog.measureTimeForActiveCall(getSpringBeanDefinitions, "Getting Spring bean definitions") { params -> try { val springAnalyzerProcess = SpringAnalyzerProcess.createBlocking(params.classpath.toList()) - val beans = springAnalyzerProcess.terminateOnException { _ -> - springAnalyzerProcess.getBeanQualifiedNames( + val result = springAnalyzerProcess.terminateOnException { _ -> + springAnalyzerProcess.getBeanDefinitions( params.config, params.fileStorage, params.profileExpression, - ).toTypedArray() + ) } springAnalyzerProcess.terminate() - beans + val beanDefinitions = result.beanDefinitions + .map { data -> + val additionalData = data.additionalData?.let { BeanAdditionalData(it.factoryMethodName, it.configClassFqn) } + BeanDefinitionData(data.beanName, data.beanTypeFqn, additionalData) + } + .toTypedArray() + SpringAnalyzerResult(beanDefinitions) } catch (e: Exception) { - logger.error(e) { "Spring Analyzer crushed, resorting to using empty bean list" } - emptyArray() + logger.error(e) { "Spring Analyzer crashed, resorting to using empty bean list" } + SpringAnalyzerResult(emptyArray()) } } watchdog.measureTimeForActiveCall(createTestGenerator, "Creating Test Generator") { params -> diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/EngineProcessModel.Generated.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/EngineProcessModel.Generated.kt index 929664dc5a..378638286d 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/EngineProcessModel.Generated.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/EngineProcessModel.Generated.kt @@ -20,7 +20,7 @@ import kotlin.jvm.JvmStatic */ class EngineProcessModel private constructor( private val _setupUtContext: RdCall, - private val _getSpringBeanQualifiedNames: RdCall>, + private val _getSpringBeanDefinitions: RdCall, private val _createTestGenerator: RdCall, private val _isCancelled: RdCall, private val _generate: RdCall, @@ -43,7 +43,7 @@ class EngineProcessModel private constructor( serializers.register(RenderParams) serializers.register(RenderResult) serializers.register(SetupContextParams) - serializers.register(GetSpringBeanQualifiedNamesParams) + serializers.register(GetSpringBeanDefinitions) serializers.register(MethodDescription) serializers.register(FindMethodsInClassMatchingSelectedArguments) serializers.register(FindMethodsInClassMatchingSelectedResult) @@ -52,6 +52,9 @@ class EngineProcessModel private constructor( serializers.register(WriteSarifReportArguments) serializers.register(GenerateTestReportArgs) serializers.register(GenerateTestReportResult) + serializers.register(BeanAdditionalData) + serializers.register(BeanDefinitionData) + serializers.register(SpringAnalyzerResult) } @@ -71,9 +74,8 @@ class EngineProcessModel private constructor( return EngineProcessModel() } - private val __StringArraySerializer = FrameworkMarshallers.String.array() - const val serializationHash = 6076922965808046990L + const val serializationHash = 3867600509781920270L } override val serializersOwner: ISerializersOwner get() = EngineProcessModel @@ -81,7 +83,7 @@ class EngineProcessModel private constructor( //fields val setupUtContext: RdCall get() = _setupUtContext - val getSpringBeanQualifiedNames: RdCall> get() = _getSpringBeanQualifiedNames + val getSpringBeanDefinitions: RdCall get() = _getSpringBeanDefinitions val createTestGenerator: RdCall get() = _createTestGenerator val isCancelled: RdCall get() = _isCancelled val generate: RdCall get() = _generate @@ -95,7 +97,7 @@ class EngineProcessModel private constructor( //initializer init { _setupUtContext.async = true - _getSpringBeanQualifiedNames.async = true + _getSpringBeanDefinitions.async = true _createTestGenerator.async = true _isCancelled.async = true _generate.async = true @@ -109,7 +111,7 @@ class EngineProcessModel private constructor( init { bindableChildren.add("setupUtContext" to _setupUtContext) - bindableChildren.add("getSpringBeanQualifiedNames" to _getSpringBeanQualifiedNames) + bindableChildren.add("getSpringBeanDefinitions" to _getSpringBeanDefinitions) bindableChildren.add("createTestGenerator" to _createTestGenerator) bindableChildren.add("isCancelled" to _isCancelled) bindableChildren.add("generate" to _generate) @@ -125,7 +127,7 @@ class EngineProcessModel private constructor( private constructor( ) : this( RdCall(SetupContextParams, FrameworkMarshallers.Void), - RdCall>(GetSpringBeanQualifiedNamesParams, __StringArraySerializer), + RdCall(GetSpringBeanDefinitions, SpringAnalyzerResult), RdCall(TestGeneratorParams, FrameworkMarshallers.Void), RdCall(FrameworkMarshallers.Void, FrameworkMarshallers.Bool), RdCall(GenerateParams, GenerateResult), @@ -144,7 +146,7 @@ class EngineProcessModel private constructor( printer.println("EngineProcessModel (") printer.indent { print("setupUtContext = "); _setupUtContext.print(printer); println() - print("getSpringBeanQualifiedNames = "); _getSpringBeanQualifiedNames.print(printer); println() + print("getSpringBeanDefinitions = "); _getSpringBeanDefinitions.print(printer); println() print("createTestGenerator = "); _createTestGenerator.print(printer); println() print("isCancelled = "); _isCancelled.print(printer); println() print("generate = "); _generate.print(printer); println() @@ -161,7 +163,7 @@ class EngineProcessModel private constructor( override fun deepClone(): EngineProcessModel { return EngineProcessModel( _setupUtContext.deepClonePolymorphic(), - _getSpringBeanQualifiedNames.deepClonePolymorphic(), + _getSpringBeanDefinitions.deepClonePolymorphic(), _createTestGenerator.deepClonePolymorphic(), _isCancelled.deepClonePolymorphic(), _generate.deepClonePolymorphic(), @@ -179,6 +181,138 @@ val IProtocol.engineProcessModel get() = getOrCreateExtension(EngineProcessModel +/** + * #### Generated from [EngineProcessModel.kt:131] + */ +data class BeanAdditionalData ( + val factoryMethodName: String, + val configClassFqn: String +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = BeanAdditionalData::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): BeanAdditionalData { + val factoryMethodName = buffer.readString() + val configClassFqn = buffer.readString() + return BeanAdditionalData(factoryMethodName, configClassFqn) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: BeanAdditionalData) { + buffer.writeString(value.factoryMethodName) + buffer.writeString(value.configClassFqn) + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as BeanAdditionalData + + if (factoryMethodName != other.factoryMethodName) return false + if (configClassFqn != other.configClassFqn) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + factoryMethodName.hashCode() + __r = __r*31 + configClassFqn.hashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("BeanAdditionalData (") + printer.indent { + print("factoryMethodName = "); factoryMethodName.print(printer); println() + print("configClassFqn = "); configClassFqn.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + +/** + * #### Generated from [EngineProcessModel.kt:135] + */ +data class BeanDefinitionData ( + val beanName: String, + val beanTypeFqn: String, + val additionalData: BeanAdditionalData? +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = BeanDefinitionData::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): BeanDefinitionData { + val beanName = buffer.readString() + val beanTypeFqn = buffer.readString() + val additionalData = buffer.readNullable { BeanAdditionalData.read(ctx, buffer) } + return BeanDefinitionData(beanName, beanTypeFqn, additionalData) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: BeanDefinitionData) { + buffer.writeString(value.beanName) + buffer.writeString(value.beanTypeFqn) + buffer.writeNullable(value.additionalData) { BeanAdditionalData.write(ctx, buffer, it) } + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as BeanDefinitionData + + if (beanName != other.beanName) return false + if (beanTypeFqn != other.beanTypeFqn) return false + if (additionalData != other.additionalData) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + beanName.hashCode() + __r = __r*31 + beanTypeFqn.hashCode() + __r = __r*31 + if (additionalData != null) additionalData.hashCode() else 0 + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("BeanDefinitionData (") + printer.indent { + print("beanName = "); beanName.print(printer); println() + print("beanTypeFqn = "); beanTypeFqn.print(printer); println() + print("additionalData = "); additionalData.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + /** * #### Generated from [EngineProcessModel.kt:105] */ @@ -752,7 +886,7 @@ data class GenerateTestReportResult ( /** * #### Generated from [EngineProcessModel.kt:87] */ -data class GetSpringBeanQualifiedNamesParams ( +data class GetSpringBeanDefinitions ( val classpath: Array, val config: String, val fileStorage: Array, @@ -760,19 +894,19 @@ data class GetSpringBeanQualifiedNamesParams ( ) : IPrintable { //companion - companion object : IMarshaller { - override val _type: KClass = GetSpringBeanQualifiedNamesParams::class + companion object : IMarshaller { + override val _type: KClass = GetSpringBeanDefinitions::class @Suppress("UNCHECKED_CAST") - override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): GetSpringBeanQualifiedNamesParams { + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): GetSpringBeanDefinitions { val classpath = buffer.readArray {buffer.readString()} val config = buffer.readString() val fileStorage = buffer.readArray {buffer.readString()} val profileExpression = buffer.readNullable { buffer.readString() } - return GetSpringBeanQualifiedNamesParams(classpath, config, fileStorage, profileExpression) + return GetSpringBeanDefinitions(classpath, config, fileStorage, profileExpression) } - override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: GetSpringBeanQualifiedNamesParams) { + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: GetSpringBeanDefinitions) { buffer.writeArray(value.classpath) { buffer.writeString(it) } buffer.writeString(value.config) buffer.writeArray(value.fileStorage) { buffer.writeString(it) } @@ -790,7 +924,7 @@ data class GetSpringBeanQualifiedNamesParams ( if (this === other) return true if (other == null || other::class != this::class) return false - other as GetSpringBeanQualifiedNamesParams + other as GetSpringBeanDefinitions if (!(classpath contentDeepEquals other.classpath)) return false if (config != other.config) return false @@ -810,7 +944,7 @@ data class GetSpringBeanQualifiedNamesParams ( } //pretty print override fun print(printer: PrettyPrinter) { - printer.println("GetSpringBeanQualifiedNamesParams (") + printer.println("GetSpringBeanDefinitions (") printer.indent { print("classpath = "); classpath.print(printer); println() print("config = "); config.print(printer); println() @@ -1223,6 +1357,63 @@ data class SetupContextParams ( } +/** + * #### Generated from [EngineProcessModel.kt:140] + */ +data class SpringAnalyzerResult ( + val beanDefinitions: Array +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = SpringAnalyzerResult::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): SpringAnalyzerResult { + val beanDefinitions = buffer.readArray {BeanDefinitionData.read(ctx, buffer)} + return SpringAnalyzerResult(beanDefinitions) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: SpringAnalyzerResult) { + buffer.writeArray(value.beanDefinitions) { BeanDefinitionData.write(ctx, buffer, it) } + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as SpringAnalyzerResult + + if (!(beanDefinitions contentDeepEquals other.beanDefinitions)) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + beanDefinitions.contentDeepHashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("SpringAnalyzerResult (") + printer.indent { + print("beanDefinitions = "); beanDefinitions.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + /** * #### Generated from [EngineProcessModel.kt:37] */ diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/UtTestsDialogProcessor.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/UtTestsDialogProcessor.kt index aab4ff99a3..d8bee1a5d0 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/UtTestsDialogProcessor.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/UtTestsDialogProcessor.kt @@ -234,7 +234,7 @@ object UtTestsDialogProcessor { process.setupUtContext(classpathForClassLoader) val applicationContext = when (model.projectType) { Spring -> { - val beanQualifiedNames = + val beanDefinitions = when (val approach = model.typeReplacementApproach) { DoNotReplace -> emptyList() is ReplaceIfPossible -> { @@ -248,7 +248,7 @@ object UtTestsDialogProcessor { } val fileStorage = contentRoots.map { root -> root.url }.toTypedArray() - process.getSpringBeanQualifiedNames( + process.getSpringBeanDefinitions( classpathForClassLoader, approach.config, fileStorage, @@ -256,12 +256,12 @@ object UtTestsDialogProcessor { ) } } - val shouldUseImplementors = beanQualifiedNames.isNotEmpty() + val shouldUseImplementors = beanDefinitions.isNotEmpty() SpringApplicationContext( mockFrameworkInstalled, staticMockingConfigured, - beanQualifiedNames, + beanDefinitions, shouldUseImplementors, ) } diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/process/EngineProcess.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/process/EngineProcess.kt index b8132ebe6a..34c5a8dc9f 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/process/EngineProcess.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/process/EngineProcess.kt @@ -18,6 +18,8 @@ import org.utbot.framework.UtSettings import org.utbot.framework.codegen.domain.* import org.utbot.framework.codegen.tree.ututils.UtilClassKind import org.utbot.framework.plugin.api.* +import org.utbot.framework.plugin.api.BeanAdditionalData +import org.utbot.framework.plugin.api.BeanDefinitionData import org.utbot.framework.plugin.services.JdkInfo import org.utbot.framework.plugin.services.WorkingDirService import org.utbot.framework.process.AbstractRDProcessCompanion @@ -143,15 +145,24 @@ class EngineProcess private constructor(val project: Project, private val classN engineModel.setupUtContext.startBlocking(SetupContextParams(classpathForUrlsClassloader)) } - fun getSpringBeanQualifiedNames( + fun getSpringBeanDefinitions( classpathList: List, config: String, fileStorage: Array, - profileExpression: String?): List { + profileExpression: String?): List { assertReadAccessNotAllowed() - return engineModel.getSpringBeanQualifiedNames.startBlocking( - GetSpringBeanQualifiedNamesParams(classpathList.toTypedArray(), config, fileStorage, profileExpression) - ).toList() + val result = engineModel.getSpringBeanDefinitions.startBlocking( + GetSpringBeanDefinitions(classpathList.toTypedArray(), config, fileStorage, profileExpression) + ) + return result.beanDefinitions + .map { data -> + BeanDefinitionData( + beanName = data.beanName, + beanTypeFqn = data.beanTypeFqn, + additionalData = data.additionalData + ?.let { BeanAdditionalData(it.factoryMethodName, it.configClassFqn) } + ) + } } private fun computeSourceFileByClass(params: ComputeSourceFileByClassArguments): String = diff --git a/utbot-rd/src/main/rdgen/org/utbot/rd/models/EngineProcessModel.kt b/utbot-rd/src/main/rdgen/org/utbot/rd/models/EngineProcessModel.kt index d2bf647b42..9319464e88 100644 --- a/utbot-rd/src/main/rdgen/org/utbot/rd/models/EngineProcessModel.kt +++ b/utbot-rd/src/main/rdgen/org/utbot/rd/models/EngineProcessModel.kt @@ -84,7 +84,7 @@ object EngineProcessModel : Ext(EngineProcessRoot) { val setupContextParams = structdef { field("classpathForUrlsClassloader", immutableList(PredefinedType.string)) } - val getSpringBeanQualifiedNamesParams = structdef { + val getSpringBeanDefinitions = structdef { field("classpath", array(PredefinedType.string)) field("config", PredefinedType.string) field("fileStorage", array(PredefinedType.string)) @@ -128,9 +128,22 @@ object EngineProcessModel : Ext(EngineProcessRoot) { field("statistics", PredefinedType.string.nullable) field("hasWarnings", PredefinedType.bool) } + val beanAdditionalData = structdef { + field("factoryMethodName", PredefinedType.string) + field("configClassFqn", PredefinedType.string) + } + val beanDefinitionData = structdef { + field("beanName", PredefinedType.string) + field("beanTypeFqn", PredefinedType.string) + field("additionalData", beanAdditionalData.nullable) + } + val springAnalyzerResult = structdef { + field("beanDefinitions", array(beanDefinitionData)) + } + init { call("setupUtContext", setupContextParams, PredefinedType.void).async - call("getSpringBeanQualifiedNames", getSpringBeanQualifiedNamesParams, array(PredefinedType.string)).async + call("getSpringBeanDefinitions", getSpringBeanDefinitions, springAnalyzerResult).async call("createTestGenerator", testGeneratorParams, PredefinedType.void).async call("isCancelled", PredefinedType.void, PredefinedType.bool).async call("generate", generateParams, generateResult).async diff --git a/utbot-rd/src/main/rdgen/org/utbot/rd/models/SpringAnalyzerModel.kt b/utbot-rd/src/main/rdgen/org/utbot/rd/models/SpringAnalyzerModel.kt index 1a45ac24bb..c8f44f792d 100644 --- a/utbot-rd/src/main/rdgen/org/utbot/rd/models/SpringAnalyzerModel.kt +++ b/utbot-rd/src/main/rdgen/org/utbot/rd/models/SpringAnalyzerModel.kt @@ -12,8 +12,19 @@ object SpringAnalyzerProcessModel : Ext(SpringAnalyzerRoot) { field("profileExpression", PredefinedType.string.nullable) } + val beanAdditionalData = structdef { + field("factoryMethodName", PredefinedType.string) + field("configClassFqn", PredefinedType.string) + } + + val beanDefinitionData = structdef { + field("beanName", PredefinedType.string) + field("beanTypeFqn", PredefinedType.string) + field("additionalData", beanAdditionalData.nullable) + } + val springAnalyzerResult = structdef { - field("beanTypes", array(PredefinedType.string)) + field("beanDefinitions", array(beanDefinitionData)) } init { diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzers/PureSpringApplicationAnalyzer.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzers/PureSpringApplicationAnalyzer.kt index 6cd5343b67..fcbf0004d8 100644 --- a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzers/PureSpringApplicationAnalyzer.kt +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzers/PureSpringApplicationAnalyzer.kt @@ -3,13 +3,14 @@ package org.utbot.spring.analyzers import org.springframework.context.annotation.AnnotationConfigApplicationContext import org.springframework.core.env.ConfigurableEnvironment import org.utbot.spring.exception.UtBotSpringShutdownException +import org.utbot.spring.generated.BeanDefinitionData class PureSpringApplicationAnalyzer : SpringApplicationAnalyzer { - override fun analyze(sources: Array>, environment: ConfigurableEnvironment): List { + override fun analyze(sources: Array>, environment: ConfigurableEnvironment): List { val applicationContext = AnnotationConfigApplicationContext() applicationContext.register(*sources) applicationContext.environment = environment - return UtBotSpringShutdownException.catch { applicationContext.refresh() }.beanQualifiedNames + return UtBotSpringShutdownException.catch { applicationContext.refresh() }.beanDefinitions } override fun canAnalyze() = true diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzers/SpringApplicationAnalyzer.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzers/SpringApplicationAnalyzer.kt index 0de1723a2e..3e36a8d80b 100644 --- a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzers/SpringApplicationAnalyzer.kt +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzers/SpringApplicationAnalyzer.kt @@ -1,8 +1,11 @@ package org.utbot.spring.analyzers import org.springframework.core.env.ConfigurableEnvironment +import org.utbot.spring.generated.BeanDefinitionData interface SpringApplicationAnalyzer { - fun analyze(sources: Array>, environment: ConfigurableEnvironment): List + fun canAnalyze(): Boolean + + fun analyze(sources: Array>, environment: ConfigurableEnvironment): List } \ No newline at end of file diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzers/SpringApplicationAnalyzerFacade.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzers/SpringApplicationAnalyzerFacade.kt index 705278a27b..d06aeea5e0 100644 --- a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzers/SpringApplicationAnalyzerFacade.kt +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzers/SpringApplicationAnalyzerFacade.kt @@ -6,6 +6,7 @@ import com.jetbrains.rd.util.info import org.springframework.boot.SpringBootVersion import org.springframework.core.SpringVersion import org.utbot.spring.api.ApplicationData +import org.utbot.spring.generated.BeanDefinitionData import org.utbot.spring.utils.EnvironmentFactory import org.utbot.spring.utils.SourceFinder @@ -13,7 +14,7 @@ private val logger = getLogger() class SpringApplicationAnalyzerFacade(private val applicationData: ApplicationData) { - fun analyze(): List { + fun analyze(): List { logger.info { "Current Java version is: " + System.getProperty("java.version") } logger.info { "Current Spring version is: " + runCatching { SpringVersion.getVersion() }.getOrNull() } logger.info { "Current Spring Boot version is: " + runCatching { SpringBootVersion.getVersion() }.getOrNull() } diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzers/SpringBootApplicationAnalyzer.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzers/SpringBootApplicationAnalyzer.kt index 933a3034bc..9227967f13 100644 --- a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzers/SpringBootApplicationAnalyzer.kt +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzers/SpringBootApplicationAnalyzer.kt @@ -3,13 +3,14 @@ package org.utbot.spring.analyzers import org.springframework.boot.builder.SpringApplicationBuilder import org.springframework.core.env.ConfigurableEnvironment import org.utbot.spring.exception.UtBotSpringShutdownException +import org.utbot.spring.generated.BeanDefinitionData class SpringBootApplicationAnalyzer : SpringApplicationAnalyzer { - override fun analyze(sources: Array>, environment: ConfigurableEnvironment): List { + override fun analyze(sources: Array>, environment: ConfigurableEnvironment): List { val app = SpringApplicationBuilder(*sources) .environment(environment) .build() - return UtBotSpringShutdownException.catch { app.run() }.beanQualifiedNames + return UtBotSpringShutdownException.catch { app.run() }.beanDefinitions } override fun canAnalyze(): Boolean = try { diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/exception/UtBotSpringShutdownException.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/exception/UtBotSpringShutdownException.kt index 86bdb46a5f..6d80ba9166 100644 --- a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/exception/UtBotSpringShutdownException.kt +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/exception/UtBotSpringShutdownException.kt @@ -2,6 +2,7 @@ package org.utbot.spring.exception import com.jetbrains.rd.util.getLogger import com.jetbrains.rd.util.info +import org.utbot.spring.generated.BeanDefinitionData private val logger = getLogger() @@ -11,7 +12,7 @@ private val logger = getLogger() */ class UtBotSpringShutdownException( message: String, - val beanQualifiedNames: List + val beanDefinitions: List ): RuntimeException(message) { companion object { fun catch(block: () -> Unit): UtBotSpringShutdownException { diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/generated/SpringAnalyzerProcessModel.Generated.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/generated/SpringAnalyzerProcessModel.Generated.kt index 0553c56637..4ffc1107a6 100644 --- a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/generated/SpringAnalyzerProcessModel.Generated.kt +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/generated/SpringAnalyzerProcessModel.Generated.kt @@ -27,6 +27,8 @@ class SpringAnalyzerProcessModel private constructor( override fun registerSerializersCore(serializers: ISerializers) { serializers.register(SpringAnalyzerParams) + serializers.register(BeanAdditionalData) + serializers.register(BeanDefinitionData) serializers.register(SpringAnalyzerResult) } @@ -48,7 +50,7 @@ class SpringAnalyzerProcessModel private constructor( } - const val serializationHash = 1208896755601889441L + const val serializationHash = -2275009816925697183L } override val serializersOwner: ISerializersOwner get() = SpringAnalyzerProcessModel @@ -94,6 +96,138 @@ val IProtocol.springAnalyzerProcessModel get() = getOrCreateExtension(SpringAnal +/** + * #### Generated from [SpringAnalyzerModel.kt:15] + */ +data class BeanAdditionalData ( + val factoryMethodName: String, + val configClassFqn: String +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = BeanAdditionalData::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): BeanAdditionalData { + val factoryMethodName = buffer.readString() + val configClassFqn = buffer.readString() + return BeanAdditionalData(factoryMethodName, configClassFqn) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: BeanAdditionalData) { + buffer.writeString(value.factoryMethodName) + buffer.writeString(value.configClassFqn) + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as BeanAdditionalData + + if (factoryMethodName != other.factoryMethodName) return false + if (configClassFqn != other.configClassFqn) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + factoryMethodName.hashCode() + __r = __r*31 + configClassFqn.hashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("BeanAdditionalData (") + printer.indent { + print("factoryMethodName = "); factoryMethodName.print(printer); println() + print("configClassFqn = "); configClassFqn.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + +/** + * #### Generated from [SpringAnalyzerModel.kt:20] + */ +data class BeanDefinitionData ( + val beanName: String, + val beanTypeFqn: String, + val additionalData: BeanAdditionalData? +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = BeanDefinitionData::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): BeanDefinitionData { + val beanName = buffer.readString() + val beanTypeFqn = buffer.readString() + val additionalData = buffer.readNullable { BeanAdditionalData.read(ctx, buffer) } + return BeanDefinitionData(beanName, beanTypeFqn, additionalData) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: BeanDefinitionData) { + buffer.writeString(value.beanName) + buffer.writeString(value.beanTypeFqn) + buffer.writeNullable(value.additionalData) { BeanAdditionalData.write(ctx, buffer, it) } + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as BeanDefinitionData + + if (beanName != other.beanName) return false + if (beanTypeFqn != other.beanTypeFqn) return false + if (additionalData != other.additionalData) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + beanName.hashCode() + __r = __r*31 + beanTypeFqn.hashCode() + __r = __r*31 + if (additionalData != null) additionalData.hashCode() else 0 + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("BeanDefinitionData (") + printer.indent { + print("beanName = "); beanName.print(printer); println() + print("beanTypeFqn = "); beanTypeFqn.print(printer); println() + print("additionalData = "); additionalData.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + /** * #### Generated from [SpringAnalyzerModel.kt:9] */ @@ -164,10 +298,10 @@ data class SpringAnalyzerParams ( /** - * #### Generated from [SpringAnalyzerModel.kt:15] + * #### Generated from [SpringAnalyzerModel.kt:26] */ data class SpringAnalyzerResult ( - val beanTypes: Array + val beanDefinitions: Array ) : IPrintable { //companion @@ -176,12 +310,12 @@ data class SpringAnalyzerResult ( @Suppress("UNCHECKED_CAST") override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): SpringAnalyzerResult { - val beanTypes = buffer.readArray {buffer.readString()} - return SpringAnalyzerResult(beanTypes) + val beanDefinitions = buffer.readArray {BeanDefinitionData.read(ctx, buffer)} + return SpringAnalyzerResult(beanDefinitions) } override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: SpringAnalyzerResult) { - buffer.writeArray(value.beanTypes) { buffer.writeString(it) } + buffer.writeArray(value.beanDefinitions) { BeanDefinitionData.write(ctx, buffer, it) } } @@ -197,21 +331,21 @@ data class SpringAnalyzerResult ( other as SpringAnalyzerResult - if (!(beanTypes contentDeepEquals other.beanTypes)) return false + if (!(beanDefinitions contentDeepEquals other.beanDefinitions)) return false return true } //hash code trait override fun hashCode(): Int { var __r = 0 - __r = __r*31 + beanTypes.contentDeepHashCode() + __r = __r*31 + beanDefinitions.contentDeepHashCode() return __r } //pretty print override fun print(printer: PrettyPrinter) { printer.println("SpringAnalyzerResult (") printer.indent { - print("beanTypes = "); beanTypes.print(printer); println() + print("beanDefinitions = "); beanDefinitions.print(printer); println() } printer.print(")") } diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/postProcessors/UtBotBeanFactoryPostProcessor.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/postProcessors/UtBotBeanFactoryPostProcessor.kt index e351253d2f..969a5d4c5a 100644 --- a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/postProcessors/UtBotBeanFactoryPostProcessor.kt +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/postProcessors/UtBotBeanFactoryPostProcessor.kt @@ -10,6 +10,8 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory import org.springframework.beans.factory.support.BeanDefinitionRegistry import org.springframework.core.PriorityOrdered import org.utbot.spring.exception.UtBotSpringShutdownException +import org.utbot.spring.generated.BeanAdditionalData +import org.utbot.spring.generated.BeanDefinitionData val logger = getLogger() @@ -22,24 +24,25 @@ object UtBotBeanFactoryPostProcessor : BeanFactoryPostProcessor, PriorityOrdered override fun postProcessBeanFactory(beanFactory: ConfigurableListableBeanFactory) { logger.info { "Started post-processing bean factory in UtBot" } - val beanQualifiedNames = findBeanClassNames(beanFactory) - logger.info { "Detected ${beanQualifiedNames.size} bean qualified names" } + val beanDefinitions = findBeanDefinitions(beanFactory) + logger.info { "Detected ${beanDefinitions.size} bean qualified names" } logger.info { "Finished post-processing bean factory in UtBot" } destroyBeanDefinitions(beanFactory) - throw UtBotSpringShutdownException("Finished post-processing bean factory in UtBot", beanQualifiedNames) + throw UtBotSpringShutdownException("Finished post-processing bean factory in UtBot", beanDefinitions) } - private fun findBeanClassNames(beanFactory: ConfigurableListableBeanFactory): List = + private fun findBeanDefinitions(beanFactory: ConfigurableListableBeanFactory): List = beanFactory.beanDefinitionNames - .mapNotNull { getBeanFqn(beanFactory, it) } - .filterNot { it.startsWith("org.utbot.spring") } - .distinct() + .mapNotNull { getBeanDefinitionData(beanFactory, it) } + .filterNot { it.beanTypeFqn.startsWith("org.utbot.spring") } - private fun getBeanFqn(beanFactory: ConfigurableListableBeanFactory, beanName: String): String? { + private fun getBeanDefinitionData(beanFactory: ConfigurableListableBeanFactory, beanName: String): BeanDefinitionData? { val beanDefinition = beanFactory.getBeanDefinition(beanName) - return if (beanDefinition is AnnotatedBeanDefinition) { + + var beanAdditionalData: BeanAdditionalData? = null + val beanTypeFqn = if (beanDefinition is AnnotatedBeanDefinition) { if (beanDefinition.factoryMethodMetadata == null) { // there's no factoryMethod so bean is defined with @Component-like annotation rather than @Bean annotation // same approach isn't applicable for @Bean beans, because for them, it returns name of @Configuration class @@ -47,6 +50,10 @@ object UtBotBeanFactoryPostProcessor : BeanFactoryPostProcessor, PriorityOrdered logger.info { "Got $fqn as metadata.className for @Component-like bean: $beanName" } } } else try { + beanAdditionalData = BeanAdditionalData( + beanDefinition.factoryMethodMetadata.methodName, + beanDefinition.factoryMethodMetadata.declaringClassName + ) // TODO to avoid side effects, determine beanClassName without getting bean by analyzing method // defining bean, for example, by finding all its return statements and determining their common type // NOTE: do not simply use return type from method signature because it may be an interface type @@ -62,6 +69,8 @@ object UtBotBeanFactoryPostProcessor : BeanFactoryPostProcessor, PriorityOrdered logger.info { "Got $fqn as beanClassName for XML-like bean: $beanName" } } } + + return beanTypeFqn?.let { BeanDefinitionData(beanName, beanTypeFqn, beanAdditionalData) } } private fun destroyBeanDefinitions(beanFactory: ConfigurableListableBeanFactory) { diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/process/SpringAnalyzerProcess.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/process/SpringAnalyzerProcess.kt index 9b0509ec06..6779251838 100644 --- a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/process/SpringAnalyzerProcess.kt +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/process/SpringAnalyzerProcess.kt @@ -12,7 +12,6 @@ import org.utbot.rd.ProcessWithRdServer import org.utbot.rd.exceptions.InstantProcessDeathException import org.utbot.rd.generated.LoggerModel import org.utbot.rd.generated.loggerModel -import org.utbot.rd.loggers.UtRdKLogger import org.utbot.rd.loggers.setup import org.utbot.rd.onSchedulerBlocking import org.utbot.rd.startBlocking @@ -20,6 +19,7 @@ import org.utbot.rd.startUtProcessWithRdServer import org.utbot.rd.terminateOnException import org.utbot.spring.generated.SpringAnalyzerParams import org.utbot.spring.generated.SpringAnalyzerProcessModel +import org.utbot.spring.generated.SpringAnalyzerResult import org.utbot.spring.generated.springAnalyzerProcessModel import java.io.File import java.net.URL @@ -113,13 +113,12 @@ class SpringAnalyzerProcess private constructor( private val springAnalyzerModel: SpringAnalyzerProcessModel = onSchedulerBlocking { protocol.springAnalyzerProcessModel } private val loggerModel: LoggerModel = onSchedulerBlocking { protocol.loggerModel } - fun getBeanQualifiedNames( + fun getBeanDefinitions( configuration: String, fileStorage: Array, profileExpression: String?, - ): List { + ): SpringAnalyzerResult { val params = SpringAnalyzerParams(configuration, fileStorage, profileExpression) - val result = springAnalyzerModel.analyze.startBlocking(params) - return result.beanTypes.toList() + return springAnalyzerModel.analyze.startBlocking(params) } } diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/process/SpringAnalyzerProcessMain.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/process/SpringAnalyzerProcessMain.kt index ab0c2378ce..a8129ab204 100644 --- a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/process/SpringAnalyzerProcessMain.kt +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/process/SpringAnalyzerProcessMain.kt @@ -46,8 +46,9 @@ private fun SpringAnalyzerProcessModel.setup(watchdog: IdleWatchdog, realProtoco params.fileStorage.map { File(it).toURI().toURL() }, params.profileExpression, ) + SpringAnalyzerResult( - SpringApplicationAnalyzerFacade(applicationData).analyze().toTypedArray() + SpringApplicationAnalyzerFacade(applicationData).analyze().toTypedArray() ) } } \ No newline at end of file