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 1813769347..cc305f1b02 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 @@ -55,6 +55,7 @@ import java.io.File import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract import org.utbot.common.isAbstract +import org.utbot.framework.plugin.api.util.utContext const val SYMBOLIC_NULL_ADDR: Int = 0 @@ -1147,6 +1148,25 @@ open class TypeParameters(val parameters: List = emptyList()) class WildcardTypeParameter : TypeParameters(emptyList()) +/** + * Additional data describing user project. + */ +interface ApplicationContext + +/** + * Data we get from Spring application context + * to manage engine and code generator behaviour. + * + * @param beanQualifiedNames describes fqn of injected classes + */ +data class SpringApplicationContext( + val beanQualifiedNames: List = emptyList(), +): ApplicationContext { + private val springInjectedClasses: List by lazy { + beanQualifiedNames.map { fqn -> utContext.classLoader.loadClass(fqn).id } + } +} + interface CodeGenerationSettingItem { val id: String val displayName: String diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt index 681d0dd904..db74e9d5f8 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt @@ -116,10 +116,12 @@ import org.utbot.framework.UtSettings import org.utbot.framework.UtSettings.maximizeCoverageUsingReflection import org.utbot.framework.UtSettings.preferredCexOption import org.utbot.framework.UtSettings.substituteStaticsWithSymbolicVariable +import org.utbot.framework.plugin.api.ApplicationContext import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.ExecutableId import org.utbot.framework.plugin.api.FieldId import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.SpringApplicationContext import org.utbot.framework.plugin.api.classId import org.utbot.framework.plugin.api.id import org.utbot.framework.plugin.api.util.executable @@ -237,6 +239,7 @@ class Traverser( internal val typeResolver: TypeResolver, private val globalGraph: InterProceduralUnitGraph, private val mocker: Mocker, + private val applicationContext: ApplicationContext?, ) : UtContextInitializer() { private val visitedStmts: MutableSet = mutableSetOf() @@ -273,6 +276,8 @@ class Traverser( internal val objectCounter = ObjectCounter(TypeRegistry.objectCounterInitialValue) + + private fun findNewAddr(insideStaticInitializer: Boolean): UtAddrExpression { val newAddr = objectCounter.createNewAddr() // return negative address for objects created inside static initializer 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 ef6202d621..083efdde51 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt @@ -105,6 +105,7 @@ class UtBotSymbolicEngine( dependencyPaths: String, val mockStrategy: MockStrategy = NO_MOCKS, chosenClassesToMockAlways: Set, + applicationContext: ApplicationContext?, private val solverTimeoutInMillis: Int = checkSolverTimeoutMillis ) : UtContextInitializer() { private val graph = methodUnderTest.sootMethod.jimpleBody().apply { @@ -144,6 +145,7 @@ class UtBotSymbolicEngine( typeResolver, globalGraph, mocker, + applicationContext, ) //HACK (long strings) 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 454c3e5865..3977e178c7 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 @@ -61,6 +61,7 @@ open class TestCaseGenerator( val engineActions: MutableList<(UtBotSymbolicEngine) -> Unit> = mutableListOf(), val isCanceled: () -> Boolean = { false }, val forceSootReload: Boolean = true, + val applicationContext: ApplicationContext? = null, ) { private val logger: KLogger = KotlinLogging.logger {} private val timeoutLogger: KLogger = KotlinLogging.logger(logger.name + ".timeout") @@ -112,7 +113,14 @@ open class TestCaseGenerator( executionTimeEstimator: ExecutionTimeEstimator = ExecutionTimeEstimator(utBotGenerationTimeoutInMillis, 1) ): Flow { try { - val engine = createSymbolicEngine(controller, method, mockStrategy, chosenClassesToMockAlways, executionTimeEstimator) + val engine = createSymbolicEngine( + controller, + method, + mockStrategy, + chosenClassesToMockAlways, + applicationContext = null, + executionTimeEstimator, + ) engineActions.map { engine.apply(it) } engineActions.clear() return defaultTestFlow(engine, executionTimeEstimator.userTimeout) @@ -159,6 +167,7 @@ open class TestCaseGenerator( method, mockStrategy, chosenClassesToMockAlways, + applicationContext, executionTimeEstimator ) @@ -248,6 +257,7 @@ open class TestCaseGenerator( method: ExecutableId, mockStrategyApi: MockStrategyApi, chosenClassesToMockAlways: Set, + applicationContext: ApplicationContext?, executionTimeEstimator: ExecutionTimeEstimator ): UtBotSymbolicEngine { logger.debug("Starting symbolic execution for $method --$mockStrategyApi--") @@ -258,6 +268,7 @@ open class TestCaseGenerator( dependencyPaths = dependencyPaths, mockStrategy = mockStrategyApi.toModel(), chosenClassesToMockAlways = chosenClassesToMockAlways, + applicationContext = applicationContext, solverTimeoutInMillis = executionTimeEstimator.updatedSolverCheckTimeoutMillis ) } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/TestFlow.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/TestFlow.kt index 2407407acd..72c76905bb 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/TestFlow.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/TestFlow.kt @@ -24,6 +24,16 @@ fun defaultTestFlow(timeout: Long) = testFlow { } } +/** + * Creates default flow for Spring application. + */ +fun defaultSpringFlow(timeout: Long) = testFlow { + isSymbolicEngineEnabled = true + generationTimeout = timeout + isFuzzingEnabled = false + fuzzingValue = 0.0 +} + /** * Creates default flow that uses [UtSettings] for customization. */ 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 49fc11bab7..f4eb9c99f1 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,10 +83,13 @@ private fun EngineProcessModel.setup(kryoHelper: KryoHelper, watchdog: IdleWatch watchdog.measureTimeForActiveCall(createTestGenerator, "Creating Test Generator") { params -> AnalyticsConfigureUtil.configureML() Instrumenter.adapter = RdInstrumenter(realProtocol.rdInstrumenterAdapter) + val applicationContext: ApplicationContext = kryoHelper.readObject(params.applicationContext) + testGenerator = TestCaseGenerator(buildDirs = params.buildDir.map { Paths.get(it) }, classpath = params.classpath, dependencyPaths = params.dependencyPaths, jdkInfo = JdkInfo(Paths.get(params.jdkInfo.path), params.jdkInfo.version), + applicationContext = applicationContext, isCanceled = { runBlocking { model.isCancelled.startSuspending(Unit) @@ -105,16 +108,24 @@ private fun EngineProcessModel.setup(kryoHelper: KryoHelper, watchdog: IdleWatch if (!staticsMockingConfigured) { ForceStaticMockListener.create(testGenerator, conflictTriggers, cancelJob = true) } - val result = testGenerator.generate(methods, - MockStrategyApi.valueOf(params.mockStrategy), - kryoHelper.readObject(params.chosenClassesToMockAlways), - params.timeout, - generate = testFlow { + + val generateFlow = when (testGenerator.applicationContext) { + is SpringApplicationContext -> defaultSpringFlow(params.generationTimeout) + else -> testFlow { generationTimeout = params.generationTimeout isSymbolicEngineEnabled = params.isSymbolicEngineEnabled isFuzzingEnabled = params.isFuzzingEnabled fuzzingValue = params.fuzzingValue - }) + } + } + + val result = testGenerator.generate( + methods, + MockStrategyApi.valueOf(params.mockStrategy), + kryoHelper.readObject(params.chosenClassesToMockAlways), + params.timeout, + generate = generateFlow, + ) .summarizeAll(Paths.get(params.searchDirectory), null) .filterNot { it.executions.isEmpty() && it.errors.isEmpty() } 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 76ac332ac6..9a136e45bd 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 @@ -72,7 +72,7 @@ class EngineProcessModel private constructor( } - const val serializationHash = 5025678608993948804L + const val serializationHash = -4839464828913070560L } override val serializersOwner: ISerializersOwner get() = EngineProcessModel @@ -173,7 +173,7 @@ val IProtocol.engineProcessModel get() = getOrCreateExtension(EngineProcessModel /** - * #### Generated from [EngineProcessModel.kt:101] + * #### Generated from [EngineProcessModel.kt:102] */ data class FindMethodParamNamesArguments ( val classId: ByteArray, @@ -236,7 +236,7 @@ data class FindMethodParamNamesArguments ( /** - * #### Generated from [EngineProcessModel.kt:105] + * #### Generated from [EngineProcessModel.kt:106] */ data class FindMethodParamNamesResult ( val paramNames: ByteArray @@ -293,7 +293,7 @@ data class FindMethodParamNamesResult ( /** - * #### Generated from [EngineProcessModel.kt:94] + * #### Generated from [EngineProcessModel.kt:95] */ data class FindMethodsInClassMatchingSelectedArguments ( val classId: ByteArray, @@ -356,7 +356,7 @@ data class FindMethodsInClassMatchingSelectedArguments ( /** - * #### Generated from [EngineProcessModel.kt:98] + * #### Generated from [EngineProcessModel.kt:99] */ data class FindMethodsInClassMatchingSelectedResult ( val executableIds: ByteArray @@ -413,7 +413,7 @@ data class FindMethodsInClassMatchingSelectedResult ( /** - * #### Generated from [EngineProcessModel.kt:43] + * #### Generated from [EngineProcessModel.kt:44] */ data class GenerateParams ( val mockInstalled: Boolean, @@ -536,7 +536,7 @@ data class GenerateParams ( /** - * #### Generated from [EngineProcessModel.kt:61] + * #### Generated from [EngineProcessModel.kt:62] */ data class GenerateResult ( val notEmptyCases: Int, @@ -599,7 +599,7 @@ data class GenerateResult ( /** - * #### Generated from [EngineProcessModel.kt:113] + * #### Generated from [EngineProcessModel.kt:114] */ data class GenerateTestReportArgs ( val eventLogMessage: String?, @@ -692,7 +692,7 @@ data class GenerateTestReportArgs ( /** - * #### Generated from [EngineProcessModel.kt:122] + * #### Generated from [EngineProcessModel.kt:123] */ data class GenerateTestReportResult ( val notifyMessage: String, @@ -824,7 +824,7 @@ data class JdkInfo ( /** - * #### Generated from [EngineProcessModel.kt:89] + * #### Generated from [EngineProcessModel.kt:90] */ data class MethodDescription ( val name: String, @@ -893,7 +893,7 @@ data class MethodDescription ( /** - * #### Generated from [EngineProcessModel.kt:65] + * #### Generated from [EngineProcessModel.kt:66] */ data class RenderParams ( val testSetsId: Long, @@ -1034,7 +1034,7 @@ data class RenderParams ( /** - * #### Generated from [EngineProcessModel.kt:82] + * #### Generated from [EngineProcessModel.kt:83] */ data class RenderResult ( val generatedCode: String, @@ -1097,7 +1097,7 @@ data class RenderResult ( /** - * #### Generated from [EngineProcessModel.kt:86] + * #### Generated from [EngineProcessModel.kt:87] */ data class SetupContextParams ( val classpathForUrlsClassloader: List @@ -1160,7 +1160,8 @@ data class TestGeneratorParams ( val buildDir: Array, val classpath: String?, val dependencyPaths: String, - val jdkInfo: JdkInfo + val jdkInfo: JdkInfo, + val applicationContext: ByteArray ) : IPrintable { //companion @@ -1173,7 +1174,8 @@ data class TestGeneratorParams ( val classpath = buffer.readNullable { buffer.readString() } val dependencyPaths = buffer.readString() val jdkInfo = JdkInfo.read(ctx, buffer) - return TestGeneratorParams(buildDir, classpath, dependencyPaths, jdkInfo) + val applicationContext = buffer.readByteArray() + return TestGeneratorParams(buildDir, classpath, dependencyPaths, jdkInfo, applicationContext) } override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: TestGeneratorParams) { @@ -1181,6 +1183,7 @@ data class TestGeneratorParams ( buffer.writeNullable(value.classpath) { buffer.writeString(it) } buffer.writeString(value.dependencyPaths) JdkInfo.write(ctx, buffer, value.jdkInfo) + buffer.writeByteArray(value.applicationContext) } @@ -1200,6 +1203,7 @@ data class TestGeneratorParams ( if (classpath != other.classpath) return false if (dependencyPaths != other.dependencyPaths) return false if (jdkInfo != other.jdkInfo) return false + if (!(applicationContext contentEquals other.applicationContext)) return false return true } @@ -1210,6 +1214,7 @@ data class TestGeneratorParams ( __r = __r*31 + if (classpath != null) classpath.hashCode() else 0 __r = __r*31 + dependencyPaths.hashCode() __r = __r*31 + jdkInfo.hashCode() + __r = __r*31 + applicationContext.contentHashCode() return __r } //pretty print @@ -1220,6 +1225,7 @@ data class TestGeneratorParams ( print("classpath = "); classpath.print(printer); println() print("dependencyPaths = "); dependencyPaths.print(printer); println() print("jdkInfo = "); jdkInfo.print(printer); println() + print("applicationContext = "); applicationContext.print(printer); println() } printer.print(")") } @@ -1229,7 +1235,7 @@ data class TestGeneratorParams ( /** - * #### Generated from [EngineProcessModel.kt:108] + * #### Generated from [EngineProcessModel.kt:109] */ data class WriteSarifReportArguments ( val testSetsId: Long, 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 e7f9911d75..329e17e20c 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 @@ -27,6 +27,7 @@ import org.utbot.framework.CancellationStrategyType.SAVE_PROCESSED_RESULTS import org.utbot.framework.UtSettings 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.util.LockFile import org.utbot.framework.plugin.api.util.withStaticsSubstitutionRequired import org.utbot.framework.plugin.services.JdkInfoService @@ -168,6 +169,12 @@ object UtTestsDialogProcessor { psiClass.canonicalName to psiClass.containingFile.virtualFile.canonicalPath }.toMap() } + + // TODO: obtain bean definitions and other info from `utbot-spring-analyzer` + val springApplicationContext = SpringApplicationContext( + beanQualifiedNames = emptyList(), + ) + val process = EngineProcess.createBlocking(project, classNameToPath) process.terminateOnException { _ -> @@ -176,14 +183,14 @@ object UtTestsDialogProcessor { buildDirs, classpath, pluginJarsPath.joinToString(separator = File.pathSeparator), - JdkInfoService.provide() + JdkInfoService.provide(), + springApplicationContext, ) { ApplicationManager.getApplication().runReadAction(Computable { indicator.isCanceled }) } - for (srcClass in model.srcClasses) { if (indicator.isCanceled) { when (UtSettings.cancellationStrategyType) { 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 c63682b2b5..b0eec3639b 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 @@ -188,6 +188,7 @@ class EngineProcess private constructor(val project: Project, private val classN classPath: String?, dependencyPaths: String, jdkInfo: JdkInfo, + applicationContext: ApplicationContext, isCancelled: (Unit) -> Boolean ) { assertReadAccessNotAllowed() @@ -199,7 +200,8 @@ class EngineProcess private constructor(val project: Project, private val classN buildDir.toTypedArray(), classPath, dependencyPaths, - JdkInfo(jdkInfo.path.pathString, jdkInfo.version) + JdkInfo(jdkInfo.path.pathString, jdkInfo.version), + kryoHelper.writeObject(applicationContext) ) engineModel.createTestGenerator.startBlocking(params) } 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 532962d55b..08484c8273 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 @@ -39,6 +39,7 @@ object EngineProcessModel : Ext(EngineProcessRoot) { field("classpath", PredefinedType.string.nullable) field("dependencyPaths", PredefinedType.string) field("jdkInfo", jdkInfo) + field("applicationContext", array(PredefinedType.byte)) } val generateParams = structdef { // mocks