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 b7a9db87ac..4e325a414b 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 @@ -1323,6 +1323,13 @@ enum class TypeReplacementMode { NoImplementors, } +interface CodeGenerationContext + +interface SpringCodeGenerationContext : CodeGenerationContext { + val springTestType: SpringTestType + val springSettings: SpringSettings +} + /** * A context to use when no specific data is required. * @@ -1332,7 +1339,7 @@ enum class TypeReplacementMode { open class ApplicationContext( val mockFrameworkInstalled: Boolean = true, staticsMockingIsConfigured: Boolean = true, -) { +) : CodeGenerationContext { var staticsMockingIsConfigured = staticsMockingIsConfigured private set @@ -1384,21 +1391,26 @@ open class ApplicationContext( ): Boolean = field.isFinal || !field.isPublic } -sealed class TypeReplacementApproach { - /** - * Do not replace interfaces and abstract classes with concrete implementors. - * Use mocking instead of it. - */ - object DoNotReplace : TypeReplacementApproach() +sealed interface SpringConfiguration { + class JavaConfiguration(val classBinaryName: String) : SpringConfiguration + class XMLConfiguration(val absolutePath: String) : SpringConfiguration +} - /** - * Try to replace interfaces and abstract classes with concrete implementors - * obtained from bean definitions. - * If it is impossible, use mocking. - * - * Currently used in Spring applications only. - */ - class ReplaceIfPossible(val config: String) : TypeReplacementApproach() +sealed interface SpringSettings { + class AbsentSpringSettings : SpringSettings { + // Denotes no configuration and no profile setting + + // NOTICE: + // `class` should not be replaced with `object` + // in order to avoid issues caused by Kryo deserialization + // that creates new instances breaking `when` expressions + // that check reference equality instead of type equality + } + + class PresentSpringSettings( + val configuration: SpringConfiguration, + val profiles: Array + ) : SpringSettings } /** @@ -1422,9 +1434,9 @@ class SpringApplicationContext( staticsMockingIsConfigured: Boolean, val beanDefinitions: List = emptyList(), private val shouldUseImplementors: Boolean, - val typeReplacementApproach: TypeReplacementApproach, - val testType: SpringTestsType -): ApplicationContext(mockInstalled, staticsMockingIsConfigured) { + override val springTestType: SpringTestType, + override val springSettings: SpringSettings, +): ApplicationContext(mockInstalled, staticsMockingIsConfigured), SpringCodeGenerationContext { companion object { private val logger = KotlinLogging.logger {} @@ -1436,10 +1448,9 @@ class SpringApplicationContext( private val springInjectedClasses: Set get() { if (!areInjectedClassesInitialized) { - // TODO: use more info from SpringBeanDefinitionData than beanTypeFqn offers here - for (beanFqn in beanDefinitions.map { it.beanTypeFqn }) { + for (beanTypeName in beanDefinitions.map { it.beanTypeName }) { try { - val beanClass = utContext.classLoader.loadClass(beanFqn) + val beanClass = utContext.classLoader.loadClass(beanTypeName) if (!beanClass.isAbstract && !beanClass.isInterface && !beanClass.isLocalClass && (!beanClass.isMemberClass || beanClass.isStatic)) { springInjectedClassesStorage += beanClass.id @@ -1449,7 +1460,7 @@ class SpringApplicationContext( // it is possible to have problems with classes loading. when (e) { is ClassNotFoundException, is NoClassDefFoundError, is IllegalAccessError -> - logger.warn { "Failed to load bean class for $beanFqn (${e.message})" } + logger.warn { "Failed to load bean class for $beanTypeName (${e.message})" } else -> throw e } @@ -1500,19 +1511,19 @@ class SpringApplicationContext( ): Boolean = field.fieldId in classUnderTest.allDeclaredFieldIds && field.declaringClass.id !in springInjectedClasses } -enum class SpringTestsType( +enum class SpringTestType( override val id: String, override val displayName: String, override val description: String, // Integration tests generation requires spring test framework being installed - var frameworkInstalled: Boolean = false, + var testFrameworkInstalled: Boolean = false, ) : CodeGenerationSettingItem { - UNIT_TESTS( + UNIT_TEST( "Unit tests", "Unit tests", "Generate unit tests mocking other classes" ), - INTEGRATION_TESTS( + INTEGRATION_TEST( "Integration tests", "Integration tests", "Generate integration tests autowiring real instance" @@ -1521,8 +1532,8 @@ enum class SpringTestsType( override fun toString() = id companion object : CodeGenerationSettingBox { - override val defaultItem = UNIT_TESTS - override val allItems: List = values().toList() + override val defaultItem = UNIT_TEST + override val allItems: List = values().toList() } } @@ -1530,10 +1541,12 @@ enum class SpringTestsType( * Describes information about beans obtained from Spring analysis process. * * Contains the name of the bean, its type (class or interface) and optional additional data. + * + * @param beanTypeName a name in a form obtained by [java.lang.Class.getName] method. */ data class BeanDefinitionData( val beanName: String, - val beanTypeFqn: String, + val beanTypeName: String, val additionalData: BeanAdditionalData?, ) @@ -1542,11 +1555,13 @@ data class BeanDefinitionData( * * 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). + * + * @param configClassName a name in a form obtained by [java.lang.Class.getName] method. */ data class BeanAdditionalData( val factoryMethodName: String, val parameterTypes: List, - val configClassFqn: String, + val configClassName: String, ) val RefType.isAbstractType diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/SpringModelUtils.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/SpringModelUtils.kt index 9befad4bf4..b4a890d797 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/SpringModelUtils.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/SpringModelUtils.kt @@ -31,6 +31,9 @@ object SpringModelUtils { val springBootTestContextBootstrapperClassId = ClassId("org.springframework.boot.test.context.SpringBootTestContextBootstrapper") + val activeProfilesClassId = ClassId("org.springframework.test.context.ActiveProfiles") + val contextConfigurationClassId = ClassId("org.springframework.test.context.ContextConfiguration") + // most likely only one persistent library is on the classpath, but we need to be able to work with either of them private val persistentLibraries = listOf("javax.persistence", "jakarta.persistence") diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/UtSettingsUtil.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/UtSettingsUtil.kt index 2c3117d81d..a0585ca383 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/UtSettingsUtil.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/UtSettingsUtil.kt @@ -14,4 +14,4 @@ inline fun withStaticsSubstitutionRequired(condition: Boolean, block: () -> } finally { UtSettings.substituteStaticsWithSymbolicVariable = standardSubstitutionSetting } -} \ No newline at end of file +} 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 405e71996c..d84e80a123 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt @@ -39,6 +39,7 @@ import org.utbot.framework.plugin.api.util.* import org.utbot.framework.util.convertToAssemble import org.utbot.framework.util.graph import org.utbot.framework.util.sootMethod +import org.utbot.framework.plugin.api.SpringSettings.* import org.utbot.fuzzer.* import org.utbot.fuzzing.* import org.utbot.fuzzing.providers.FieldValueProvider @@ -388,7 +389,7 @@ class UtBotSymbolicEngine( var testEmittedByFuzzer = 0 val valueProviders = ValueProvider.of(defaultValueProviders(defaultIdGenerator)) .letIf(applicationContext is SpringApplicationContext - && applicationContext.typeReplacementApproach is TypeReplacementApproach.ReplaceIfPossible + && applicationContext.springSettings is PresentSpringSettings ) { provider -> val relevantRepositories = concreteExecutor.getRelevantSpringRepositories(methodUnderTest.classId) logger.info { "Detected relevant repositories for class ${methodUnderTest.classId}: $relevantRepositories" } @@ -412,7 +413,7 @@ class UtBotSymbolicEngine( defaultIdGenerator, beanNameProvider = { classId -> (applicationContext as SpringApplicationContext).beanDefinitions - .filter { it.beanTypeFqn == classId.name } + .filter { it.beanTypeName == classId.name } .map { it.beanName } }, relevantRepositories = relevantRepositories diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/TestClassModel.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/TestClassModel.kt index aabb89803a..d613d7e2d0 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/TestClassModel.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/TestClassModel.kt @@ -30,9 +30,8 @@ class SpringTestClassModel( val springSpecificInformation: SpringSpecificInformation, ): TestClassModel(classUnderTest, methodTestSets, nestedClasses) - class SpringSpecificInformation( - val thisInstanceModels: TypedModelWrappers = mapOf(), - val thisInstanceDependentMocks: TypedModelWrappers = mapOf(), - val autowiredFromContextModels: TypedModelWrappers = mapOf(), + val thisInstanceModels: TypedModelWrappers, + val thisInstanceDependentMocks: TypedModelWrappers, + val autowiredFromContextModels: TypedModelWrappers, ) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/generator/SpringCodeGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/generator/SpringCodeGenerator.kt index fca9b79b19..3dbcedf3b7 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/generator/SpringCodeGenerator.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/generator/SpringCodeGenerator.kt @@ -17,12 +17,14 @@ import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.CodegenLanguage import org.utbot.framework.plugin.api.ExecutableId import org.utbot.framework.plugin.api.MockFramework -import org.utbot.framework.plugin.api.SpringTestsType +import org.utbot.framework.plugin.api.SpringTestType +import org.utbot.framework.plugin.api.SpringCodeGenerationContext +import org.utbot.framework.plugin.api.SpringSettings.* class SpringCodeGenerator( - private val springTestsType: SpringTestsType = SpringTestsType.defaultItem, val classUnderTest: ClassId, val projectType: ProjectType, + val codeGenerationContext: SpringCodeGenerationContext, paramNames: MutableMap> = mutableMapOf(), generateUtilClassFile: Boolean = false, testFramework: TestFramework = TestFramework.defaultItem, @@ -59,9 +61,13 @@ class SpringCodeGenerator( val testClassModel = SpringTestClassModelBuilder(context).createTestClassModel(classUnderTest, testSets) logger.info { "Code generation phase started at ${now()}" } - val astConstructor = when (springTestsType) { - SpringTestsType.UNIT_TESTS -> CgSpringUnitTestClassConstructor(context) - SpringTestsType.INTEGRATION_TESTS -> CgSpringIntegrationTestClassConstructor(context) + val astConstructor = when (codeGenerationContext.springTestType) { + SpringTestType.UNIT_TEST -> CgSpringUnitTestClassConstructor(context) + SpringTestType.INTEGRATION_TEST -> + when (val settings = codeGenerationContext.springSettings) { + is PresentSpringSettings -> CgSpringIntegrationTestClassConstructor(context, settings) + is AbsentSpringSettings -> error("No Spring settings were provided for Spring integration test generation.") + } } val testClassFile = astConstructor.construct(testClassModel) logger.info { "Code generation phase finished at ${now()}" } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgAbstractSpringTestClassConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgAbstractSpringTestClassConstructor.kt index 4e7f4761b8..7f6db82243 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgAbstractSpringTestClassConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgAbstractSpringTestClassConstructor.kt @@ -27,7 +27,6 @@ abstract class CgAbstractSpringTestClassConstructor(context: CgContext): protected val variableConstructor: CgSpringVariableConstructor = CgComponents.getVariableConstructorBy(context) as CgSpringVariableConstructor - protected val statementConstructor: CgStatementConstructor = CgComponents.getStatementConstructorBy(context) override fun constructTestClassBody(testClassModel: SpringTestClassModel): CgClassBody { return buildClassBody(currentTestClass) { @@ -88,7 +87,7 @@ abstract class CgAbstractSpringTestClassConstructor(context: CgContext): annotationClassId: ClassId, groupedModelsByClassId: TypedModelWrappers, ): List { - val annotation = statementConstructor.addAnnotation(annotationClassId, Field) + val annotation = addAnnotation(annotationClassId, Field) val constructedDeclarations = mutableListOf() for ((classId, listOfUtModels) in groupedModelsByClassId) { @@ -133,7 +132,7 @@ abstract class CgAbstractSpringTestClassConstructor(context: CgContext): } protected fun constructBeforeMethod(statements: List): CgFrameworkUtilMethod { - val beforeAnnotation = statementConstructor.addAnnotation(context.testFramework.beforeMethodId, Method) + val beforeAnnotation = addAnnotation(context.testFramework.beforeMethodId, Method) return CgFrameworkUtilMethod( name = "setUp", statements = statements, @@ -143,7 +142,7 @@ abstract class CgAbstractSpringTestClassConstructor(context: CgContext): } protected fun constructAfterMethod(statements: List): CgFrameworkUtilMethod { - val afterAnnotation = statementConstructor.addAnnotation(context.testFramework.afterMethodId, Method) + val afterAnnotation = addAnnotation(context.testFramework.afterMethodId, Method) return CgFrameworkUtilMethod( name = "tearDown", statements = statements, diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgSpringIntegrationTestClassConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgSpringIntegrationTestClassConstructor.kt index b089bbfe6e..0fd7e103d6 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgSpringIntegrationTestClassConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgSpringIntegrationTestClassConstructor.kt @@ -7,12 +7,16 @@ import org.utbot.framework.codegen.domain.TestNg import org.utbot.framework.codegen.domain.context.CgContext import org.utbot.framework.codegen.domain.models.* import org.utbot.framework.codegen.domain.models.AnnotationTarget.* +import org.utbot.framework.codegen.util.resolve import org.utbot.framework.plugin.api.ClassId -import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.framework.plugin.api.SpringSettings.* +import org.utbot.framework.plugin.api.SpringConfiguration.* import org.utbot.framework.plugin.api.util.SpringModelUtils +import org.utbot.framework.plugin.api.util.SpringModelUtils.activeProfilesClassId import org.utbot.framework.plugin.api.util.SpringModelUtils.autoConfigureTestDbClassId import org.utbot.framework.plugin.api.util.SpringModelUtils.autowiredClassId import org.utbot.framework.plugin.api.util.SpringModelUtils.bootstrapWithClassId +import org.utbot.framework.plugin.api.util.SpringModelUtils.contextConfigurationClassId import org.utbot.framework.plugin.api.util.SpringModelUtils.dirtiesContextClassId import org.utbot.framework.plugin.api.util.SpringModelUtils.dirtiesContextClassModeClassId import org.utbot.framework.plugin.api.util.SpringModelUtils.springBootTestContextBootstrapperClassId @@ -20,20 +24,25 @@ import org.utbot.framework.plugin.api.util.SpringModelUtils.springExtensionClass import org.utbot.framework.plugin.api.util.SpringModelUtils.transactionalClassId import org.utbot.framework.plugin.api.util.utContext -class CgSpringIntegrationTestClassConstructor(context: CgContext) : CgAbstractSpringTestClassConstructor(context) { +class CgSpringIntegrationTestClassConstructor( + context: CgContext, + private val springSettings: PresentSpringSettings +) : CgAbstractSpringTestClassConstructor(context) { override fun constructTestClass(testClassModel: SpringTestClassModel): CgClass { - collectSpringSpecificAnnotations() + addNecessarySpringSpecificAnnotations() return super.constructTestClass(testClassModel) } override fun constructClassFields(testClassModel: SpringTestClassModel): List { - val autowiredFromContextModels = testClassModel.springSpecificInformation.autowiredFromContextModels + val autowiredFromContextModels = + testClassModel.springSpecificInformation.autowiredFromContextModels return constructFieldsWithAnnotation(autowiredClassId, autowiredFromContextModels) } - override fun constructAdditionalMethods() = CgMethodsCluster(header = null, content = emptyList()) + override fun constructAdditionalMethods() = + CgMethodsCluster(header = null, content = emptyList()) - private fun collectSpringSpecificAnnotations() { + private fun addNecessarySpringSpecificAnnotations() { val springRunnerType = when (testFramework) { Junit4 -> SpringModelUtils.runWithClassId Junit5 -> SpringModelUtils.extendWithClassId @@ -41,17 +50,53 @@ class CgSpringIntegrationTestClassConstructor(context: CgContext) : CgAbstractSp else -> error("Trying to generate tests for Spring project with non-JVM framework") } - statementConstructor.addAnnotation( + addAnnotation( classId = springRunnerType, argument = createGetClassExpression(springExtensionClassId, codegenLanguage), target = Class, ) - statementConstructor.addAnnotation( + addAnnotation( classId = bootstrapWithClassId, argument = createGetClassExpression(springBootTestContextBootstrapperClassId, codegenLanguage), target = Class, ) - + addAnnotation( + classId = activeProfilesClassId, + namedArguments = + listOf( + CgNamedAnnotationArgument( + name = "profiles", + value = + CgArrayAnnotationArgument( + springSettings.profiles.map { profile -> + profile.resolve() + } + ) + ) + ), + target = Class, + ) + addAnnotation( + classId = contextConfigurationClassId, + namedArguments = + listOf( + CgNamedAnnotationArgument( + name = "classes", + value = CgArrayAnnotationArgument( + listOf( + createGetClassExpression( + // TODO: + // For now we support only JavaConfigurations in integration tests. + // Adapt for XMLConfigurations when supported. + ClassId((springSettings.configuration as JavaConfiguration).classBinaryName), + codegenLanguage + ) + ) + ) + ) + ), + target = Class, + ) addAnnotation( classId = dirtiesContextClassId, namedArguments = listOf( diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/TestCaseGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/TestCaseGenerator.kt index 41c067adc8..e7e2e5a65c 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/TestCaseGenerator.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/TestCaseGenerator.kt @@ -34,6 +34,8 @@ import org.utbot.framework.util.ConflictTriggers import org.utbot.framework.util.SootUtils import org.utbot.framework.util.jimpleBody import org.utbot.framework.util.toModel +import org.utbot.framework.plugin.api.SpringSettings.* +import org.utbot.framework.plugin.api.SpringTestType.* import org.utbot.instrumentation.ConcreteExecutor import org.utbot.instrumentation.instrumentation.execution.SpringUtExecutionInstrumentation import org.utbot.instrumentation.instrumentation.execution.UtExecutionInstrumentation @@ -69,19 +71,19 @@ open class TestCaseGenerator( private val timeoutLogger: KLogger = KotlinLogging.logger(logger.name + ".timeout") private val executionInstrumentation by lazy { when (applicationContext) { - is SpringApplicationContext -> when (val approach = applicationContext.typeReplacementApproach) { - is TypeReplacementApproach.ReplaceIfPossible -> - when (applicationContext.testType) { - SpringTestsType.UNIT_TESTS -> UtExecutionInstrumentation - SpringTestsType.INTEGRATION_TESTS -> SpringUtExecutionInstrumentation( - UtExecutionInstrumentation, - approach.config, - applicationContext.beanDefinitions, - buildDirs.map { it.toURL() }.toTypedArray(), - ) - } - is TypeReplacementApproach.DoNotReplace -> UtExecutionInstrumentation + is SpringApplicationContext -> when (val settings = applicationContext.springSettings) { + is AbsentSpringSettings -> UtExecutionInstrumentation + is PresentSpringSettings -> when (applicationContext.springTestType) { + UNIT_TEST -> UtExecutionInstrumentation + INTEGRATION_TEST -> SpringUtExecutionInstrumentation( + UtExecutionInstrumentation, + settings, + applicationContext.beanDefinitions, + buildDirs.map { it.toURL() }.toTypedArray(), + ) + } } + else -> UtExecutionInstrumentation } } 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 45f9eaf582..0fe1b94720 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 @@ -83,9 +83,7 @@ private fun EngineProcessModel.setup(kryoHelper: KryoHelper, watchdog: IdleWatch val springAnalyzerProcess = SpringAnalyzerProcess.createBlocking(params.classpath.toList()) val result = springAnalyzerProcess.terminateOnException { _ -> springAnalyzerProcess.getBeanDefinitions( - params.config, - params.fileStorage, - params.profileExpression, + kryoHelper.readObject(params.springSettings) ) } springAnalyzerProcess.terminate() @@ -152,16 +150,15 @@ private fun EngineProcessModel.setup(kryoHelper: KryoHelper, watchdog: IdleWatch } } watchdog.measureTimeForActiveCall(render, "Rendering tests") { params -> - val projectType = ProjectType.valueOf(params.projectType) - val codeGenerator = projectType.createCodeGenerator(kryoHelper, params) + val codeGenerator = createCodeGenerator(kryoHelper, params, testGenerator.applicationContext) codeGenerator.generateAsStringWithTestReport(testSets[params.testSetsId]!!).let { testGenerationReports.add(it.testsGenerationReport) RenderResult(it.generatedCode, it.utilClassKind?.javaClass?.simpleName) } } - watchdog.measureTimeForActiveCall(obtainClassId, "Obtain class id in UtContext") { canonicalName -> - kryoHelper.writeObject(UtContext.currentContext()!!.classLoader.loadClass(canonicalName).id) + watchdog.measureTimeForActiveCall(obtainClassId, "Obtain class id in UtContext") { binaryName -> + kryoHelper.writeObject(UtContext.currentContext()!!.classLoader.loadClass(binaryName).id) } watchdog.measureTimeForActiveCall(findMethodsInClassMatchingSelected, "Find methods in Class") { params -> val classId = kryoHelper.readObject(params.classId) @@ -292,56 +289,70 @@ private fun destinationWarningMessage(testPackageName: String?, classUnderTestPa } } -private fun ProjectType.createCodeGenerator(kryoHelper: KryoHelper, params: RenderParams): AbstractCodeGenerator { +private fun createCodeGenerator(kryoHelper: KryoHelper, params: RenderParams, codeGenerationContext: CodeGenerationContext): AbstractCodeGenerator { with(params) { val classUnderTest: ClassId = kryoHelper.readObject(classUnderTest) val paramNames: MutableMap> = kryoHelper.readObject(paramNames) val testFramework = testFrameworkByName(testFramework) val staticMocking = if (staticsMocking.startsWith("No")) NoStaticMocking else MockitoStaticMocking val forceStaticMocking: ForceStaticMocking = kryoHelper.readObject(forceStaticMocking) + val projectType = ProjectType.valueOf(projectType) - return when (this@createCodeGenerator) { - ProjectType.PureJvm -> CodeGenerator( - classUnderTest = classUnderTest, - projectType = ProjectType.valueOf(projectType), - generateUtilClassFile = generateUtilClassFile, - paramNames = paramNames, - testFramework = testFramework, - mockFramework = MockFramework.valueOf(mockFramework), - codegenLanguage = CodegenLanguage.valueOf(codegenLanguage), - cgLanguageAssistant = CgLanguageAssistant.getByCodegenLanguage(CodegenLanguage.valueOf(codegenLanguage)), - parameterizedTestSource = ParametrizedTestSource.valueOf(parameterizedTestSource), - staticsMocking = staticMocking, - forceStaticMocking = forceStaticMocking, - generateWarningsForStaticMocking = generateWarningsForStaticMocking, - runtimeExceptionTestsBehaviour = RuntimeExceptionTestsBehaviour.valueOf(runtimeExceptionTestsBehaviour), - hangingTestsTimeout = HangingTestsTimeout(hangingTestsTimeout), - enableTestsTimeout = enableTestsTimeout, - testClassPackageName = testClassPackageName, - ) - - ProjectType.Spring -> SpringCodeGenerator( - springTestsType = SpringTestsType.valueOf(springTestsType), - classUnderTest = classUnderTest, - projectType = ProjectType.valueOf(projectType), - generateUtilClassFile = generateUtilClassFile, - paramNames = paramNames, - testFramework = testFramework, - mockFramework = MockFramework.valueOf(mockFramework), - codegenLanguage = CodegenLanguage.valueOf(codegenLanguage), - cgLanguageAssistant = CgLanguageAssistant.getByCodegenLanguage(CodegenLanguage.valueOf(codegenLanguage)), - parameterizedTestSource = ParametrizedTestSource.valueOf(parameterizedTestSource), - staticsMocking = staticMocking, - forceStaticMocking = forceStaticMocking, - generateWarningsForStaticMocking = generateWarningsForStaticMocking, - runtimeExceptionTestsBehaviour = RuntimeExceptionTestsBehaviour.valueOf(runtimeExceptionTestsBehaviour), - hangingTestsTimeout = HangingTestsTimeout(hangingTestsTimeout), - enableTestsTimeout = enableTestsTimeout, - testClassPackageName = testClassPackageName, - ) + return when (codeGenerationContext) { + is SpringCodeGenerationContext -> { + SpringCodeGenerator( + classUnderTest = classUnderTest, + projectType = projectType, + codeGenerationContext = codeGenerationContext, + generateUtilClassFile = generateUtilClassFile, + paramNames = paramNames, + testFramework = testFramework, + mockFramework = MockFramework.valueOf(mockFramework), + codegenLanguage = CodegenLanguage.valueOf(codegenLanguage), + cgLanguageAssistant = CgLanguageAssistant.getByCodegenLanguage( + CodegenLanguage.valueOf( + codegenLanguage + ) + ), + parameterizedTestSource = ParametrizedTestSource.valueOf(parameterizedTestSource), + staticsMocking = staticMocking, + forceStaticMocking = forceStaticMocking, + generateWarningsForStaticMocking = generateWarningsForStaticMocking, + runtimeExceptionTestsBehaviour = RuntimeExceptionTestsBehaviour.valueOf( + runtimeExceptionTestsBehaviour + ), + hangingTestsTimeout = HangingTestsTimeout(hangingTestsTimeout), + enableTestsTimeout = enableTestsTimeout, + testClassPackageName = testClassPackageName, + ) + } - ProjectType.Python -> error("Python code generator can not be created in Engine process") - ProjectType.JavaScript -> error("JavaScript code generator can not be created in Engine process") + else -> { + CodeGenerator( + classUnderTest = classUnderTest, + projectType = projectType, + generateUtilClassFile = generateUtilClassFile, + paramNames = paramNames, + testFramework = testFramework, + mockFramework = MockFramework.valueOf(mockFramework), + codegenLanguage = CodegenLanguage.valueOf(codegenLanguage), + cgLanguageAssistant = CgLanguageAssistant.getByCodegenLanguage( + CodegenLanguage.valueOf( + codegenLanguage + ) + ), + parameterizedTestSource = ParametrizedTestSource.valueOf(parameterizedTestSource), + staticsMocking = staticMocking, + forceStaticMocking = forceStaticMocking, + generateWarningsForStaticMocking = generateWarningsForStaticMocking, + runtimeExceptionTestsBehaviour = RuntimeExceptionTestsBehaviour.valueOf( + runtimeExceptionTestsBehaviour + ), + hangingTestsTimeout = HangingTestsTimeout(hangingTestsTimeout), + enableTestsTimeout = enableTestsTimeout, + testClassPackageName = testClassPackageName, + ) + } } } } \ No newline at end of file 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 ed2db61f68..d98a214ce0 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 @@ -75,7 +75,7 @@ class EngineProcessModel private constructor( } - const val serializationHash = 2605460374173973135L + const val serializationHash = -3702497121699333502L } override val serializersOwner: ISerializersOwner get() = EngineProcessModel @@ -182,33 +182,33 @@ val IProtocol.engineProcessModel get() = getOrCreateExtension(EngineProcessModel /** - * #### Generated from [EngineProcessModel.kt:132] + * #### Generated from [EngineProcessModel.kt:130] */ data class BeanAdditionalData ( val factoryMethodName: String, val parameterTypes: List, - val configClassFqn: String + val configClassName: 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 parameterTypes = buffer.readList { buffer.readString() } - val configClassFqn = buffer.readString() - return BeanAdditionalData(factoryMethodName, parameterTypes, configClassFqn) + val configClassName = buffer.readString() + return BeanAdditionalData(factoryMethodName, parameterTypes, configClassName) } - + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: BeanAdditionalData) { buffer.writeString(value.factoryMethodName) buffer.writeList(value.parameterTypes) { v -> buffer.writeString(v) } - buffer.writeString(value.configClassFqn) + buffer.writeString(value.configClassName) } - - + + } //fields //methods @@ -218,13 +218,13 @@ data class BeanAdditionalData ( 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 (parameterTypes != other.parameterTypes) return false - if (configClassFqn != other.configClassFqn) return false - + if (configClassName != other.configClassName) return false + return true } //hash code trait @@ -232,7 +232,7 @@ data class BeanAdditionalData ( var __r = 0 __r = __r*31 + factoryMethodName.hashCode() __r = __r*31 + parameterTypes.hashCode() - __r = __r*31 + configClassFqn.hashCode() + __r = __r*31 + configClassName.hashCode() return __r } //pretty print @@ -241,7 +241,7 @@ data class BeanAdditionalData ( printer.indent { print("factoryMethodName = "); factoryMethodName.print(printer); println() print("parameterTypes = "); parameterTypes.print(printer); println() - print("configClassFqn = "); configClassFqn.print(printer); println() + print("configClassName = "); configClassName.print(printer); println() } printer.print(")") } @@ -251,7 +251,7 @@ data class BeanAdditionalData ( /** - * #### Generated from [EngineProcessModel.kt:137] + * #### Generated from [EngineProcessModel.kt:135] */ data class BeanDefinitionData ( val beanName: String, @@ -259,10 +259,10 @@ data class BeanDefinitionData ( 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() @@ -270,14 +270,14 @@ data class BeanDefinitionData ( 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 @@ -287,13 +287,13 @@ data class BeanDefinitionData ( 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 @@ -320,7 +320,7 @@ data class BeanDefinitionData ( /** - * #### Generated from [EngineProcessModel.kt:106] + * #### Generated from [EngineProcessModel.kt:104] */ data class FindMethodParamNamesArguments ( val classId: ByteArray, @@ -383,7 +383,7 @@ data class FindMethodParamNamesArguments ( /** - * #### Generated from [EngineProcessModel.kt:110] + * #### Generated from [EngineProcessModel.kt:108] */ data class FindMethodParamNamesResult ( val paramNames: ByteArray @@ -440,7 +440,7 @@ data class FindMethodParamNamesResult ( /** - * #### Generated from [EngineProcessModel.kt:99] + * #### Generated from [EngineProcessModel.kt:97] */ data class FindMethodsInClassMatchingSelectedArguments ( val classId: ByteArray, @@ -503,7 +503,7 @@ data class FindMethodsInClassMatchingSelectedArguments ( /** - * #### Generated from [EngineProcessModel.kt:103] + * #### Generated from [EngineProcessModel.kt:101] */ data class FindMethodsInClassMatchingSelectedResult ( val executableIds: ByteArray @@ -560,7 +560,7 @@ data class FindMethodsInClassMatchingSelectedResult ( /** - * #### Generated from [EngineProcessModel.kt:44] + * #### Generated from [EngineProcessModel.kt:43] */ data class GenerateParams ( val methods: ByteArray, @@ -630,7 +630,7 @@ data class GenerateParams ( if (fuzzingValue != other.fuzzingValue) return false if (searchDirectory != other.searchDirectory) return false if (taintConfigPath != other.taintConfigPath) return false - + return true } //hash code trait @@ -671,7 +671,7 @@ data class GenerateParams ( /** - * #### Generated from [EngineProcessModel.kt:60] + * #### Generated from [EngineProcessModel.kt:59] */ data class GenerateResult ( val notEmptyCases: Int, @@ -734,7 +734,7 @@ data class GenerateResult ( /** - * #### Generated from [EngineProcessModel.kt:118] + * #### Generated from [EngineProcessModel.kt:116] */ data class GenerateTestReportArgs ( val eventLogMessage: String?, @@ -827,7 +827,7 @@ data class GenerateTestReportArgs ( /** - * #### Generated from [EngineProcessModel.kt:127] + * #### Generated from [EngineProcessModel.kt:125] */ data class GenerateTestReportResult ( val notifyMessage: String, @@ -900,32 +900,26 @@ data class GenerateTestReportResult ( */ data class GetSpringBeanDefinitions ( val classpath: Array, - val config: String, - val fileStorage: Array, - val profileExpression: String? + val springSettings: ByteArray ) : IPrintable { //companion - + companion object : IMarshaller { override val _type: KClass = GetSpringBeanDefinitions::class - + @Suppress("UNCHECKED_CAST") 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 GetSpringBeanDefinitions(classpath, config, fileStorage, profileExpression) + val springSettings = buffer.readByteArray() + return GetSpringBeanDefinitions(classpath, springSettings) } - + 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) } - buffer.writeNullable(value.profileExpression) { buffer.writeString(it) } + buffer.writeByteArray(value.springSettings) } - - + + } //fields //methods @@ -935,23 +929,19 @@ data class GetSpringBeanDefinitions ( override fun equals(other: Any?): Boolean { if (this === other) return true if (other == null || other::class != this::class) return false - + other as GetSpringBeanDefinitions - + if (!(classpath contentDeepEquals other.classpath)) return false - if (config != other.config) return false - if (!(fileStorage contentDeepEquals other.fileStorage)) return false - if (profileExpression != other.profileExpression) return false - + if (!(springSettings contentEquals other.springSettings)) return false + return true } //hash code trait override fun hashCode(): Int { var __r = 0 __r = __r*31 + classpath.contentDeepHashCode() - __r = __r*31 + config.hashCode() - __r = __r*31 + fileStorage.contentDeepHashCode() - __r = __r*31 + if (profileExpression != null) profileExpression.hashCode() else 0 + __r = __r*31 + springSettings.contentHashCode() return __r } //pretty print @@ -959,9 +949,7 @@ data class GetSpringBeanDefinitions ( printer.println("GetSpringBeanDefinitions (") printer.indent { print("classpath = "); classpath.print(printer); println() - print("config = "); config.print(printer); println() - print("fileStorage = "); fileStorage.print(printer); println() - print("profileExpression = "); profileExpression.print(printer); println() + print("springSettings = "); springSettings.print(printer); println() } printer.print(")") } @@ -1034,7 +1022,7 @@ data class JdkInfo ( /** - * #### Generated from [EngineProcessModel.kt:94] + * #### Generated from [EngineProcessModel.kt:92] */ data class MethodDescription ( val name: String, @@ -1103,10 +1091,9 @@ data class MethodDescription ( /** - * #### Generated from [EngineProcessModel.kt:64] + * #### Generated from [EngineProcessModel.kt:63] */ data class RenderParams ( - val springTestsType: String, val testSetsId: Long, val classUnderTest: ByteArray, val projectType: String, @@ -1131,7 +1118,6 @@ data class RenderParams ( @Suppress("UNCHECKED_CAST") override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): RenderParams { - val springTestsType = buffer.readString() val testSetsId = buffer.readLong() val classUnderTest = buffer.readByteArray() val projectType = buffer.readString() @@ -1148,11 +1134,10 @@ data class RenderParams ( val hangingTestsTimeout = buffer.readLong() val enableTestsTimeout = buffer.readBool() val testClassPackageName = buffer.readString() - return RenderParams(springTestsType, testSetsId, classUnderTest, projectType, paramNames, generateUtilClassFile, testFramework, mockFramework, codegenLanguage, parameterizedTestSource, staticsMocking, forceStaticMocking, generateWarningsForStaticMocking, runtimeExceptionTestsBehaviour, hangingTestsTimeout, enableTestsTimeout, testClassPackageName) + return RenderParams(testSetsId, classUnderTest, projectType, paramNames, generateUtilClassFile, testFramework, mockFramework, codegenLanguage, parameterizedTestSource, staticsMocking, forceStaticMocking, generateWarningsForStaticMocking, runtimeExceptionTestsBehaviour, hangingTestsTimeout, enableTestsTimeout, testClassPackageName) } override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: RenderParams) { - buffer.writeString(value.springTestsType) buffer.writeLong(value.testSetsId) buffer.writeByteArray(value.classUnderTest) buffer.writeString(value.projectType) @@ -1184,7 +1169,6 @@ data class RenderParams ( other as RenderParams - if (springTestsType != other.springTestsType) return false if (testSetsId != other.testSetsId) return false if (!(classUnderTest contentEquals other.classUnderTest)) return false if (projectType != other.projectType) return false @@ -1207,7 +1191,6 @@ data class RenderParams ( //hash code trait override fun hashCode(): Int { var __r = 0 - __r = __r*31 + springTestsType.hashCode() __r = __r*31 + testSetsId.hashCode() __r = __r*31 + classUnderTest.contentHashCode() __r = __r*31 + projectType.hashCode() @@ -1230,7 +1213,6 @@ data class RenderParams ( override fun print(printer: PrettyPrinter) { printer.println("RenderParams (") printer.indent { - print("springTestsType = "); springTestsType.print(printer); println() print("testSetsId = "); testSetsId.print(printer); println() print("classUnderTest = "); classUnderTest.print(printer); println() print("projectType = "); projectType.print(printer); println() @@ -1376,27 +1358,27 @@ data class SetupContextParams ( /** - * #### Generated from [EngineProcessModel.kt:142] + * #### 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 @@ -1406,11 +1388,11 @@ data class SpringAnalyzerResult ( 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 @@ -1433,7 +1415,7 @@ data class SpringAnalyzerResult ( /** - * #### Generated from [EngineProcessModel.kt:37] + * #### Generated from [EngineProcessModel.kt:36] */ data class TestGeneratorParams ( val buildDir: Array, @@ -1514,7 +1496,7 @@ data class TestGeneratorParams ( /** - * #### Generated from [EngineProcessModel.kt:113] + * #### Generated from [EngineProcessModel.kt:111] */ data class WriteSarifReportArguments ( val testSetsId: Long, diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/SpringUtExecutionInstrumentation.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/SpringUtExecutionInstrumentation.kt index 5f68de1c07..0e6df14e34 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/SpringUtExecutionInstrumentation.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/SpringUtExecutionInstrumentation.kt @@ -7,6 +7,7 @@ import org.utbot.common.hasOnClasspath import org.utbot.framework.plugin.api.BeanDefinitionData import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.SpringRepositoryId +import org.utbot.framework.plugin.api.SpringSettings.* import org.utbot.framework.plugin.api.util.jClass import org.utbot.instrumentation.instrumentation.ArgumentList import org.utbot.instrumentation.instrumentation.Instrumentation @@ -19,11 +20,11 @@ import java.net.URLClassLoader import java.security.ProtectionDomain /** - * UtExecutionInstrumentation wrapper that is aware of Spring config and initialises Spring context + * UtExecutionInstrumentation wrapper that is aware of Spring configuration and profiles and initialises Spring context */ class SpringUtExecutionInstrumentation( private val delegateInstrumentation: UtExecutionInstrumentation, - private val springConfig: String, + private val springSettings: PresentSpringSettings, private val beanDefinitions: List, private val buildDirs: Array, ) : Instrumentation by delegateInstrumentation { @@ -55,7 +56,7 @@ class SpringUtExecutionInstrumentation( ) userSourcesClassLoader = URLClassLoader(buildDirs, null) - instrumentationContext = SpringInstrumentationContext(springConfig, delegateInstrumentation.instrumentationContext) + instrumentationContext = SpringInstrumentationContext(springSettings, delegateInstrumentation.instrumentationContext) delegateInstrumentation.instrumentationContext = instrumentationContext delegateInstrumentation.init(pathsToUserClasses) springApi.beforeTestClass() @@ -83,7 +84,7 @@ class SpringUtExecutionInstrumentation( private fun getRelevantBeans(clazz: Class<*>): Set = relatedBeansCache.getOrPut(clazz) { beanDefinitions - .filter { it.beanTypeFqn == clazz.name } + .filter { it.beanTypeName == clazz.name } // forces `getBean()` to load Spring classes, // otherwise execution of method under test may fail with timeout .onEach { springApi.getBean(it.beanName) } diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/context/SpringInstrumentationContext.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/context/SpringInstrumentationContext.kt index 4ecac5fc69..4a6625949b 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/context/SpringInstrumentationContext.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/context/SpringInstrumentationContext.kt @@ -1,5 +1,7 @@ package org.utbot.instrumentation.instrumentation.execution.context +import org.utbot.framework.plugin.api.SpringSettings.* +import org.utbot.framework.plugin.api.SpringConfiguration.* import org.utbot.framework.plugin.api.UtConcreteValue import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtSpringContextModel @@ -9,7 +11,7 @@ import org.utbot.spring.api.instantiator.SpringApiProviderFacade import org.utbot.spring.api.instantiator.InstantiationSettings class SpringInstrumentationContext( - private val springConfig: String, + private val springSettings: PresentSpringSettings, private val delegateInstrumentationContext: InstrumentationContext, ) : InstrumentationContext by delegateInstrumentationContext { // TODO: recreate context/app every time whenever we change method under test @@ -19,9 +21,15 @@ class SpringInstrumentationContext( val instantiationSettings = InstantiationSettings( configurationClasses = arrayOf( - classLoader.loadClass(springConfig), + // TODO: for now we prohibit generating integration tests with XML configuration supplied, + // so we expect JavaConfigurations only. + // After fix rewrite the following. + classLoader.loadClass( + (springSettings.configuration as? JavaConfiguration)?.classBinaryName + ?: error("JavaConfiguration was expected, but ${springSettings.configuration.javaClass.name} was provided.") + ) ), - profileExpression = null, // TODO pass profile expression here + profiles = springSettings.profiles, ) SpringApiProviderFacade diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/rd/generated/InstrumentedProcessModel.Generated.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/rd/generated/InstrumentedProcessModel.Generated.kt index bd36a1903f..175bbff366 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/rd/generated/InstrumentedProcessModel.Generated.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/rd/generated/InstrumentedProcessModel.Generated.kt @@ -113,7 +113,7 @@ class InstrumentedProcessModel private constructor( val getSpringBean: RdCall get() = _getSpringBean /** - * Get Spring repositories by bean names (requires Spring instrumentation) + * Gets a list of [SpringRepositoryId]s that class specified by the [ClassId] (possibly indirectly) depends on (requires Spring instrumentation) */ val getRelevantSpringRepositories: RdCall get() = _getRelevantSpringRepositories //methods diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt index f35c1ae026..9399ae1279 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt @@ -71,6 +71,7 @@ import org.utbot.framework.codegen.domain.StaticImport import org.utbot.framework.codegen.tree.ututils.UtilClassKind import org.utbot.framework.codegen.tree.ututils.UtilClassKind.Companion.UT_UTILS_INSTANCE_NAME import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.CodeGenerationContext import org.utbot.framework.plugin.api.CodegenLanguage import org.utbot.intellij.plugin.inspection.UnitTestBotInspectionManager import org.utbot.intellij.plugin.models.GenerateTestsModel @@ -115,6 +116,7 @@ object CodeGenerationController { fun generateTests( model: GenerateTestsModel, + codeGenerationContext: CodeGenerationContext, classesWithTests: Map, psi2KClass: Map, process: EngineProcess, @@ -161,6 +163,7 @@ object CodeGenerationController { testFilePointer, srcClassPathToSarifReport, model, + codeGenerationContext, latch, utilClassListener, indicator @@ -664,6 +667,7 @@ object CodeGenerationController { filePointer: SmartPsiElementPointer, srcClassPathToSarifReport: MutableMap, model: GenerateTestsModel, + codeGenerationContext: CodeGenerationContext, reportsCountDown: CountDownLatch, utilClassListener: UtilClassListener, indicator: ProgressIndicator @@ -684,23 +688,13 @@ object CodeGenerationController { return@run } proc.render( - model.springTestsType, + model, testSetsId, classUnderTest, - model.projectType, paramNames.toMutableMap(), generateUtilClassFile = true, - model.testFramework, - model.mockFramework, - model.staticsMocking, - model.forceStaticMocking, - model.generateWarningsForStaticMocking, - model.codegenLanguage, - model.parametrizedTestSource, - model.runtimeExceptionTestsBehaviour, - model.hangingTestsTimeout, enableTestsTimeout = true, - testPackageName + testPackageName, ) } catch (e: Exception) { logger.warn(e) { "Cannot render test class ${testClass.name}" } 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 3f98dd22b9..14fbe938c6 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 @@ -49,20 +49,16 @@ import org.utbot.framework.CancellationStrategyType.NONE import org.utbot.framework.CancellationStrategyType.SAVE_PROCESSED_RESULTS import org.utbot.framework.UtSettings import org.utbot.framework.codegen.domain.ProjectType.* -import org.utbot.framework.plugin.api.TypeReplacementApproach.* -import org.utbot.framework.plugin.api.ApplicationContext -import org.utbot.framework.plugin.api.BeanDefinitionData -import org.utbot.framework.plugin.api.ClassId -import org.utbot.framework.plugin.api.JavaDocCommentStyle -import org.utbot.framework.plugin.api.SpringApplicationContext -import org.utbot.framework.plugin.api.SpringTestsType +import org.utbot.framework.plugin.api.* +import org.utbot.framework.plugin.api.SpringSettings.* +import org.utbot.framework.plugin.api.SpringConfiguration.* +import org.utbot.framework.plugin.api.SpringTestType.* import org.utbot.framework.plugin.api.util.LockFile import org.utbot.framework.plugin.api.util.withStaticsSubstitutionRequired import org.utbot.framework.plugin.services.JdkInfoService import org.utbot.framework.plugin.services.WorkingDirService import org.utbot.intellij.plugin.generator.CodeGenerationController.generateTests import org.utbot.intellij.plugin.models.GenerateTestsModel -import org.utbot.intellij.plugin.models.packageName import org.utbot.intellij.plugin.process.EngineProcess import org.utbot.intellij.plugin.process.RdTestGenerationResult import org.utbot.intellij.plugin.settings.Settings @@ -73,6 +69,7 @@ import org.utbot.intellij.plugin.ui.utils.testModules import org.utbot.intellij.plugin.util.IntelliJApiHelper import org.utbot.intellij.plugin.util.PsiClassHelper import org.utbot.intellij.plugin.util.isAbstract +import org.utbot.intellij.plugin.util.binaryName import org.utbot.intellij.plugin.util.PluginJdkInfoProvider import org.utbot.intellij.plugin.util.PluginWorkingDirProvider import org.utbot.intellij.plugin.util.assertIsNonDispatchThread @@ -174,13 +171,21 @@ object UtTestsDialogProcessor { } private fun createTests(project: Project, model: GenerateTestsModel) { - val springConfigClass = when (val approach = model.typeReplacementApproach) { - DoNotReplace -> null - is ReplaceIfPossible -> - approach.config.takeUnless { it.endsWith(".xml") }?.let { - PsiClassHelper.findClass(it, project) ?: error("Cannot find configuration class $it.") - } - } + val springConfigClass = + when (val settings = model.springSettings) { + is AbsentSpringSettings -> null + is PresentSpringSettings -> + when (val config = settings.configuration) { + is JavaConfiguration -> { + PsiClassHelper + .findClass(config.classBinaryName, project) + ?: error("Cannot find configuration class ${config.classBinaryName}.") + } + // TODO: for XML config we also need to compile module containing, + // since it may reference classes from that module + is XMLConfiguration -> null + } + } val filesToCompile = (model.srcClasses + listOfNotNull(springConfigClass)) .map { it.containingFile.virtualFile } @@ -236,7 +241,7 @@ object UtTestsDialogProcessor { val totalClasses = model.srcClasses.size val classNameToPath = runReadAction { model.srcClasses.associate { psiClass -> - psiClass.canonicalName to psiClass.containingFile.virtualFile.canonicalPath + psiClass.binaryName to psiClass.containingFile.virtualFile.canonicalPath } } @@ -251,27 +256,16 @@ object UtTestsDialogProcessor { val applicationContext = when (model.projectType) { Spring -> { val beanDefinitions = - when (val approach = model.typeReplacementApproach) { - DoNotReplace -> emptyList() - is ReplaceIfPossible -> { - val contentRoots = runReadAction { - listOfNotNull( - model.srcModule, - springConfigClass?.module - ).distinct().flatMap { module -> - ModuleRootManager.getInstance(module).contentRoots.toList() - } - } - - val fileStorage = contentRoots.map { root -> root.url }.toTypedArray() + when (val settings = model.springSettings) { + is AbsentSpringSettings -> emptyList() + is PresentSpringSettings -> { process.getSpringBeanDefinitions( classpathForClassLoader, - approach.config, - fileStorage, - model.profileNames, + settings ) } } + val shouldUseImplementors = beanDefinitions.isNotEmpty() val clarifiedBeanDefinitions = @@ -282,8 +276,8 @@ object UtTestsDialogProcessor { staticMockingConfigured, clarifiedBeanDefinitions, shouldUseImplementors, - model.typeReplacementApproach, - model.springTestsType + model.springTestType, + model.springSettings, ) } else -> ApplicationContext(mockFrameworkInstalled, staticMockingConfigured) @@ -310,12 +304,12 @@ object UtTestsDialogProcessor { } val (methods, classNameForLog) = process.executeWithTimeoutSuspended { - var canonicalName = "" + var binaryName = "" var srcMethods: List = emptyList() var srcNameForLog: String? = null DumbService.getInstance(project) .runReadActionInSmartMode(Computable { - canonicalName = srcClass.canonicalName + binaryName = srcClass.binaryName srcNameForLog = srcClass.name srcMethods = if (model.extractMembersFromSrcClasses) { val chosenMethods = @@ -329,7 +323,7 @@ object UtTestsDialogProcessor { srcClass.extractClassMethodsIncludingNested(false) } }) - val classId = process.obtainClassId(canonicalName) + val classId = process.obtainClassId(binaryName) psi2KClass[srcClass] = classId process.findMethodsInClassMatchingSelected( classId, @@ -376,20 +370,18 @@ object UtTestsDialogProcessor { }, 0, 500, TimeUnit.MILLISECONDS) try { val useEngine = when (model.projectType) { - Spring -> when (model.springTestsType) { - SpringTestsType.UNIT_TESTS -> true - SpringTestsType.INTEGRATION_TESTS -> false + Spring -> when (model.springTestType) { + UNIT_TEST -> true + INTEGRATION_TEST -> false } else -> true } val useFuzzing = when (model.projectType) { - Spring -> when (model.springTestsType) { - SpringTestsType.UNIT_TESTS -> when (model.typeReplacementApproach) { - DoNotReplace -> true - is ReplaceIfPossible -> false - } - SpringTestsType.INTEGRATION_TESTS -> true + Spring -> when (model.springTestType) { + UNIT_TEST -> model.springSettings is AbsentSpringSettings + INTEGRATION_TEST -> true } + else -> UtSettings.useFuzzing } val rdGenerateResult = process.generate( @@ -445,7 +437,7 @@ object UtTestsDialogProcessor { // indicator.checkCanceled() invokeLater { - generateTests(model, testSetsByClass, psi2KClass, process, indicator) + generateTests(model, applicationContext, testSetsByClass, psi2KClass, process, indicator) logger.info { "Generation complete" } } } @@ -465,26 +457,6 @@ object UtTestsDialogProcessor { return if (path != null && path.toFile().exists()) path else null } - private val PsiClass.canonicalName: String - /* - This method calculates exactly name that is used by compiler convention, - i.e. result is the exact name of .class file for provided PsiClass. - This value is used to provide classes to engine process - follow usages for clarification. - Equivalent for Class.getCanonicalName. - P.S. We cannot load project class in IDEA jvm - */ - get() { - return if (packageName.isEmpty()) { - qualifiedName?.replace(".", "$") ?: "" - } else { - val name = qualifiedName - ?.substringAfter("$packageName.") - ?.replace(".", "$") - ?: error("Unable to get canonical name for $this") - "$packageName.$name" - } - } - private fun clarifyBeanDefinitionReturnTypes(beanDefinitions: List, project: Project) = beanDefinitions.map { bean -> // Here we extract a real return type. @@ -497,10 +469,12 @@ object UtTestsDialogProcessor { val additionalData = bean.additionalData ?: return@runReadAction null val configPsiClass = - PsiClassHelper.findClass(additionalData.configClassFqn, project) ?: return@runReadAction null - .also { - logger.warn("Cannot find configuration class ${additionalData.configClassFqn}.") - } + PsiClassHelper + .findClass(additionalData.configClassName, project) + ?: return@runReadAction null + .also { + logger.warn("Cannot find configuration class ${additionalData.configClassName}.") + } val beanPsiMethod = configPsiClass @@ -520,7 +494,7 @@ object UtTestsDialogProcessor { .also { logger.warn( "Several similar methods named ${bean.beanName} " + - "were found in ${additionalData.configClassFqn} configuration class." + "were found in ${additionalData.configClassName} configuration class." ) } @@ -529,12 +503,12 @@ object UtTestsDialogProcessor { .findReturnStatements(beanPsiMethod) .mapNotNullTo(mutableSetOf()) { stmt -> stmt.returnValue?.type?.canonicalText } - beanTypes.singleOrNull() ?: bean.beanTypeFqn + beanTypes.singleOrNull() ?: bean.beanTypeName } ?: return@map bean BeanDefinitionData( beanName = bean.beanName, - beanTypeFqn = beanType, + beanTypeName = beanType, additionalData = bean.additionalData ) } diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/models/GenerateTestsModel.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/models/GenerateTestsModel.kt index 1bc2e481fc..b0114ca4b3 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/models/GenerateTestsModel.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/models/GenerateTestsModel.kt @@ -7,9 +7,6 @@ import org.utbot.framework.codegen.domain.ParametrizedTestSource import org.utbot.framework.codegen.domain.RuntimeExceptionTestsBehaviour import org.utbot.framework.codegen.domain.StaticsMocking import org.utbot.framework.codegen.domain.TestFramework -import org.utbot.framework.plugin.api.ClassId -import org.utbot.framework.plugin.api.MockFramework -import org.utbot.framework.plugin.api.MockStrategyApi import com.intellij.openapi.module.Module import com.intellij.openapi.project.Project import com.intellij.psi.PsiClass @@ -19,9 +16,12 @@ import org.jetbrains.concurrency.Promise import org.jetbrains.kotlin.psi.KtFile import org.utbot.framework.SummariesGenerationType import org.utbot.framework.UtSettings -import org.utbot.framework.plugin.api.TypeReplacementApproach +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.MockFramework +import org.utbot.framework.plugin.api.MockStrategyApi import org.utbot.framework.plugin.api.JavaDocCommentStyle -import org.utbot.framework.plugin.api.SpringTestsType +import org.utbot.framework.plugin.api.SpringTestType +import org.utbot.framework.plugin.api.SpringSettings import org.utbot.framework.util.ConflictTriggers import org.utbot.intellij.plugin.settings.Settings @@ -44,8 +44,6 @@ class GenerateTestsModel( override var sourceRootHistory = project.service().sourceRootHistory override var codegenLanguage = project.service().codegenLanguage - lateinit var springTestsType: SpringTestsType - lateinit var testFramework: TestFramework lateinit var mockStrategy: MockStrategyApi lateinit var mockFramework: MockFramework @@ -59,8 +57,8 @@ class GenerateTestsModel( lateinit var chosenClassesToMockAlways: Set lateinit var commentStyle: JavaDocCommentStyle - lateinit var typeReplacementApproach: TypeReplacementApproach - lateinit var profileNames: String + lateinit var springSettings: SpringSettings + lateinit var springTestType: SpringTestType val conflictTriggers: ConflictTriggers = ConflictTriggers() val preCompilePromises: MutableList> = mutableListOf() 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 2dbc7f9ef4..b6fdbf62a2 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 @@ -13,9 +13,9 @@ import com.jetbrains.rd.util.ConcurrentHashMap import com.jetbrains.rd.util.lifetime.LifetimeDefinition import kotlinx.coroutines.runBlocking import mu.KotlinLogging +import org.utbot.framework.plugin.api.SpringSettings.* import org.utbot.common.* 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 @@ -145,20 +145,28 @@ class EngineProcess private constructor(val project: Project, private val classN fun getSpringBeanDefinitions( classpathList: List, - config: String, - fileStorage: Array, - profileExpression: String?): List { + springSettings: PresentSpringSettings + ): List { assertReadAccessNotAllowed() val result = engineModel.getSpringBeanDefinitions.startBlocking( - GetSpringBeanDefinitions(classpathList.toTypedArray(), config, fileStorage, profileExpression) + GetSpringBeanDefinitions( + classpathList.toTypedArray(), + kryoHelper.writeObject(springSettings) + ) ) return result.beanDefinitions .map { data -> BeanDefinitionData( beanName = data.beanName, - beanTypeFqn = data.beanTypeFqn, + beanTypeName = data.beanTypeFqn, additionalData = data.additionalData - ?.let { BeanAdditionalData(it.factoryMethodName, it.parameterTypes, it.configClassFqn) } + ?.let { + BeanAdditionalData( + it.factoryMethodName, + it.parameterTypes, + it.configClassName + ) + } ) } } @@ -197,9 +205,9 @@ class EngineProcess private constructor(val project: Project, private val classN engineModel.createTestGenerator.startBlocking(params) } - fun obtainClassId(canonicalName: String): ClassId { + fun obtainClassId(binaryName: String): ClassId { assertReadAccessNotAllowed() - return kryoHelper.readObject(engineModel.obtainClassId.startBlocking(canonicalName)) + return kryoHelper.readObject(engineModel.obtainClassId.startBlocking(binaryName)) } fun findMethodsInClassMatchingSelected(clazzId: ClassId, srcMethods: List): List { @@ -274,55 +282,63 @@ class EngineProcess private constructor(val project: Project, private val classN } fun render( - springTestsType: SpringTestsType, + model: GenerateTestsModel, testSetsId: Long, classUnderTest: ClassId, - projectType: ProjectType, paramNames: MutableMap>, generateUtilClassFile: Boolean, - testFramework: TestFramework, - mockFramework: MockFramework, - staticsMocking: StaticsMocking, - forceStaticMocking: ForceStaticMocking, - generateWarningsForStaticsMocking: Boolean, - codegenLanguage: CodegenLanguage, - parameterizedTestSource: ParametrizedTestSource, - runtimeExceptionTestsBehaviour: RuntimeExceptionTestsBehaviour, - hangingTestSource: HangingTestsTimeout, enableTestsTimeout: Boolean, - testClassPackageName: String + testClassPackageName: String, ): Pair { assertReadAccessNotAllowed() - val params = RenderParams( - springTestsType.name, + val params = makeParams( + model, testSetsId, - kryoHelper.writeObject(classUnderTest), - projectType.toString(), - kryoHelper.writeObject(paramNames), + classUnderTest, + paramNames, generateUtilClassFile, - testFramework.id.lowercase(), - mockFramework.name, - codegenLanguage.name, - parameterizedTestSource.name, - staticsMocking.id, - kryoHelper.writeObject(forceStaticMocking), - generateWarningsForStaticsMocking, - runtimeExceptionTestsBehaviour.name, - hangingTestSource.timeoutMs, enableTestsTimeout, - testClassPackageName + testClassPackageName, ) val result = engineModel.render.startBlocking(params) val realUtilClassKind = result.utilClassKind?.let { - if (UtilClassKind.RegularUtUtils(codegenLanguage).javaClass.simpleName == it) - UtilClassKind.RegularUtUtils(codegenLanguage) + if (UtilClassKind.RegularUtUtils(model.codegenLanguage).javaClass.simpleName == it) + UtilClassKind.RegularUtUtils(model.codegenLanguage) else - UtilClassKind.UtUtilsWithMockito(codegenLanguage) + UtilClassKind.UtUtilsWithMockito(model.codegenLanguage) } return result.generatedCode to realUtilClassKind } + private fun makeParams( + model: GenerateTestsModel, + testSetsId: Long, + classUnderTest: ClassId, + paramNames: MutableMap>, + generateUtilClassFile: Boolean, + enableTestsTimeout: Boolean, + testClassPackageName: String, + ): RenderParams = + RenderParams( + testSetsId, + kryoHelper.writeObject(classUnderTest), + model.projectType.toString(), + kryoHelper.writeObject(paramNames), + generateUtilClassFile, + model.testFramework.id.lowercase(), + model.mockFramework.name, + model.codegenLanguage.name, + model.parametrizedTestSource.name, + model.staticsMocking.id, + kryoHelper.writeObject(model.forceStaticMocking), + model.generateWarningsForStaticMocking, + model.runtimeExceptionTestsBehaviour.name, + model.hangingTestsTimeout.timeoutMs, + enableTestsTimeout, + testClassPackageName, + ) + private fun getSourceFile(params: SourceStrategyMethodArgs): String? = DumbService.getInstance(project).runReadActionInSmartMode { sourceFindingStrategies[params.testSetId]!!.getSourceFile( diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/Settings.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/Settings.kt index d012b0d3bd..4de8b70089 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/Settings.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/Settings.kt @@ -24,7 +24,7 @@ private fun fromGenerateTestsModel(model: GenerateTestsModel): Settings.State { forceStaticMocking = model.forceStaticMocking, parametrizedTestSource = model.parametrizedTestSource, classesToMockAlways = model.chosenClassesToMockAlways.mapTo(mutableSetOf()) { it.name }.toTypedArray(), - springTestsType = model.springTestsType, + springTestType = model.springTestType, fuzzingValue = model.fuzzingValue, runGeneratedTestsWithCoverage = model.runGeneratedTestsWithCoverage, commentStyle = model.commentStyle, diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt index 69e70a1153..46ddb0920f 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt @@ -54,6 +54,7 @@ import com.intellij.ui.JBColor import com.intellij.ui.JBIntSpinner import com.intellij.ui.SideBorder import com.intellij.ui.SimpleTextAttributes +import org.utbot.framework.plugin.api.SpringSettings.* import com.intellij.ui.components.CheckBox import com.intellij.ui.components.JBLabel import com.intellij.ui.components.JBScrollPane @@ -90,15 +91,15 @@ import org.utbot.framework.codegen.domain.SpringBoot import org.utbot.framework.codegen.domain.StaticsMocking import org.utbot.framework.codegen.domain.TestFramework import org.utbot.framework.codegen.domain.TestNg -import org.utbot.framework.plugin.api.CodeGenerationSettingItem -import org.utbot.framework.plugin.api.CodegenLanguage -import org.utbot.framework.plugin.api.MockFramework -import org.utbot.framework.plugin.api.MockFramework.MOCKITO import org.utbot.framework.plugin.api.MockStrategyApi -import org.utbot.framework.plugin.api.SpringTestsType -import org.utbot.framework.plugin.api.SpringTestsType.* import org.utbot.framework.plugin.api.TreatOverflowAsError -import org.utbot.framework.plugin.api.TypeReplacementApproach +import org.utbot.framework.plugin.api.MockFramework.MOCKITO +import org.utbot.framework.plugin.api.SpringTestType +import org.utbot.framework.plugin.api.MockFramework +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.framework.plugin.api.CodeGenerationSettingItem +import org.utbot.framework.plugin.api.SpringConfiguration +import org.utbot.framework.plugin.api.SpringTestType.* import org.utbot.framework.plugin.api.utils.MOCKITO_EXTENSIONS_FILE_CONTENT import org.utbot.framework.plugin.api.utils.MOCKITO_EXTENSIONS_FOLDER import org.utbot.framework.plugin.api.utils.MOCKITO_MOCKMAKER_FILE_NAME @@ -132,16 +133,12 @@ import org.utbot.intellij.plugin.ui.utils.isBuildWithGradle import org.utbot.intellij.plugin.ui.utils.parseVersion import org.utbot.intellij.plugin.ui.utils.testResourceRootTypes import org.utbot.intellij.plugin.ui.utils.testRootType -import org.utbot.intellij.plugin.util.IntelliJApiHelper -import org.utbot.intellij.plugin.util.SpringConfigurationsHelper -import org.utbot.intellij.plugin.util.extractFirstLevelMembers -import org.utbot.intellij.plugin.util.findSdkVersion +import org.utbot.intellij.plugin.util.* import java.awt.BorderLayout import java.awt.Color import java.awt.Component import java.awt.Dimension import java.awt.event.ActionEvent -import java.io.File import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths @@ -160,7 +157,6 @@ import javax.swing.JList import javax.swing.JSpinner import javax.swing.text.DefaultFormatter import kotlin.io.path.notExists -import org.utbot.intellij.plugin.util.findSdkVersionOrNull private const val RECENTS_KEY = "org.utbot.recents" @@ -198,13 +194,13 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m private val codegenLanguages = createComboBox(CodegenLanguage.values()) private val testFrameworks = createComboBox(TestFramework.allItems.toTypedArray()) - private val javaConfigurationHelper = SpringConfigurationsHelper(".") - private val xmlConfigurationHelper = SpringConfigurationsHelper(File.separator) + private val javaConfigurationHelper = SpringConfigurationsHelper(SpringConfigurationType.ClassConfiguration) + private val xmlConfigurationHelper = SpringConfigurationsHelper(SpringConfigurationType.FileConfiguration) private val mockStrategies = createComboBox(MockStrategyApi.values()) private val staticsMocking = JCheckBox("Mock static methods") - private val springTestsType = createComboBox(SpringTestsType.values()) + private val springTestType = createComboBox(SpringTestType.values()) private val springConfig = createComboBoxWithSeparatorsForSpringConfigs(shortenConfigurationNames()) private val profileNames = JBTextField(23).apply { emptyText.text = DEFAULT_SPRING_PROFILE_NAME } @@ -374,7 +370,7 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m else -> null } installedDiFramework?.let { - INTEGRATION_TESTS.frameworkInstalled = findDependencyInjectionTestLibrary(model.testModule, it) != null + INTEGRATION_TEST.testFrameworkInstalled = findDependencyInjectionTestLibrary(model.testModule, it) != null } model.projectType = if (installedDiFramework != null) ProjectType.Spring else ProjectType.PureJvm @@ -422,7 +418,7 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m ) } row("Tests type:") { - cell(springTestsType) + cell(springTestType) contextHelp( "Unit tests do not initialize ApplicationContext
" + "and do not autowire beans, while integration tests do." @@ -699,23 +695,28 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m model.timeout = TimeUnit.SECONDS.toMillis(timeoutSpinner.number.toLong()) model.testSourceRoot?.apply { model.updateSourceRootHistory(this.toNioPath().toString()) } - model.typeReplacementApproach = + model.springSettings = when (springConfig.item) { - NO_SPRING_CONFIGURATION_OPTION -> TypeReplacementApproach.DoNotReplace + NO_SPRING_CONFIGURATION_OPTION -> AbsentSpringSettings() else -> { val shortConfigName = springConfig.item.toString() - //TODO: avoid this check on xml here, merge two helpers into one - val fullConfigName = if (isXmlSpringConfigUsed()) { - xmlConfigurationHelper.restoreFullName(shortConfigName) - } else { - javaConfigurationHelper.restoreFullName(shortConfigName) - } - - TypeReplacementApproach.ReplaceIfPossible(fullConfigName) + val config = + if (isXmlSpringConfigUsed()) { + val absolutePath = xmlConfigurationHelper.restoreFullName(shortConfigName) + SpringConfiguration.XMLConfiguration(absolutePath) + } else { + val classBinaryName = javaConfigurationHelper.restoreFullName(shortConfigName) + SpringConfiguration.JavaConfiguration(classBinaryName) + } + + PresentSpringSettings( + configuration = config, + profiles = parseProfileExpression(profileNames.text, DEFAULT_SPRING_PROFILE_NAME) + ) } } - model.profileNames = profileNames.text.let { it.ifEmpty { DEFAULT_SPRING_PROFILE_NAME } } - model.springTestsType = springTestsType.item + + model.springTestType = springTestType.item val settings = model.project.service() with(settings) { @@ -876,10 +877,10 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m updateParametrizationEnabled() } ProjectType.Spring -> { - springTestsType.item = - if (isSpringConfigSelected()) settings.springTestsType else SpringTestsType.defaultItem + springTestType.item = + if (isSpringConfigSelected()) settings.springTestType else SpringTestType.defaultItem updateSpringSettings() - updateTestFrameworkList(settings.springTestsType) + updateTestFrameworkList(settings.springTestType) } ProjectType.Python, ProjectType.JavaScript -> { } @@ -938,7 +939,7 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m } private fun configureSpringTestFrameworkIfRequired() { - if (springTestsType.item == INTEGRATION_TESTS) { + if (springTestType.item == INTEGRATION_TEST) { val framework = when { SpringBoot.isInstalled -> SpringBoot @@ -995,7 +996,7 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m model.preCompilePromises += addDependency(model.testModule, libraryDescriptor) } - INTEGRATION_TESTS.frameworkInstalled = true + INTEGRATION_TEST.testFrameworkInstalled = true } private fun configureMockFramework() { @@ -1142,10 +1143,10 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m springConfig.addActionListener { _ -> if (isSpringConfigSelected()) { if (isXmlSpringConfigUsed()) { - springTestsType.item = SpringTestsType.defaultItem + springTestType.item = SpringTestType.defaultItem } - if (springTestsType.item == UNIT_TESTS) { + if (springTestType.item == UNIT_TEST) { mockStrategies.item = MockStrategyApi.springDefaultItem } } else { @@ -1154,7 +1155,7 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m else -> MockStrategyApi.defaultItem } - springTestsType.item = SpringTestsType.defaultItem + springTestType.item = SpringTestType.defaultItem profileNames.text = "" } @@ -1162,17 +1163,17 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m updateControlsEnabledStatus() } - springTestsType.addActionListener { event -> + springTestType.addActionListener { event -> val comboBox = event.source as ComboBox<*> - val item = comboBox.item as SpringTestsType + val item = comboBox.item as SpringTestType updateTestFrameworkList(item) when (item) { - UNIT_TESTS -> { + UNIT_TEST -> { mockStrategies.item = MockStrategyApi.springDefaultItem } - INTEGRATION_TESTS -> { + INTEGRATION_TEST -> { mockStrategies.item = MockStrategyApi.springIntegrationTestItem } } @@ -1207,11 +1208,11 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m updateTestFrameworkList(enabledTestFrameworks, defaultItem) } - private fun updateTestFrameworkList(springTestsType: SpringTestsType) { + private fun updateTestFrameworkList(springTestType: SpringTestType) { // We do not support Spring integration tests for TestNg - val enabledTestFrameworks = when (springTestsType) { - UNIT_TESTS -> TestFramework.allItems - INTEGRATION_TESTS -> TestFramework.allItems.filterNot { it == TestNg } + val enabledTestFrameworks = when (springTestType) { + UNIT_TEST -> TestFramework.allItems + INTEGRATION_TEST -> TestFramework.allItems.filterNot { it == TestNg } } updateTestFrameworkList(enabledTestFrameworks) @@ -1274,13 +1275,13 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m // We check for > 1 because there is already extra-dummy NO_SPRING_CONFIGURATION_OPTION option springConfig.isEnabled = model.projectType == ProjectType.Spring && springConfig.itemCount > 1 - springTestsType.renderer = object : ColoredListCellRenderer() { + springTestType.renderer = object : ColoredListCellRenderer() { override fun customizeCellRenderer( - list: JList, value: SpringTestsType, + list: JList, value: SpringTestType, index: Int, selected: Boolean, hasFocus: Boolean ) { this.append(value.displayName, SimpleTextAttributes.REGULAR_ATTRIBUTES) - if (value == INTEGRATION_TESTS && !INTEGRATION_TESTS.frameworkInstalled) { + if (value == INTEGRATION_TEST && !INTEGRATION_TEST.testFrameworkInstalled) { val additionalText = when { SpringBoot.isInstalled -> " (spring-boot-test will be installed)" SpringBeans.isInstalled -> " (spring-test will be installed)" @@ -1344,10 +1345,10 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m if (isSpringConfigSelected()) { mockStrategies.isEnabled = false profileNames.isEnabled = true - springTestsType.isEnabled = !isXmlSpringConfigUsed() + springTestType.isEnabled = !isXmlSpringConfigUsed() } else { profileNames.isEnabled = false - springTestsType.isEnabled = false + springTestType.isEnabled = false } } } diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/SpringConfigurationsHelper.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/SpringConfigurationsHelper.kt index b5ed2ee1ad..1c21adfd16 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/SpringConfigurationsHelper.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/SpringConfigurationsHelper.kt @@ -1,5 +1,7 @@ package org.utbot.intellij.plugin.util +import java.io.File + /** * This class is a converter between full Spring configuration names and shortened versions. * @@ -13,7 +15,7 @@ package org.utbot.intellij.plugin.util * -> * [["web.WebConfig", "web2.WebConfig", "AnotherConfig"]] */ -class SpringConfigurationsHelper(val separator: String) { +class SpringConfigurationsHelper(val configType: SpringConfigurationType) { private val nameToInfo = mutableMapOf() @@ -21,7 +23,7 @@ class SpringConfigurationsHelper(val separator: String) { val shortenedName: String get() = innerShortName - private val pathFragments: MutableList = fullName.split(separator).toMutableList() + private val pathFragments: MutableList = fullName.split(*configType.separatorsToSplitBy).toMutableList() private var innerShortName = pathFragments.removeLast() fun enlargeShortName(): Boolean { @@ -30,7 +32,7 @@ class SpringConfigurationsHelper(val separator: String) { } val lastElement = pathFragments.removeLast() - innerShortName = "${lastElement}$separator$innerShortName" + innerShortName = "${lastElement}${configType.separatorToConcatenateBy}$innerShortName" return true } } @@ -40,7 +42,7 @@ class SpringConfigurationsHelper(val separator: String) { .values .singleOrNull { it.shortenedName == shortenedName } ?.fullName - ?: error("Full name of configuration file cannot be restored by shortened name $shortenedName") + ?: error("Full name of configuration file cannot be restored from shortened name $shortenedName") fun shortenSpringConfigNames(fullNames: Set): Map { fullNames.forEach { nameToInfo[it] = NameInfo(it) } @@ -51,7 +53,7 @@ class SpringConfigurationsHelper(val separator: String) { nameInfoCollection = nameInfoCollection.sortedBy { it.shortenedName }.toMutableList() var index = 0 - while(index < nameInfoCollection.size){ + while (index < nameInfoCollection.size) { val curShortenedPath = nameInfoCollection[index].shortenedName // here we search a block of shortened paths that are equivalent @@ -60,8 +62,7 @@ class SpringConfigurationsHelper(val separator: String) { while (maxIndexWithSamePath < nameInfoCollection.size) { if (nameInfoCollection[maxIndexWithSamePath].shortenedName == curShortenedPath) { maxIndexWithSamePath++ - } - else { + } else { break } } @@ -89,4 +90,37 @@ class SpringConfigurationsHelper(val separator: String) { private fun collectShortenedNames() = nameToInfo.values.associate { it.fullName to it.shortenedName } +} + +/* + * Transforms active profile information + * from the form of user input to a list of active profiles. + * + * NOTICE: Current user input form is comma-separated values, but it may be changed later. + */ +fun parseProfileExpression(profileExpression: String?, default: String): Array { + if (profileExpression.isNullOrEmpty()) { + return arrayOf(default) + } + + return profileExpression + .filter { !it.isWhitespace() } + .split(',') + .toTypedArray() +} + +@Deprecated("To be deleted") +enum class SpringConfigurationType( + val separatorsToSplitBy: Array, + val separatorToConcatenateBy: String, +) { + ClassConfiguration( + separatorsToSplitBy = arrayOf("."), + separatorToConcatenateBy = ".", + ), + + FileConfiguration( + separatorsToSplitBy = arrayOf(File.separator), + separatorToConcatenateBy = File.separator, + ), } \ No newline at end of file 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 8b6825d969..a4a51181fb 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 @@ -33,7 +33,6 @@ object EngineProcessModel : Ext(EngineProcessRoot) { field("path", PredefinedType.string) field("version", PredefinedType.int) } - val testGeneratorParams = structdef { field("buildDir", array(PredefinedType.string)) field("classpath", PredefinedType.string.nullable) @@ -62,7 +61,6 @@ object EngineProcessModel : Ext(EngineProcessRoot) { field("testSetsId", PredefinedType.long) } val renderParams = structdef { - field("springTestsType", PredefinedType.string) field("testSetsId", PredefinedType.long) field("classUnderTest", array(PredefinedType.byte)) field("projectType", PredefinedType.string) @@ -89,9 +87,7 @@ object EngineProcessModel : Ext(EngineProcessRoot) { } val getSpringBeanDefinitions = structdef { field("classpath", array(PredefinedType.string)) - field("config", PredefinedType.string) - field("fileStorage", array(PredefinedType.string)) - field("profileExpression", PredefinedType.string.nullable) + field("springSettings", array(PredefinedType.byte)) } val methodDescription = structdef { field("name", PredefinedType.string) @@ -134,7 +130,7 @@ object EngineProcessModel : Ext(EngineProcessRoot) { val beanAdditionalData = structdef { field("factoryMethodName", PredefinedType.string) field("parameterTypes", immutableList(PredefinedType.string)) - field("configClassFqn", PredefinedType.string) + field("configClassName", PredefinedType.string) } val beanDefinitionData = structdef { field("beanName", PredefinedType.string) 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 02bdc5321e..e5474cae67 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 @@ -7,9 +7,7 @@ object SpringAnalyzerRoot : Root() object SpringAnalyzerProcessModel : Ext(SpringAnalyzerRoot) { val springAnalyzerParams = structdef { - field("configuration", PredefinedType.string) - field("fileStorage", array(PredefinedType.string)) - field("profileExpression", PredefinedType.string.nullable) + field("springSettings", array(PredefinedType.byte)) } val beanAdditionalData = structdef { diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzer/SpringApplicationAnalyzer.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzer/SpringApplicationAnalyzer.kt index 74b1ea5c66..e1a07a7c57 100644 --- a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzer/SpringApplicationAnalyzer.kt +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzer/SpringApplicationAnalyzer.kt @@ -10,10 +10,11 @@ import org.utbot.spring.utils.SourceFinder class SpringApplicationAnalyzer { fun getBeanDefinitions(applicationData: ApplicationData): Array { + // TODO: get rid of SourceFinder val configurationClasses = SourceFinder(applicationData).findSources() val instantiationSettings = InstantiationSettings( configurationClasses, - applicationData.profileExpression, + applicationData.springSettings.profiles, ) return SpringApiProviderFacade.getInstance(this::class.java.classLoader) diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/api/ApplicationData.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/api/ApplicationData.kt index a2423019b2..b776eb9c76 100644 --- a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/api/ApplicationData.kt +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/api/ApplicationData.kt @@ -1,9 +1,7 @@ package org.utbot.spring.api -import java.net.URL +import org.utbot.framework.plugin.api.SpringSettings.* class ApplicationData( - val configurationFile: String, - val fileStorage: List, - val profileExpression: String?, + val springSettings: PresentSpringSettings ) 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 0688b8047b..066048b61e 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 @@ -50,7 +50,7 @@ class SpringAnalyzerProcessModel private constructor( } - const val serializationHash = 8934866731594302609L + const val serializationHash = 8094902537230457267L } override val serializersOwner: ISerializersOwner get() = SpringAnalyzerProcessModel @@ -97,7 +97,7 @@ val IProtocol.springAnalyzerProcessModel get() = getOrCreateExtension(SpringAnal /** - * #### Generated from [SpringAnalyzerModel.kt:15] + * #### Generated from [SpringAnalyzerModel.kt:13] */ data class BeanAdditionalData ( val factoryMethodName: String, @@ -166,7 +166,7 @@ data class BeanAdditionalData ( /** - * #### Generated from [SpringAnalyzerModel.kt:21] + * #### Generated from [SpringAnalyzerModel.kt:19] */ data class BeanDefinitionData ( val beanName: String, @@ -238,9 +238,7 @@ data class BeanDefinitionData ( * #### Generated from [SpringAnalyzerModel.kt:9] */ data class SpringAnalyzerParams ( - val configuration: String, - val fileStorage: Array, - val profileExpression: String? + val springSettings: ByteArray ) : IPrintable { //companion @@ -249,16 +247,12 @@ data class SpringAnalyzerParams ( @Suppress("UNCHECKED_CAST") override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): SpringAnalyzerParams { - val configuration = buffer.readString() - val fileStorage = buffer.readArray {buffer.readString()} - val profileExpression = buffer.readNullable { buffer.readString() } - return SpringAnalyzerParams(configuration, fileStorage, profileExpression) + val springSettings = buffer.readByteArray() + return SpringAnalyzerParams(springSettings) } override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: SpringAnalyzerParams) { - buffer.writeString(value.configuration) - buffer.writeArray(value.fileStorage) { buffer.writeString(it) } - buffer.writeNullable(value.profileExpression) { buffer.writeString(it) } + buffer.writeByteArray(value.springSettings) } @@ -274,27 +268,21 @@ data class SpringAnalyzerParams ( other as SpringAnalyzerParams - if (configuration != other.configuration) return false - if (!(fileStorage contentDeepEquals other.fileStorage)) return false - if (profileExpression != other.profileExpression) return false + if (!(springSettings contentEquals other.springSettings)) return false return true } //hash code trait override fun hashCode(): Int { var __r = 0 - __r = __r*31 + configuration.hashCode() - __r = __r*31 + fileStorage.contentDeepHashCode() - __r = __r*31 + if (profileExpression != null) profileExpression.hashCode() else 0 + __r = __r*31 + springSettings.contentHashCode() return __r } //pretty print override fun print(printer: PrettyPrinter) { printer.println("SpringAnalyzerParams (") printer.indent { - print("configuration = "); configuration.print(printer); println() - print("fileStorage = "); fileStorage.print(printer); println() - print("profileExpression = "); profileExpression.print(printer); println() + print("springSettings = "); springSettings.print(printer); println() } printer.print(")") } @@ -304,7 +292,7 @@ data class SpringAnalyzerParams ( /** - * #### Generated from [SpringAnalyzerModel.kt:27] + * #### Generated from [SpringAnalyzerModel.kt:25] */ data class SpringAnalyzerResult ( val beanDefinitions: Array 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 7c5bc6ff03..044ef0c4a9 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 @@ -22,6 +22,8 @@ import org.utbot.spring.generated.SpringAnalyzerProcessModel import org.utbot.spring.generated.SpringAnalyzerResult import org.utbot.spring.generated.springAnalyzerProcessModel import java.io.File +import org.utbot.framework.plugin.api.SpringSettings.* +import org.utbot.framework.process.kryo.KryoHelper class SpringAnalyzerProcessInstantDeathException : InstantProcessDeathException( @@ -86,13 +88,12 @@ class SpringAnalyzerProcess private constructor( private val springAnalyzerModel: SpringAnalyzerProcessModel = onSchedulerBlocking { protocol.springAnalyzerProcessModel } private val loggerModel: LoggerModel = onSchedulerBlocking { protocol.loggerModel } + private val kryoHelper = KryoHelper(lifetime) fun getBeanDefinitions( - configuration: String, - fileStorage: Array, - profileExpression: String?, + springSettings: PresentSpringSettings ): SpringAnalyzerResult { - val params = SpringAnalyzerParams(configuration, fileStorage, profileExpression) + val params = SpringAnalyzerParams(kryoHelper.writeObject(springSettings)) 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 2475780f42..8a1d72f8d2 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 @@ -7,6 +7,7 @@ import com.jetbrains.rd.util.info import com.jetbrains.rd.util.lifetime.Lifetime import com.jetbrains.rd.util.reactive.adviseOnce import org.utbot.common.AbstractSettings +import org.utbot.framework.process.kryo.KryoHelper import org.utbot.rd.ClientProtocolBuilder import org.utbot.rd.IdleWatchdog import org.utbot.rd.RdSettingsContainerFactory @@ -40,11 +41,11 @@ suspend fun main(args: Array) = } private fun SpringAnalyzerProcessModel.setup(watchdog: IdleWatchdog, realProtocol: IProtocol) { + val kryoHelper = KryoHelper(realProtocol.lifetime) + watchdog.measureTimeForActiveCall(analyze, "Analyzing Spring Application") { params -> val applicationData = ApplicationData( - params.configuration, - params.fileStorage.map { File(it).toURI().toURL() }, - params.profileExpression, + kryoHelper.readObject(params.springSettings) ) SpringAnalyzerResult( diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/utils/SourceFinder.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/utils/SourceFinder.kt index a9601e6d6b..691ce2f40c 100644 --- a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/utils/SourceFinder.kt +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/utils/SourceFinder.kt @@ -4,10 +4,10 @@ import com.jetbrains.rd.util.getLogger import com.jetbrains.rd.util.info import org.springframework.context.annotation.ImportResource import org.utbot.common.patchAnnotation +import org.utbot.framework.plugin.api.SpringConfiguration.* import org.utbot.spring.api.ApplicationData import org.utbot.spring.config.TestApplicationConfiguration import org.utbot.spring.configurators.ApplicationConfigurationType -import java.io.File import java.nio.file.Path import kotlin.io.path.Path @@ -18,29 +18,24 @@ class SourceFinder( ) { private val classLoader: ClassLoader = this::class.java.classLoader - fun findSources(): Array> = when (configurationType) { - ApplicationConfigurationType.XmlConfiguration -> { - logger.info { "Using xml Spring configuration" } + fun findSources(): Array> = + when (val config = applicationData.springSettings.configuration) { + is JavaConfiguration -> { + logger.info { "Using java Spring configuration" } + arrayOf( + TestApplicationConfiguration::class.java, + classLoader.loadClass(config.classBinaryName) + ) + } - // Put `applicationData.configurationFile` in `@ImportResource` of `TestApplicationConfiguration` - patchImportResourceAnnotation(Path(applicationData.configurationFile).fileName) + is XMLConfiguration -> { + logger.info { "Using xml Spring configuration" } - arrayOf(TestApplicationConfiguration::class.java) - } - - ApplicationConfigurationType.JavaConfiguration -> { - logger.info { "Using java Spring configuration" } - arrayOf( - TestApplicationConfiguration::class.java, - classLoader.loadClass(applicationData.configurationFile) - ) - } - } + // Put `applicationData.configurationFile` in `@ImportResource` of `TestApplicationConfiguration` + patchImportResourceAnnotation(Path(config.absolutePath).fileName) - private val configurationType: ApplicationConfigurationType - get() = when (File(applicationData.configurationFile).extension) { - "xml" -> ApplicationConfigurationType.XmlConfiguration - else -> ApplicationConfigurationType.JavaConfiguration + arrayOf(TestApplicationConfiguration::class.java) + } } private fun patchImportResourceAnnotation(userXmlFilePath: Path) = diff --git a/utbot-spring-commons-api/src/main/kotlin/org/utbot/spring/api/instantiator/InstantiationSettings.kt b/utbot-spring-commons-api/src/main/kotlin/org/utbot/spring/api/instantiator/InstantiationSettings.kt index db6eafe115..3a783a1b7b 100644 --- a/utbot-spring-commons-api/src/main/kotlin/org/utbot/spring/api/instantiator/InstantiationSettings.kt +++ b/utbot-spring-commons-api/src/main/kotlin/org/utbot/spring/api/instantiator/InstantiationSettings.kt @@ -2,5 +2,5 @@ package org.utbot.spring.api.instantiator class InstantiationSettings( val configurationClasses: Array>, - val profileExpression: String?, + val profiles: Array, ) \ No newline at end of file diff --git a/utbot-spring-commons/src/main/kotlin/org/utbot/spring/SpringApiImpl.kt b/utbot-spring-commons/src/main/kotlin/org/utbot/spring/SpringApiImpl.kt index 58773e5e76..65907f737a 100644 --- a/utbot-spring-commons/src/main/kotlin/org/utbot/spring/SpringApiImpl.kt +++ b/utbot-spring-commons/src/main/kotlin/org/utbot/spring/SpringApiImpl.kt @@ -30,7 +30,7 @@ class SpringApiImpl( patchAnnotation( annotation = it.getAnnotation(ActiveProfiles::class.java), property = "value", - newValue = parseProfileExpression(instantiationSettings.profileExpression) + newValue = instantiationSettings.profiles ) patchAnnotation( annotation = it.getAnnotation(ContextConfiguration::class.java), @@ -153,23 +153,6 @@ class SpringApiImpl( false } - companion object { - private const val DEFAULT_PROFILE_NAME = "default" - - /** - * Transforms active profile information - * from the form of user input to a list of active profiles. - * - * Current user input form is comma-separated values, but it may be changed later. - */ - private fun parseProfileExpression(profileExpression: String?): Array = - if (profileExpression.isNullOrEmpty()) arrayOf(DEFAULT_PROFILE_NAME) - else profileExpression - .filter { !it.isWhitespace() } - .split(',') - .toTypedArray() - } - data class SimpleBeanDefinition( val beanName: String, val bean: Any, diff --git a/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/models/BaseTestModel.kt b/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/models/BaseTestModel.kt index fa9aa46931..321a2af4e9 100644 --- a/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/models/BaseTestModel.kt +++ b/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/models/BaseTestModel.kt @@ -1,10 +1,8 @@ package org.utbot.intellij.plugin.models import com.intellij.openapi.module.Module -import com.intellij.openapi.module.ModuleManager import com.intellij.openapi.module.ModuleUtil import com.intellij.openapi.project.Project -import com.intellij.openapi.roots.TestModuleProperties import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.newvfs.impl.FakeVirtualFile import com.intellij.psi.JavaPsiFacade @@ -23,12 +21,10 @@ import org.utbot.intellij.plugin.ui.utils.getResourcesPaths import org.utbot.intellij.plugin.ui.utils.getSortedTestRoots import org.utbot.intellij.plugin.ui.utils.isBuildWithGradle import org.utbot.intellij.plugin.ui.utils.suitableTestSourceRoots -import java.io.IOException -import java.io.StringReader +import org.utbot.intellij.plugin.util.binaryName import java.nio.file.Files import javax.xml.parsers.DocumentBuilder import javax.xml.parsers.DocumentBuilderFactory -import javax.xml.parsers.ParserConfigurationException import kotlin.streams.asSequence @@ -37,8 +33,8 @@ const val HISTORY_LIMIT = 10 const val SPRINGBOOT_APPLICATION_FQN = "org.springframework.boot.autoconfigure.SpringBootApplication" const val SPRINGBOOT_CONFIGURATION_FQN = "org.springframework.boot.SpringBootConfiguration" -const val SPRING_CONFIGURATION_ANNOTATION = "org.springframework.context.annotation.Configuration" -const val SPRING_TESTCONFIGURATION_ANNOTATION = "org.springframework.boot.test.context.TestConfiguration" +const val SPRING_CONFIGURATION_ANNOTATION_FQN = "org.springframework.context.annotation.Configuration" +const val SPRING_TESTCONFIGURATION_ANNOTATION_FQN = "org.springframework.boot.test.context.TestConfiguration" const val SPRING_BEANS_SCHEMA_URL = "http://www.springframework.org/schema/beans" const val SPRING_LOAD_DTD_GRAMMAR_PROPERTY = "http://apache.org/xml/features/nonvalidating/load-dtd-grammar" @@ -98,7 +94,8 @@ open class BaseTestsModel( * @see [getSortedAnnotatedClasses] */ fun getSortedSpringBootApplicationClasses(): Set = - getSortedAnnotatedClasses(SPRINGBOOT_CONFIGURATION_FQN) + getSortedAnnotatedClasses(SPRINGBOOT_APPLICATION_FQN) + getSortedAnnotatedClasses(SPRINGBOOT_CONFIGURATION_FQN) + + getSortedAnnotatedClasses(SPRINGBOOT_APPLICATION_FQN) /** * Finds @TestConfiguration and @Configuration classes in Spring application. @@ -106,7 +103,8 @@ open class BaseTestsModel( * @see [getSortedAnnotatedClasses] */ fun getSortedSpringConfigurationClasses(): Set = - getSortedAnnotatedClasses(SPRING_TESTCONFIGURATION_ANNOTATION) + getSortedAnnotatedClasses(SPRING_CONFIGURATION_ANNOTATION) + getSortedAnnotatedClasses(SPRING_TESTCONFIGURATION_ANNOTATION_FQN) + + getSortedAnnotatedClasses(SPRING_CONFIGURATION_ANNOTATION_FQN) /** * Finds classes annotated with given annotation in [srcModule] and [potentialTestModules]. @@ -116,21 +114,27 @@ open class BaseTestsModel( * - classes from production source roots */ private fun getSortedAnnotatedClasses(annotationFqn: String): Set { - val searchScope = potentialTestModules.fold(GlobalSearchScope.moduleScope(srcModule)) { accScope, module -> - accScope.union(GlobalSearchScope.moduleScope(module)) - } + val searchScope = + potentialTestModules + .fold(GlobalSearchScope.moduleScope(srcModule)) { accScope, module -> + accScope.union(GlobalSearchScope.moduleScope(module)) + } val annotationClass = JavaPsiFacade .getInstance(project) - .findClass(annotationFqn, GlobalSearchScope.allScope(project)) ?: return emptySet() + .findClass(annotationFqn, GlobalSearchScope.allScope(project)) + ?: return emptySet() - val testRootToIndex = getSortedTestRoots().withIndex().associate { (i, root) -> root.dir to i } + val testRootToIndex = + getSortedTestRoots() + .withIndex() + .associate { (i, root) -> root.dir to i } return AnnotatedElementsSearch .searchPsiClasses(annotationClass, searchScope) .findAll() .sortedBy { testRootToIndex[it.containingFile.sourceRoot] ?: Int.MAX_VALUE } - .mapNotNullTo(mutableSetOf()) { it.qualifiedName } + .mapNotNullTo(mutableSetOf()) { it.binaryName } } fun getSpringXMLConfigurationFiles(): Set { diff --git a/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/settings/CommonSettings.kt b/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/settings/CommonSettings.kt index 3f34246e8e..4f8d9573bc 100644 --- a/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/settings/CommonSettings.kt +++ b/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/settings/CommonSettings.kt @@ -37,7 +37,7 @@ import kotlin.reflect.KClass import org.utbot.common.isWindows import org.utbot.framework.SummariesGenerationType import org.utbot.framework.codegen.domain.UnknownTestFramework -import org.utbot.framework.plugin.api.SpringTestsType +import org.utbot.framework.plugin.api.SpringTestType import org.utbot.framework.plugin.api.isSummarizationCompatible @State( @@ -63,7 +63,7 @@ class Settings(val project: Project) : PersistentStateComponent var treatOverflowAsError: TreatOverflowAsError = TreatOverflowAsError.defaultItem, var parametrizedTestSource: ParametrizedTestSource = ParametrizedTestSource.defaultItem, var classesToMockAlways: Array = Mocker.defaultSuperClassesToMockAlwaysNames.toTypedArray(), - var springTestsType: SpringTestsType = SpringTestsType.defaultItem, + var springTestType: SpringTestType = SpringTestType.defaultItem, var fuzzingValue: Double = 0.05, var runGeneratedTestsWithCoverage: Boolean = false, var commentStyle: JavaDocCommentStyle = JavaDocCommentStyle.defaultItem, @@ -92,7 +92,7 @@ class Settings(val project: Project) : PersistentStateComponent if (treatOverflowAsError != other.treatOverflowAsError) return false if (parametrizedTestSource != other.parametrizedTestSource) return false if (!classesToMockAlways.contentEquals(other.classesToMockAlways)) return false - if (springTestsType != other.springTestsType) return false + if (springTestType != other.springTestType) return false if (fuzzingValue != other.fuzzingValue) return false if (runGeneratedTestsWithCoverage != other.runGeneratedTestsWithCoverage) return false if (commentStyle != other.commentStyle) return false @@ -116,7 +116,7 @@ class Settings(val project: Project) : PersistentStateComponent result = 31 * result + treatOverflowAsError.hashCode() result = 31 * result + parametrizedTestSource.hashCode() result = 31 * result + classesToMockAlways.contentHashCode() - result = 31 * result + springTestsType.hashCode() + result = 31 * result + springTestType.hashCode() result = 31 * result + fuzzingValue.hashCode() result = 31 * result + if (runGeneratedTestsWithCoverage) 1 else 0 result = 31 * result + summariesGenerationType.hashCode() @@ -165,7 +165,7 @@ class Settings(val project: Project) : PersistentStateComponent val classesToMockAlways: Set get() = state.classesToMockAlways.toSet() - val springTestsType: SpringTestsType get() = state.springTestsType + val springTestType: SpringTestType get() = state.springTestType val javaDocCommentStyle: JavaDocCommentStyle get() = state.commentStyle diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/PsiClassHelper.kt b/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/util/PsiClassHelper.kt similarity index 77% rename from utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/PsiClassHelper.kt rename to utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/util/PsiClassHelper.kt index 83ad3a1a5f..26cd770797 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/PsiClassHelper.kt +++ b/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/util/PsiClassHelper.kt @@ -17,6 +17,26 @@ import org.jetbrains.kotlin.asJava.elements.isSetter import org.jetbrains.kotlin.psi.KtClass import org.utbot.common.filterWhen import org.utbot.framework.UtSettings +import org.utbot.intellij.plugin.models.packageName + +/** + * Used to build binary name from canonical name + * in a similar form which could be obtained by [java.lang.Class.getName] method. + * + * E.g. ```org.example.OuterClass.InnerClass.InnerInnerClass``` -> ```org.example.OuterClass$InnerClass$InnerInnerClass``` + */ +val PsiClass.binaryName: String + get() = + if (packageName.isEmpty()) { + qualifiedName?.replace(".", "$") ?: "" + } else { + val name = + qualifiedName + ?.substringAfter("$packageName.") + ?.replace(".", "$") + ?: error("Binary name construction failed: unable to get qualified name of $this") + "$packageName.$name" + } val PsiMember.isAbstract: Boolean get() = modifierList?.hasModifierProperty(PsiModifier.ABSTRACT)?: false @@ -89,8 +109,16 @@ val PsiClass.isVisible: Boolean get() = generateSequence(this) { it.containingClass }.none { it.isPrivateOrProtected } object PsiClassHelper { - fun findClass(name: String, project: Project): PsiClass? = - JavaPsiFacade + /** + * Finds [PsiClass]. + * + * @param name binary name which is converted to canonical name. + */ + fun findClass(name: String, project: Project): PsiClass? { + // Converting name to canonical name + val canonicalName = name.replace("$", ".") + return JavaPsiFacade .getInstance(project) - .findClass(name, GlobalSearchScope.projectScope(project)) + .findClass(canonicalName, GlobalSearchScope.projectScope(project)) + } } \ No newline at end of file