diff --git a/.run/Debug All.run.xml b/.run/Debug All.run.xml index 4e66ecc45a..a0cf8bd64d 100644 --- a/.run/Debug All.run.xml +++ b/.run/Debug All.run.xml @@ -3,6 +3,7 @@ + \ No newline at end of file diff --git a/.run/Debug Spring Analyzer Process.run.xml b/.run/Debug Spring Analyzer Process.run.xml new file mode 100644 index 0000000000..91c80ebf73 --- /dev/null +++ b/.run/Debug Spring Analyzer Process.run.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.run/Listen for Spring Analyzer Process.run.xml b/.run/Listen for Spring Analyzer Process.run.xml new file mode 100644 index 0000000000..80a615201a --- /dev/null +++ b/.run/Listen for Spring Analyzer Process.run.xml @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 259ec1b8e2..19e94c53a0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -19,11 +19,6 @@ plugins { `maven-publish` } -configure { - sourceCompatibility = VERSION_11 - targetCompatibility = VERSION_17 -} - allprojects { apply { diff --git a/gradle.properties b/gradle.properties index 59862181bb..485d03a1d9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -77,6 +77,9 @@ pytorchNativeVersion=1.9.1 shadowJarVersion=7.1.2 openblasVersion=0.3.10-1.5.4 arpackNgVersion=3.7.0-1.5.4 +commonsLoggingVersion=1.2 +commonsIOVersion=2.11.0 +springBootVersion=2.7.8 # configuration for build server # diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt index cdff5e110a..983e806661 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt @@ -283,7 +283,7 @@ object UtSettings : AbstractSettings(logger, defaultKeyForSettingsPath, defaultS var runEngineProcessWithDebug by getBooleanProperty(false) /** - * The engine process JDWP agent's port of the instrumented process. + * The engine process JDWP agent's port of the engine process. * A debugger attaches to the port in order to debug the process. */ var engineProcessDebugPort by getIntProperty(5005) @@ -296,6 +296,29 @@ object UtSettings : AbstractSettings(logger, defaultKeyForSettingsPath, defaultS // endregion +// region spring analyzer process debug + /** + * The property is useful only for the IntelliJ IDEs. + * If the property is set in true the spring analyzer process opens a debug port. + * @see runInstrumentedProcessWithDebug + * @see org.utbot.framework.process.SpringAnalyzerProcess + */ + var runSpringAnalyzerProcessWithDebug by getBooleanProperty(false) + + /** + * The spring analyzer process JDWP agent's port. + * A debugger attaches to the port in order to debug the process. + */ + var springAnalyzerProcessDebugPort by getIntProperty(5007) + + /** + * Value of the suspend mode for the JDWP agent of the spring analyzer process. + * If the value is true, the spring analyzer process will suspend until a debugger attaches to it. + */ + var suspendSpringAnalyzerProcessExecutionInDebugMode by getBooleanProperty(true) + +// endregion + // region instrumented process debug /** * The instrumented process JDWP agent's port of the instrumented process. @@ -312,7 +335,8 @@ object UtSettings : AbstractSettings(logger, defaultKeyForSettingsPath, defaultS /** * If true, runs the instrumented process with the ability to attach a debugger. * - * To debug the instrumented process, set the breakpoint in the instrumentedProcessRunner.start() line + * To debug the instrumented process, set the breakpoint in the + * [org.utbot.instrumentation.rd.InstrumentedProcess.Companion.invoke] * and in the instrumented process's main function and run the main process. * Then run the remote JVM debug configuration in IDEA. * If you see the message in console about successful connection, then @@ -320,7 +344,7 @@ object UtSettings : AbstractSettings(logger, defaultKeyForSettingsPath, defaultS * Now you can put the breakpoints in the instrumented process and debug * both processes simultaneously. * - * @see [org.utbot.instrumentation.process.InstrumentedProcessRunner.cmds] + * @see [org.utbot.instrumentation.rd.InstrumentedProcess.Companion.invoke] */ var runInstrumentedProcessWithDebug by getBooleanProperty(false) // endregion diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/services/JdkInfoService.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/services/JdkInfoService.kt index 62ed3560c2..08d18c1034 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/services/JdkInfoService.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/services/JdkInfoService.kt @@ -11,7 +11,7 @@ data class JdkInfo( /** * Singleton to enable abstract access to path to JDK. - * Used in [org.utbot.instrumentation.process.InstrumentedProcessRunner]. + * Used in [org.utbot.framework.process.AbstractRDProcessCompanion]. * The purpose is to use the same JDK in [org.utbot.instrumentation.ConcreteExecutor] and in the test runs. * This is necessary because the engine can be run from the various starting points, like IDEA plugin, CLI, etc. */ diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/services/WorkingDirService.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/services/WorkingDirService.kt index d7ce174bdd..e448a21a76 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/services/WorkingDirService.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/services/WorkingDirService.kt @@ -6,7 +6,7 @@ import java.nio.file.Paths /** * Singleton to enable abstract access to the working directory. * - * Used in [org.utbot.instrumentation.process.InstrumentedProcessRunner]. + * Used in [org.utbot.instrumentation.rd.InstrumentedProcess]. * The purpose is to use the same working directory in [org.utbot.instrumentation.ConcreteExecutor] * and in the test runs. */ diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/process/AbstractRDProcessCompanion.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/process/AbstractRDProcessCompanion.kt new file mode 100644 index 0000000000..41b0499cae --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/process/AbstractRDProcessCompanion.kt @@ -0,0 +1,37 @@ +package org.utbot.framework.process + +import org.utbot.common.osSpecificJavaExecutable +import org.utbot.framework.plugin.services.JdkInfoService +import org.utbot.rd.rdPortArgument +import java.io.File +import kotlin.io.path.pathString + +abstract class AbstractRDProcessCompanion( + private val debugPort: Int, + private val runWithDebug: Boolean, + private val suspendExecutionInDebugMode: Boolean, + private val processSpecificCommandLineArgs: () -> List +) { + private val javaExecutablePathString get() = + JdkInfoService.provide().path.resolve("bin${File.separatorChar}${osSpecificJavaExecutable()}") + + protected fun obtainProcessCommandLine(port: Int): List = buildList { + addAll(obtainCommonProcessCommandLineArgs()) + addAll(processSpecificCommandLineArgs()) + add(rdPortArgument(port)) + } + + private fun obtainCommonProcessCommandLineArgs(): List = buildList { + val suspendValue = if (suspendExecutionInDebugMode) "y" else "n" + val debugArgument = + "-agentlib:jdwp=transport=dt_socket,server=n,suspend=${suspendValue},quiet=y,address=$debugPort" + .takeIf { runWithDebug } + + add(javaExecutablePathString.pathString) + val javaVersionSpecificArgs = OpenModulesContainer.javaVersionSpecificArguments + if (javaVersionSpecificArgs.isNotEmpty()) { + addAll(javaVersionSpecificArgs) + } + debugArgument?.let { add(it) } + } +} \ No newline at end of file diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/process/OpenModulesContainer.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/process/OpenModulesContainer.kt index b5616b4838..2cc37032d4 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/process/OpenModulesContainer.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/process/OpenModulesContainer.kt @@ -11,6 +11,7 @@ object OpenModulesContainer { init { modulesContainer = buildList { openPackage("java.base", "sun.security.util") + openPackage("java.base", "sun.reflect.annotation") openPackage("java.base", "java.text") openPackage("java.base", "java.lang.invoke") openPackage("java.base", "jdk.internal.misc") diff --git a/utbot-framework/build.gradle b/utbot-framework/build.gradle index 3e0cd02564..5ca456d1a1 100644 --- a/utbot-framework/build.gradle +++ b/utbot-framework/build.gradle @@ -1,3 +1,7 @@ +configurations { + fetchSpringAnalyzerJar +} + dependencies { api project(':utbot-fuzzers') @@ -34,4 +38,13 @@ dependencies { implementation group: 'com.github.UnitTestBot.ksmt', name: 'ksmt-core', version: ksmtVersion implementation group: 'com.github.UnitTestBot.ksmt', name: 'ksmt-z3', version: ksmtVersion + implementation project(':utbot-spring-analyzer') + + fetchSpringAnalyzerJar project(path: ':utbot-spring-analyzer', configuration: 'springAnalyzerJar') +} + +processResources { + from(configurations.fetchSpringAnalyzerJar) { + into "lib" + } } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/Domain.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/Domain.kt index 8a3b48e9dc..248bd160db 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/Domain.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/Domain.kt @@ -3,7 +3,6 @@ package org.utbot.framework.codegen.domain import org.utbot.framework.DEFAULT_EXECUTION_TIMEOUT_IN_INSTRUMENTED_PROCESS_MS import org.utbot.framework.codegen.domain.builtin.mockitoClassId import org.utbot.framework.codegen.domain.builtin.ongoingStubbingClassId -import org.utbot.framework.codegen.domain.context.CgContext import org.utbot.framework.codegen.domain.models.CgClassId import org.utbot.framework.codegen.tree.argumentsClassId import org.utbot.framework.plugin.api.BuiltinClassId @@ -751,7 +750,7 @@ sealed class TypeReplacementApproach { * * Currently used in Spring applications only. */ - class ReplaceIfPossible(val configFqn: String) : TypeReplacementApproach() + class ReplaceIfPossible(val config: String) : TypeReplacementApproach() } abstract class DependencyInjectionFramework( 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 f0ac645d73..ca1d6cb96b 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 @@ -34,8 +34,10 @@ import org.utbot.rd.RdSettingsContainerFactory import org.utbot.rd.findRdPort import org.utbot.rd.generated.settingsModel import org.utbot.rd.loggers.UtRdKLoggerFactory +import org.utbot.rd.terminateOnException import org.utbot.sarif.RdSourceFindingStrategyFacade import org.utbot.sarif.SarifReport +import org.utbot.spring.process.SpringAnalyzerProcess import org.utbot.summary.summarizeAll import java.io.File import java.net.URLClassLoader @@ -51,16 +53,11 @@ object EngineProcessMain // use log4j2.configurationFile property to set log4j configuration suspend fun main(args: Array) = runBlocking { - Logger.set(Lifetime.Eternal, UtRdKLoggerFactory(logger)) - logger.info("-----------------------------------------------------------------------") logger.info("-------------------NEW ENGINE PROCESS STARTED--------------------------") logger.info("-----------------------------------------------------------------------") - // 0 - auto port for server, should not be used here - val port = findRdPort(args) - - ClientProtocolBuilder().withProtocolTimeout(messageFromMainTimeoutMillis).start(port) { + ClientProtocolBuilder().withProtocolTimeout(messageFromMainTimeoutMillis).start(args) { AbstractSettings.setupFactory(RdSettingsContainerFactory(protocol.settingsModel)) val kryoHelper = KryoHelper(lifetime) engineProcessModel.setup(kryoHelper, it, protocol) @@ -79,6 +76,21 @@ private fun EngineProcessModel.setup(kryoHelper: KryoHelper, watchdog: IdleWatch File(it).toURI().toURL() }.toTypedArray()))) } + watchdog.measureTimeForActiveCall(getSpringBeanQualifiedNames, "Getting Spring bean definitions") { params -> + val springAnalyzerProcess = SpringAnalyzerProcess.createBlocking() + val beans = springAnalyzerProcess.terminateOnException { _ -> + springAnalyzerProcess.getBeanQualifiedNames( + params.classpath.toList(), + params.config, + // TODO remove once spring-analyzer learns to find resources on its own, temporarily leaving it here for testing with hardcoded absolute paths + propertyFilesPaths = emptyList(), + xmlConfigurationPaths = emptyList(), + params.useSpringAnalyzer + ).toTypedArray() + } + springAnalyzerProcess.terminate() + beans + } watchdog.measureTimeForActiveCall(createTestGenerator, "Creating Test Generator") { params -> AnalyticsConfigureUtil.configureML() Instrumenter.adapter = RdInstrumenter(realProtocol.rdInstrumenterAdapter) 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 c4029c1862..47d86d3837 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 @@ -19,6 +19,7 @@ import kotlin.jvm.JvmStatic */ class EngineProcessModel private constructor( private val _setupUtContext: RdCall, + private val _getSpringBeanQualifiedNames: RdCall>, private val _createTestGenerator: RdCall, private val _isCancelled: RdCall, private val _generate: RdCall, @@ -41,6 +42,7 @@ class EngineProcessModel private constructor( serializers.register(RenderParams) serializers.register(RenderResult) serializers.register(SetupContextParams) + serializers.register(GetSpringBeanQualifiedNamesParams) serializers.register(MethodDescription) serializers.register(FindMethodsInClassMatchingSelectedArguments) serializers.register(FindMethodsInClassMatchingSelectedResult) @@ -68,8 +70,9 @@ class EngineProcessModel private constructor( return EngineProcessModel() } + private val __StringArraySerializer = FrameworkMarshallers.String.array() - const val serializationHash = -2087034443345538396L + const val serializationHash = 1955031277042475752L } override val serializersOwner: ISerializersOwner get() = EngineProcessModel @@ -77,6 +80,7 @@ class EngineProcessModel private constructor( //fields val setupUtContext: RdCall get() = _setupUtContext + val getSpringBeanQualifiedNames: RdCall> get() = _getSpringBeanQualifiedNames val createTestGenerator: RdCall get() = _createTestGenerator val isCancelled: RdCall get() = _isCancelled val generate: RdCall get() = _generate @@ -90,6 +94,7 @@ class EngineProcessModel private constructor( //initializer init { _setupUtContext.async = true + _getSpringBeanQualifiedNames.async = true _createTestGenerator.async = true _isCancelled.async = true _generate.async = true @@ -103,6 +108,7 @@ class EngineProcessModel private constructor( init { bindableChildren.add("setupUtContext" to _setupUtContext) + bindableChildren.add("getSpringBeanQualifiedNames" to _getSpringBeanQualifiedNames) bindableChildren.add("createTestGenerator" to _createTestGenerator) bindableChildren.add("isCancelled" to _isCancelled) bindableChildren.add("generate" to _generate) @@ -118,6 +124,7 @@ class EngineProcessModel private constructor( private constructor( ) : this( RdCall(SetupContextParams, FrameworkMarshallers.Void), + RdCall>(GetSpringBeanQualifiedNamesParams, __StringArraySerializer), RdCall(TestGeneratorParams, FrameworkMarshallers.Void), RdCall(FrameworkMarshallers.Void, FrameworkMarshallers.Bool), RdCall(GenerateParams, GenerateResult), @@ -136,6 +143,7 @@ class EngineProcessModel private constructor( printer.println("EngineProcessModel (") printer.indent { print("setupUtContext = "); _setupUtContext.print(printer); println() + print("getSpringBeanQualifiedNames = "); _getSpringBeanQualifiedNames.print(printer); println() print("createTestGenerator = "); _createTestGenerator.print(printer); println() print("isCancelled = "); _isCancelled.print(printer); println() print("generate = "); _generate.print(printer); println() @@ -152,6 +160,7 @@ class EngineProcessModel private constructor( override fun deepClone(): EngineProcessModel { return EngineProcessModel( _setupUtContext.deepClonePolymorphic(), + _getSpringBeanQualifiedNames.deepClonePolymorphic(), _createTestGenerator.deepClonePolymorphic(), _isCancelled.deepClonePolymorphic(), _generate.deepClonePolymorphic(), @@ -170,7 +179,7 @@ val IProtocol.engineProcessModel get() = getOrCreateExtension(EngineProcessModel /** - * #### Generated from [EngineProcessModel.kt:99] + * #### Generated from [EngineProcessModel.kt:104] */ data class FindMethodParamNamesArguments ( val classId: ByteArray, @@ -233,7 +242,7 @@ data class FindMethodParamNamesArguments ( /** - * #### Generated from [EngineProcessModel.kt:103] + * #### Generated from [EngineProcessModel.kt:108] */ data class FindMethodParamNamesResult ( val paramNames: ByteArray @@ -290,7 +299,7 @@ data class FindMethodParamNamesResult ( /** - * #### Generated from [EngineProcessModel.kt:92] + * #### Generated from [EngineProcessModel.kt:97] */ data class FindMethodsInClassMatchingSelectedArguments ( val classId: ByteArray, @@ -353,7 +362,7 @@ data class FindMethodsInClassMatchingSelectedArguments ( /** - * #### Generated from [EngineProcessModel.kt:96] + * #### Generated from [EngineProcessModel.kt:101] */ data class FindMethodsInClassMatchingSelectedResult ( val executableIds: ByteArray @@ -578,7 +587,7 @@ data class GenerateResult ( /** - * #### Generated from [EngineProcessModel.kt:111] + * #### Generated from [EngineProcessModel.kt:116] */ data class GenerateTestReportArgs ( val eventLogMessage: String?, @@ -671,7 +680,7 @@ data class GenerateTestReportArgs ( /** - * #### Generated from [EngineProcessModel.kt:120] + * #### Generated from [EngineProcessModel.kt:125] */ data class GenerateTestReportResult ( val notifyMessage: String, @@ -739,6 +748,75 @@ data class GenerateTestReportResult ( } +/** + * #### Generated from [EngineProcessModel.kt:87] + */ +data class GetSpringBeanQualifiedNamesParams ( + val classpath: Array, + val config: String, + val useSpringAnalyzer: Boolean +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = GetSpringBeanQualifiedNamesParams::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): GetSpringBeanQualifiedNamesParams { + val classpath = buffer.readArray {buffer.readString()} + val config = buffer.readString() + val useSpringAnalyzer = buffer.readBool() + return GetSpringBeanQualifiedNamesParams(classpath, config, useSpringAnalyzer) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: GetSpringBeanQualifiedNamesParams) { + buffer.writeArray(value.classpath) { buffer.writeString(it) } + buffer.writeString(value.config) + buffer.writeBool(value.useSpringAnalyzer) + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as GetSpringBeanQualifiedNamesParams + + if (!(classpath contentDeepEquals other.classpath)) return false + if (config != other.config) return false + if (useSpringAnalyzer != other.useSpringAnalyzer) 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 + useSpringAnalyzer.hashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("GetSpringBeanQualifiedNamesParams (") + printer.indent { + print("classpath = "); classpath.print(printer); println() + print("config = "); config.print(printer); println() + print("useSpringAnalyzer = "); useSpringAnalyzer.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + /** * #### Generated from [EngineProcessModel.kt:32] */ @@ -803,7 +881,7 @@ data class JdkInfo ( /** - * #### Generated from [EngineProcessModel.kt:87] + * #### Generated from [EngineProcessModel.kt:92] */ data class MethodDescription ( val name: String, @@ -1220,7 +1298,7 @@ data class TestGeneratorParams ( /** - * #### Generated from [EngineProcessModel.kt:106] + * #### Generated from [EngineProcessModel.kt:111] */ data class WriteSarifReportArguments ( val testSetsId: Long, diff --git a/utbot-instrumentation-tests/build.gradle b/utbot-instrumentation-tests/build.gradle index a6df1408c9..aefc5967a8 100644 --- a/utbot-instrumentation-tests/build.gradle +++ b/utbot-instrumentation-tests/build.gradle @@ -31,7 +31,7 @@ dependencies { } processResources { - // We will extract this jar in `InstrumentedProcessRunner` class. + // We will extract this jar in `InstrumentedProcess` class. from(configurations.fetchInstrumentationJar) { into "instrumentation-lib" } diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/ConcreteExecutor.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/ConcreteExecutor.kt index 07abbdb850..dc34abe14d 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/ConcreteExecutor.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/ConcreteExecutor.kt @@ -27,7 +27,6 @@ import org.utbot.framework.plugin.api.FieldId import org.utbot.framework.plugin.api.util.UtContext import org.utbot.framework.plugin.api.util.signature import org.utbot.instrumentation.instrumentation.Instrumentation -import org.utbot.instrumentation.process.InstrumentedProcessRunner import org.utbot.instrumentation.process.generated.ComputeStaticFieldParams import org.utbot.instrumentation.process.generated.InvokeMethodCommandParams import org.utbot.instrumentation.rd.InstrumentedProcess @@ -112,7 +111,6 @@ class ConcreteExecutor> p internal val pathsToUserClasses: String ) : Closeable, Executor { private val ldef: LifetimeDefinition = LifetimeDefinition() - private val instrumentedProcessRunner: InstrumentedProcessRunner = InstrumentedProcessRunner() companion object { @@ -161,7 +159,6 @@ class ConcreteExecutor> p if (proc == null || !proc.lifetime.isAlive) { proc = InstrumentedProcess( ldef, - instrumentedProcessRunner, instrumentation, pathsToUserClasses, classLoader diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/process/InstrumentedProcessMain.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/process/InstrumentedProcessMain.kt index 0e1af9c98c..486e282537 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/process/InstrumentedProcessMain.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/process/InstrumentedProcessMain.kt @@ -18,15 +18,10 @@ import org.utbot.instrumentation.util.KryoHelper import org.utbot.rd.IdleWatchdog import org.utbot.rd.ClientProtocolBuilder import org.utbot.rd.RdSettingsContainerFactory -import org.utbot.rd.findRdPort import org.utbot.rd.generated.loggerModel import org.utbot.rd.generated.settingsModel -import org.utbot.rd.generated.synchronizationModel import org.utbot.rd.loggers.UtRdRemoteLoggerFactory import java.io.File -import java.io.IOException -import java.io.OutputStream -import java.io.PrintStream import java.net.URLClassLoader import java.security.AllPermission import kotlin.time.Duration @@ -62,23 +57,6 @@ const val DISABLE_SANDBOX_OPTION = "--disable-sandbox" private val logger = getLogger() private val messageFromMainTimeout: Duration = 120.seconds -private fun closeStandardStreams() { - // we should change out/err streams as not to spend time on user output - // and also because rd default logging system writes some initial values to stdout, polluting it as well - val tmpStream = PrintStream(object : OutputStream() { - override fun write(b: Int) {} - }) - val prevOut = System.out - val prevError = System.err - System.setOut(tmpStream) - System.setErr(tmpStream) - // stdin/stderr should be closed as not to leave hanging descriptors - // and we cannot log any exceptions here as rd remote logging is still not configured - // so we pass any exceptions - silent { prevOut.close() } - silent { prevError.close() } -} - interface DummyForMockitoWarmup { fun method1() } @@ -102,9 +80,6 @@ object InstrumentedProcessMain * It should be compiled into separate jar file (instrumented_process.jar) and be run with an agent (agent.jar) option. */ fun main(args: Array) = runBlocking { - // We don't want user code to litter the standard output, so we redirect it. - closeStandardStreams() - if (!args.contains(DISABLE_SANDBOX_OPTION)) { permissions { // Enable all permissions for instrumentation. @@ -113,11 +88,9 @@ fun main(args: Array) = runBlocking { } } - val port = findRdPort(args) - try { - ClientProtocolBuilder().withProtocolTimeout(messageFromMainTimeout).start(port) { - synchronizationModel.initRemoteLogging.adviseOnce(lifetime) { + ClientProtocolBuilder().withProtocolTimeout(messageFromMainTimeout).start(args) { + loggerModel.initRemoteLogging.adviseOnce(lifetime) { Logger.set(Lifetime.Eternal, UtRdRemoteLoggerFactory(loggerModel)) this.protocol.scheduler.queue { warmupMockito() } } diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/process/InstrumentedProcessRunner.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/process/InstrumentedProcessRunner.kt deleted file mode 100644 index 8d06d98128..0000000000 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/process/InstrumentedProcessRunner.kt +++ /dev/null @@ -1,109 +0,0 @@ -package org.utbot.instrumentation.process - -import mu.KotlinLogging -import org.utbot.common.* -import org.utbot.common.scanForResourcesContaining -import org.utbot.common.utBotTempDirectory -import org.utbot.framework.plugin.services.JdkInfoService -import org.utbot.framework.UtSettings -import org.utbot.framework.plugin.services.WorkingDirService -import org.utbot.framework.process.OpenModulesContainer -import org.utbot.instrumentation.agent.DynamicClassTransformer -import org.utbot.rd.rdPortArgument -import java.io.File - -private val logger = KotlinLogging.logger {} - -class InstrumentedProcessRunner { - private val cmds: List by lazy { - val debugCmd = listOfNotNull(DEBUG_RUN_CMD.takeIf { UtSettings.runInstrumentedProcessWithDebug }) - val javaVersionSpecificArguments = OpenModulesContainer.javaVersionSpecificArguments - val pathToJava = JdkInfoService.provide().path - - listOf(pathToJava.resolve("bin${File.separatorChar}${osSpecificJavaExecutable()}").toString()) + - debugCmd + - javaVersionSpecificArguments + - listOf("-javaagent:$jarFile", "-ea", "-jar", "$jarFile") - } - - fun start(rdPort: Int): Process { - val portArgument = rdPortArgument(rdPort) - - logger.debug { "Starting instrumented process: ${cmds.joinToString(" ")} $portArgument" } - - val directory = WorkingDirService.provide().toFile() - val commandsWithOptions = buildList { - addAll(cmds) - if (!UtSettings.useSandbox) { - add(DISABLE_SANDBOX_OPTION) - } - add(portArgument) - } - - val processBuilder = ProcessBuilder(commandsWithOptions) - .directory(directory) - - return processBuilder.start().also { - logger.info { - "------------------------------------------------------------------\n" + - "--------Instrumented process started with PID=${it.getPid}--------\n" + - "------------------------------------------------------------------" - } - } - } - - companion object { - private fun suspendValue(): String = if (UtSettings.suspendInstrumentedProcessExecutionInDebugMode) "y" else "n" - - private const val UTBOT_INSTRUMENTATION = "utbot-instrumentation" - private const val INSTRUMENTATION_LIB = "lib" - - private val DEBUG_RUN_CMD = "-agentlib:jdwp=transport=dt_socket,server=n,suspend=${suspendValue()},quiet=y,address=${UtSettings.instrumentedProcessDebugPort}" - - private val NULL_FILE_PATH: String = if (System.getProperty("os.name").startsWith("Windows")) { - "NUL" - } else { - "/dev/null" - } - - private val NULL_FILE = File(NULL_FILE_PATH) - - /** - * * Firstly, searches for utbot-instrumentation jar in the classpath. - * - * * In case of failure, searches for utbot-instrumentation jar in the resources and extracts it to the - * temporary directory. This jar file must be placed to the resources by `processResources` gradle task - * in the gradle configuration of the project which depends on utbot-instrumentation module. - */ - private val jarFile: File by lazy { - logger.debug().measureTime({ "Finding $UTBOT_INSTRUMENTATION jar" } ) { - run { - logger.debug("Trying to find jar in the resources.") - val tempDir = utBotTempDirectory.toFile() - val unzippedJarName = "$UTBOT_INSTRUMENTATION-${currentProcessPid}.jar" - val instrumentationJarFile = File(tempDir, unzippedJarName) - - InstrumentedProcessRunner::class.java.classLoader - .firstOrNullResourceIS(INSTRUMENTATION_LIB) { resourcePath -> - resourcePath.contains(UTBOT_INSTRUMENTATION) && resourcePath.endsWith(".jar") - } - ?.use { input -> - instrumentationJarFile.writeBytes(input.readBytes()) - } - ?: return@run null - instrumentationJarFile - } ?: run { - logger.debug("Failed to find jar in the resources. Trying to find it in the classpath.") - InstrumentedProcessRunner::class.java.classLoader - .scanForResourcesContaining(DynamicClassTransformer::class.java.nameOfPackage) - .firstOrNull { - it.absolutePath.contains(UTBOT_INSTRUMENTATION) && it.extension == "jar" - } - } ?: error(""" - Can't find file: $UTBOT_INSTRUMENTATION-.jar. - Make sure you added $UTBOT_INSTRUMENTATION-.jar to the resources folder from gradle. - """.trimIndent()) - } - } - } -} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/rd/InstrumentedProcess.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/rd/InstrumentedProcess.kt index 973c79a597..03d1ccb4ec 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/rd/InstrumentedProcess.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/rd/InstrumentedProcess.kt @@ -2,9 +2,20 @@ package org.utbot.instrumentation.rd import com.jetbrains.rd.util.lifetime.Lifetime import mu.KotlinLogging +import org.utbot.common.currentProcessPid +import org.utbot.common.debug +import org.utbot.common.firstOrNullResourceIS +import org.utbot.common.getPid +import org.utbot.common.measureTime +import org.utbot.common.nameOfPackage +import org.utbot.common.scanForResourcesContaining +import org.utbot.common.utBotTempDirectory import org.utbot.framework.UtSettings +import org.utbot.framework.plugin.services.WorkingDirService +import org.utbot.framework.process.AbstractRDProcessCompanion +import org.utbot.instrumentation.agent.DynamicClassTransformer import org.utbot.instrumentation.instrumentation.Instrumentation -import org.utbot.instrumentation.process.InstrumentedProcessRunner +import org.utbot.instrumentation.process.DISABLE_SANDBOX_OPTION import org.utbot.instrumentation.process.generated.AddPathsParams import org.utbot.instrumentation.process.generated.InstrumentedProcessModel import org.utbot.instrumentation.process.generated.SetInstrumentationParams @@ -14,16 +25,55 @@ import org.utbot.rd.ProcessWithRdServer import org.utbot.rd.exceptions.InstantProcessDeathException import org.utbot.rd.generated.LoggerModel import org.utbot.rd.generated.loggerModel -import org.utbot.rd.generated.synchronizationModel import org.utbot.rd.loggers.UtRdKLogger -import org.utbot.rd.loggers.UtRdRemoteLogger +import org.utbot.rd.loggers.setup import org.utbot.rd.onSchedulerBlocking import org.utbot.rd.startUtProcessWithRdServer import org.utbot.rd.terminateOnException +import java.io.File private val logger = KotlinLogging.logger { } private val rdLogger = UtRdKLogger(logger, "") +private const val UTBOT_INSTRUMENTATION = "utbot-instrumentation" +private const val INSTRUMENTATION_LIB = "lib" + +private fun tryFindInstrumentationJarInResources(): File? { + logger.debug("Trying to find jar in the resources.") + val tempDir = utBotTempDirectory.toFile() + val unzippedJarName = "$UTBOT_INSTRUMENTATION-${currentProcessPid}.jar" + val instrumentationJarFile = File(tempDir, unzippedJarName) + + InstrumentedProcess::class.java.classLoader + .firstOrNullResourceIS(INSTRUMENTATION_LIB) { resourcePath -> + resourcePath.contains(UTBOT_INSTRUMENTATION) && resourcePath.endsWith(".jar") + } + ?.use { input -> + instrumentationJarFile.writeBytes(input.readBytes()) + } ?: return null + return instrumentationJarFile +} + +private fun tryFindInstrumentationJarOnClasspath(): File? { + logger.debug("Trying to find it in the classpath.") + return InstrumentedProcess::class.java.classLoader + .scanForResourcesContaining(DynamicClassTransformer::class.java.nameOfPackage) + .firstOrNull { + it.absolutePath.contains(UTBOT_INSTRUMENTATION) && it.extension == "jar" + } +} + +private val instrumentationJarFile: File = + logger.debug().measureTime({ "Finding $UTBOT_INSTRUMENTATION jar" } ) { + tryFindInstrumentationJarInResources() ?: run { + logger.debug("Failed to find jar in the resources.") + tryFindInstrumentationJarOnClasspath() + } ?: error(""" + Can't find file: $UTBOT_INSTRUMENTATION-.jar. + Make sure you added $UTBOT_INSTRUMENTATION-.jar to the resources folder from gradle. + """.trimIndent()) + } + class InstrumentedProcessInstantDeathException : InstantProcessDeathException(UtSettings.instrumentedProcessDebugPort, UtSettings.runInstrumentedProcessWithDebug) @@ -42,18 +92,41 @@ class InstrumentedProcess private constructor( val instrumentedProcessModel: InstrumentedProcessModel = onSchedulerBlocking { protocol.instrumentedProcessModel } val loggerModel: LoggerModel = onSchedulerBlocking { protocol.loggerModel } - companion object { + companion object : AbstractRDProcessCompanion( + debugPort = UtSettings.instrumentedProcessDebugPort, + runWithDebug = UtSettings.runInstrumentedProcessWithDebug, + suspendExecutionInDebugMode = UtSettings.suspendInstrumentedProcessExecutionInDebugMode, + processSpecificCommandLineArgs = { + buildList { + add("-javaagent:${instrumentationJarFile.path}") + add("-ea") + add("-jar") + add(instrumentationJarFile.path) + if (!UtSettings.useSandbox) + add(DISABLE_SANDBOX_OPTION) + } + }) { + suspend operator fun > invoke( parent: Lifetime, - instrumentedProcessRunner: InstrumentedProcessRunner, instrumentation: TInstrumentation, pathsToUserClasses: String, classLoader: ClassLoader? ): InstrumentedProcess = parent.createNested().terminateOnException { lifetime -> val rdProcess: ProcessWithRdServer = startUtProcessWithRdServer( lifetime = lifetime - ) { - val process = instrumentedProcessRunner.start(it) + ) { port -> + val cmd = obtainProcessCommandLine(port) + logger.debug { "Starting instrumented process: $cmd" } + val directory = WorkingDirService.provide().toFile() + val processBuilder = ProcessBuilder(cmd) + .directory(directory) + val process = processBuilder.start() + logger.info { + "------------------------------------------------------------------\n" + + "--------Instrumented process started with PID=${process.getPid}--------\n" + + "------------------------------------------------------------------" + } if (!process.isAlive) { throw InstrumentedProcessInstantDeathException() } @@ -63,21 +136,7 @@ class InstrumentedProcess private constructor( logger.trace("rd process started") val proc = InstrumentedProcess(classLoader, rdProcess) - - // currently we do not specify log level for different categories in instrumented process - // though it is possible with some additional map on categories -> consider performance - proc.loggerModel.getCategoryMinimalLogLevel.set { _ -> - // this logLevel is obtained from KotlinLogger - rdLogger.logLevel.ordinal - } - - proc.loggerModel.log.advise(proc.lifetime) { - val logLevel = UtRdRemoteLogger.logLevelValues[it.logLevelOrdinal] - // assume throwable already in message - rdLogger.log(logLevel, it.message, null) - } - - rdProcess.protocol.synchronizationModel.initRemoteLogging.fire(Unit) + proc.loggerModel.setup(rdLogger, proc.lifetime) proc.lifetime.onTermination { logger.trace { "process is terminating" } diff --git a/utbot-intellij/build.gradle.kts b/utbot-intellij/build.gradle.kts index 5314824aed..97cf806fff 100644 --- a/utbot-intellij/build.gradle.kts +++ b/utbot-intellij/build.gradle.kts @@ -183,4 +183,4 @@ dependencies { testRuntimeOnly("org.junit.platform:junit-platform-launcher:$junit4PlatformVersion") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junit5Version") testRuntimeOnly("org.junit.vintage:junit-vintage-engine:$junit5Version") -} \ No newline at end of file +} 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 cd1958d82f..cfd5cb6a77 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 @@ -13,9 +13,11 @@ import com.intellij.openapi.roots.ProjectFileIndex import com.intellij.openapi.ui.Messages import com.intellij.openapi.util.Computable import com.intellij.openapi.util.text.StringUtil +import com.intellij.psi.JavaPsiFacade import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiClass import com.intellij.psi.PsiMethod +import com.intellij.psi.search.GlobalSearchScope import com.intellij.refactoring.util.classMembers.MemberInfo import com.intellij.task.ProjectTask import com.intellij.task.ProjectTaskManager @@ -38,7 +40,7 @@ import kotlin.io.path.pathString import mu.KotlinLogging import org.jetbrains.concurrency.Promise import org.jetbrains.idea.maven.project.MavenProjectsManager -import org.jetbrains.kotlin.idea.util.module +import org.jetbrains.kotlin.idea.base.util.module import org.utbot.framework.CancellationStrategyType.CANCEL_EVERYTHING import org.utbot.framework.CancellationStrategyType.NONE import org.utbot.framework.CancellationStrategyType.SAVE_PROCESSED_RESULTS @@ -156,7 +158,15 @@ object UtTestsDialogProcessor { } private fun createTests(project: Project, model: GenerateTestsModel) { - val promise = compile(project, model.srcClasses.map { it.containingFile.virtualFile }.toTypedArray()) + val springConfigClass = when (val approach = model.typeReplacementApproach) { + TypeReplacementApproach.DoNotReplace -> null + is TypeReplacementApproach.ReplaceIfPossible -> + approach.config.takeUnless { it.endsWith(".xml") }?.let { + JavaPsiFacade.getInstance(project).findClass(it, GlobalSearchScope.projectScope(project)) ?: + error("Can't find configuration class $it") + } + } + val promise = compile(project, (model.srcClasses + listOfNotNull(springConfigClass)).map { it.containingFile.virtualFile }.toTypedArray()) promise.onSuccess { if (it.hasErrors() || it.isAborted) return@onSuccess @@ -183,7 +193,7 @@ object UtTestsDialogProcessor { updateIndicator(indicator, ProgressRange.SOLVING, "Generate tests: read classes", 0.0) val buildPaths = ReadAction - .nonBlocking { findPaths(model.srcClasses) } + .nonBlocking { findPaths(model.srcClasses, springConfigClass) } .executeSynchronously() ?: return @@ -202,28 +212,32 @@ object UtTestsDialogProcessor { val mockFrameworkInstalled = model.mockFramework.isInstalled val staticMockingConfigured = model.staticsMocking.isConfigured - val applicationContext = when (model.projectType) { - Spring -> { - val shouldUseImplementors = when (model.typeReplacementApproach) { - TypeReplacementApproach.DoNotReplace -> false - is TypeReplacementApproach.ReplaceIfPossible -> true - } - - SpringApplicationContext( - mockFrameworkInstalled, - staticMockingConfigured, - // TODO: obtain bean definitions and other info from `utbot-spring-analyzer` - beanQualifiedNames = emptyList(), - shouldUseImplementors = shouldUseImplementors, - ) - } - else -> ApplicationContext(mockFrameworkInstalled, staticMockingConfigured) - } - val process = EngineProcess.createBlocking(project, classNameToPath) process.terminateOnException { _ -> process.setupUtContext(buildDirs + classpathList) + val applicationContext = when (model.projectType) { + Spring -> { + val beanQualifiedNames = + when (val approach = model.typeReplacementApproach) { + TypeReplacementApproach.DoNotReplace -> emptyList() + is TypeReplacementApproach.ReplaceIfPossible -> + process.getSpringBeanQualifiedNames( + buildDirs + classpathList, + approach.config, + model.useSpringAnalyzer + ).also { logger.info { "Detected Spring Beans: $it" } } + } + + SpringApplicationContext( + mockFrameworkInstalled, + staticMockingConfigured, + beanQualifiedNames, + shouldUseImplementors = beanQualifiedNames.isNotEmpty(), + ) + } + else -> ApplicationContext(mockFrameworkInstalled, staticMockingConfigured) + } process.createTestGenerator( buildDirs, classpath, @@ -409,16 +423,23 @@ object UtTestsDialogProcessor { } } - private fun findPaths(srcClasses: Set): BuildPaths? { + private fun findPaths(srcClasses: Set, springConfigPsiClass: PsiClass?): BuildPaths? { val srcModule = findSrcModule(srcClasses) + val springConfigModule = springConfigPsiClass?.let { it.module ?: error("Module for spring configuration class not found") } - val buildDirs = CompilerPaths.getOutputPaths(arrayOf(srcModule)) + val buildDirs = CompilerPaths.getOutputPaths(setOfNotNull( + srcModule, springConfigModule + ).toTypedArray()) .toList() .filter { Paths.get(it).exists() } .nullize() ?: return null val pathsList = OrderEnumerator.orderEntries(srcModule).recursively().pathsList + springConfigModule?.takeIf { it != srcModule }?.let { module -> + pathsList.addAll(OrderEnumerator.orderEntries(module).recursively().pathsList.pathList) + } + val (classpath, classpathList) = if (IntelliJApiHelper.isAndroidStudio()) { // Filter out manifests from classpath. val filterPredicate = { it: String -> @@ -442,6 +463,6 @@ object UtTestsDialogProcessor { val classpath: String, val classpathList: List, val pluginJarsPath: List - // ^ TODO: Now we collect ALL dependent libs and pass them to the instrumented process. Most of them are redundant. + // ^ TODO: Now we collect ALL dependent libs and pass them to the instrumented and spring analyzer processes. Most of them are redundant. ) } \ No newline at end of file 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 ca30e87370..211c7174a8 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 @@ -56,6 +56,9 @@ class GenerateTestsModel( lateinit var typeReplacementApproach: TypeReplacementApproach + // TODO remove once issue with spring-analyzer expecting .properties and .xml file paths is resolved + var useSpringAnalyzer = false + val conflictTriggers: ConflictTriggers = ConflictTriggers() var runGeneratedTestsWithCoverage : Boolean = false 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 6ce0c11c32..ca27e4bd85 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 @@ -19,9 +19,8 @@ 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.services.JdkInfo -import org.utbot.framework.plugin.services.JdkInfoService import org.utbot.framework.plugin.services.WorkingDirService -import org.utbot.framework.process.OpenModulesContainer +import org.utbot.framework.process.AbstractRDProcessCompanion import org.utbot.framework.process.generated.* import org.utbot.framework.process.generated.MethodDescription import org.utbot.framework.util.Conflict @@ -49,90 +48,67 @@ import kotlin.io.path.pathString import kotlin.reflect.KProperty1 import kotlin.reflect.full.memberProperties -private val engineProcessLogConfigurationsDirectory = utBotTempDirectory.toFile().resolve("rdEngineProcessLogConfigurations") -private val logger = KotlinLogging.logger {} -private val engineProcessLogDirectory = utBotTempDirectory.toFile().resolve("rdEngineProcessLogs") +private val engineProcessLogConfigurationsDirectory = utBotTempDirectory.toFile().resolve("rdEngineProcessLogConfigurations").also { it.mkdirs() } +private val logger = KotlinLogging.logger {}.also { overrideDefaultRdLoggerFactoryWithKLogger(it) } +private val engineProcessLogDirectory = utBotTempDirectory.toFile().resolve("rdEngineProcessLogs").also { it.mkdirs() } private const val configurationFileDeleteKey = "delete_this_comment_key" private const val deleteOpenComment = "" -data class RdTestGenerationResult(val notEmptyCases: Int, val testSetsId: Long) - -class EngineProcessInstantDeathException : - InstantProcessDeathException(UtSettings.engineProcessDebugPort, UtSettings.runEngineProcessWithDebug) - -class EngineProcess private constructor(val project: Project, private val classNameToPath: Map, rdProcess: ProcessWithRdServer) : - ProcessWithRdServer by rdProcess { - companion object { - private val log4j2ConfigFile: File - - init { - engineProcessLogDirectory.mkdirs() - engineProcessLogConfigurationsDirectory.mkdirs() - overrideDefaultRdLoggerFactoryWithKLogger(logger) - - val customFile = File(UtSettings.engineProcessLogConfigFile) - - if (customFile.exists()) { - log4j2ConfigFile = customFile - } else { - log4j2ConfigFile = Files.createTempFile(engineProcessLogConfigurationsDirectory.toPath(), null, ".xml").toFile() - this.javaClass.classLoader.getResourceAsStream("log4j2.xml")?.use { logConfig -> - val resultConfig = logConfig.readBytes().toString(Charset.defaultCharset()) - .replace(Regex("$deleteOpenComment|$deleteCloseComment"), "") - .replace("ref=\"IdeaAppender\"", "ref=\"EngineProcessAppender\"") - .replace("\${env:UTBOT_LOG_DIR}", engineProcessLogDirectory.canonicalPath.trimEnd(File.separatorChar) + File.separatorChar) - Files.copy( - resultConfig.byteInputStream(), - log4j2ConfigFile.toPath(), - StandardCopyOption.REPLACE_EXISTING - ) - } - } - } +private fun createEngineProcessLog4j2Config(): File { + val customFile = File(UtSettings.engineProcessLogConfigFile) + + val log4j2ConfigFile = + if (customFile.exists()) customFile + else Files.createTempFile(engineProcessLogConfigurationsDirectory.toPath(), null, ".xml").toFile() + + EngineProcess::class.java.classLoader.getResourceAsStream("log4j2.xml")?.use { logConfig -> + val resultConfig = logConfig.readBytes().toString(Charset.defaultCharset()) + .replace(Regex("$deleteOpenComment|$deleteCloseComment"), "") + .replace("ref=\"IdeaAppender\"", "ref=\"EngineProcessAppender\"") + .replace("\${env:UTBOT_LOG_DIR}", engineProcessLogDirectory.canonicalPath.trimEnd(File.separatorChar) + File.separatorChar) + Files.copy( + resultConfig.byteInputStream(), + log4j2ConfigFile.toPath(), + StandardCopyOption.REPLACE_EXISTING + ) + } + return log4j2ConfigFile +} - private val log4j2ConfigSwitch = "-Dlog4j2.configurationFile=${log4j2ConfigFile.canonicalPath}" +private val log4j2ConfigFile: File = createEngineProcessLog4j2Config() - private fun suspendValue(): String = if (UtSettings.suspendEngineProcessExecutionInDebugMode) "y" else "n" +private val log4j2ConfigSwitch = "-Dlog4j2.configurationFile=${log4j2ConfigFile.canonicalPath}" - private val debugArgument: String? - get() = "-agentlib:jdwp=transport=dt_socket,server=n,suspend=${suspendValue()},quiet=y,address=${UtSettings.engineProcessDebugPort}" - .takeIf { UtSettings.runEngineProcessWithDebug } +private val pluginClasspath: String + get() = (EngineProcess::class.java.classLoader as PluginClassLoader).classPath.baseUrls.joinToString( + separator = File.pathSeparator, + prefix = "\"", + postfix = "\"" + ) - private val javaExecutablePathString: Path - get() = JdkInfoService.jdkInfoProvider.info.path.resolve("bin${File.separatorChar}${osSpecificJavaExecutable()}") +private const val startFileName = "org.utbot.framework.process.EngineProcessMainKt" - private val pluginClasspath: String - get() = (this.javaClass.classLoader as PluginClassLoader).classPath.baseUrls.joinToString( - separator = File.pathSeparator, - prefix = "\"", - postfix = "\"" - ) - - private const val startFileName = "org.utbot.framework.process.EngineProcessMainKt" +data class RdTestGenerationResult(val notEmptyCases: Int, val testSetsId: Long) - private fun obtainEngineProcessCommandLine(port: Int) = buildList { - add(javaExecutablePathString.pathString) - add("-ea") - val javaVersionSpecificArgs = OpenModulesContainer.javaVersionSpecificArguments - if (javaVersionSpecificArgs.isNotEmpty()) { - addAll(javaVersionSpecificArgs) - } - debugArgument?.let { add(it) } - add(log4j2ConfigSwitch) - add("-cp") - add(pluginClasspath) - add(startFileName) - add(rdPortArgument(port)) - } +class EngineProcessInstantDeathException : + InstantProcessDeathException(UtSettings.engineProcessDebugPort, UtSettings.runEngineProcessWithDebug) +class EngineProcess private constructor(val project: Project, private val classNameToPath: Map, rdProcess: ProcessWithRdServer) : + ProcessWithRdServer by rdProcess { + companion object : AbstractRDProcessCompanion( + debugPort = UtSettings.engineProcessDebugPort, + runWithDebug = UtSettings.runEngineProcessWithDebug, + suspendExecutionInDebugMode = UtSettings.suspendEngineProcessExecutionInDebugMode, + processSpecificCommandLineArgs = { listOf("-ea", log4j2ConfigSwitch, "-cp", pluginClasspath, startFileName) } + ) { fun createBlocking(project: Project, classNameToPath: Map): EngineProcess = runBlocking { EngineProcess(project, classNameToPath) } suspend operator fun invoke(project: Project, classNameToPath: Map): EngineProcess = LifetimeDefinition().terminateOnException { lifetime -> val rdProcess = startUtProcessWithRdServer(lifetime) { port -> - val cmd = obtainEngineProcessCommandLine(port) + val cmd = obtainProcessCommandLine(port) val directory = WorkingDirService.provide().toFile() val builder = ProcessBuilder(cmd).directory(directory) val process = builder.start() @@ -167,6 +143,13 @@ class EngineProcess private constructor(val project: Project, private val classN engineModel.setupUtContext.startBlocking(SetupContextParams(classpathForUrlsClassloader)) } + fun getSpringBeanQualifiedNames(classpathList: List, config: String, useSpringAnalyzer: Boolean): List { + assertReadAccessNotAllowed() + return engineModel.getSpringBeanQualifiedNames.startBlocking( + GetSpringBeanQualifiedNamesParams(classpathList.toTypedArray(), config, useSpringAnalyzer) + ).toList() + } + private fun computeSourceFileByClass(params: ComputeSourceFileByClassArguments): String = DumbService.getInstance(project).runReadActionInSmartMode { val scope = GlobalSearchScope.allScope(project) diff --git a/utbot-rd/build.gradle b/utbot-rd/build.gradle index be70e3e35e..549a850bbc 100644 --- a/utbot-rd/build.gradle +++ b/utbot-rd/build.gradle @@ -257,6 +257,31 @@ task generateCommonModels(type: RdGenTask) { } } +task generateSpringModels(type: RdGenTask) { + def currentProjectDir = project.projectDir + def ideaPluginProjectDir = project.rootProject.childProjects["utbot-spring-analyzer"].projectDir + def generatedOutputDir = new File(ideaPluginProjectDir, "src/main/kotlin/org/utbot/spring/generated") + def hashDir = generatedOutputDir + def sourcesDir = new File(currentProjectDir, "src/main/rdgen/org/utbot/rd/models") + def rdParams = extensions.getByName("params") as RdGenExtension + + group = "rdgen" + rdParams.verbose = true + rdParams.sources(sourcesDir) + rdParams.hashFolder = hashDir.canonicalPath + // where to search roots + rdParams.packages = "org.utbot.rd.models" + + rdParams.generator { + language = "kotlin" + transform = "symmetric" + root = "org.utbot.rd.models.SpringAnalyzerRoot" + + directory = generatedOutputDir.canonicalPath + namespace = "org.utbot.spring.generated" + } +} + task generateCSharpModels(type: RdGenTask) { def currentProjectDir = project.projectDir def riderPluginProjectDir = project.rootProject.projectDir.toPath().resolve("utbot-rider").toFile() diff --git a/utbot-rd/src/main/kotlin/org/utbot/rd/ClientProcessUtil.kt b/utbot-rd/src/main/kotlin/org/utbot/rd/ClientProcessUtil.kt index a9b3d23bc0..133437c11c 100644 --- a/utbot-rd/src/main/kotlin/org/utbot/rd/ClientProcessUtil.kt +++ b/utbot-rd/src/main/kotlin/org/utbot/rd/ClientProcessUtil.kt @@ -21,6 +21,8 @@ import org.utbot.common.* import org.utbot.rd.generated.synchronizationModel import org.utbot.rd.loggers.withLevel import java.io.File +import java.io.OutputStream +import java.io.PrintStream import kotlin.time.Duration const val rdProcessDirName = "rdProcessSync" @@ -146,7 +148,28 @@ class IdleWatchdog(private val ldef: LifetimeDefinition, val timeout: Duration) class ClientProtocolBuilder { private var timeout = Duration.INFINITE - suspend fun start(port: Int, parent: Lifetime? = null, block: Protocol.(IdleWatchdog) -> Unit) { + private fun silentlyCloseStandardStreams() { + // we should change out/err streams as not to spend time on user output + // and also because rd default logging system writes some initial values to stdout, polluting it as well + val tmpStream = PrintStream(object : OutputStream() { + override fun write(b: Int) {} + }) + val prevOut = System.out + val prevError = System.err + System.setOut(tmpStream) + System.setErr(tmpStream) + // stdin/stderr should be closed as not to leave hanging descriptors + // and we cannot log any exceptions here as rd remote logging is still not configured + // so we pass any exceptions + silent { prevOut.close() } + silent { prevError.close() } + } + + suspend fun start(args: Array, parent: Lifetime? = null, block: Protocol.(IdleWatchdog) -> Unit) { + silentlyCloseStandardStreams() + + val port = findRdPort(args) + UtRdCoroutineScope.initialize() val pid = currentProcessPid.toInt() val ldef = parent?.createNested() ?: LifetimeDefinition() diff --git a/utbot-rd/src/main/kotlin/org/utbot/rd/generated/LoggerModel.Generated.kt b/utbot-rd/src/main/kotlin/org/utbot/rd/generated/LoggerModel.Generated.kt index 11d81e7768..f5a45841b3 100644 --- a/utbot-rd/src/main/kotlin/org/utbot/rd/generated/LoggerModel.Generated.kt +++ b/utbot-rd/src/main/kotlin/org/utbot/rd/generated/LoggerModel.Generated.kt @@ -18,6 +18,7 @@ import kotlin.jvm.JvmStatic * #### Generated from [LoggerModel.kt:8] */ class LoggerModel private constructor( + private val _initRemoteLogging: RdSignal, private val _log: RdSignal, private val _getCategoryMinimalLogLevel: RdCall ) : RdExtBase() { @@ -47,13 +48,14 @@ class LoggerModel private constructor( } - const val serializationHash = -6259198217478203203L + const val serializationHash = 1686273842005935878L } override val serializersOwner: ISerializersOwner get() = LoggerModel override val serializationHash: Long get() = LoggerModel.serializationHash //fields + val initRemoteLogging: IAsyncSignal get() = _initRemoteLogging val log: IAsyncSignal get() = _log /** @@ -64,11 +66,13 @@ class LoggerModel private constructor( //methods //initializer init { + _initRemoteLogging.async = true _log.async = true _getCategoryMinimalLogLevel.async = true } init { + bindableChildren.add("initRemoteLogging" to _initRemoteLogging) bindableChildren.add("log" to _log) bindableChildren.add("getCategoryMinimalLogLevel" to _getCategoryMinimalLogLevel) } @@ -76,6 +80,7 @@ class LoggerModel private constructor( //secondary constructor private constructor( ) : this( + RdSignal(FrameworkMarshallers.Void), RdSignal(LogArguments), RdCall(FrameworkMarshallers.String, FrameworkMarshallers.Int) ) @@ -86,6 +91,7 @@ class LoggerModel private constructor( override fun print(printer: PrettyPrinter) { printer.println("LoggerModel (") printer.indent { + print("initRemoteLogging = "); _initRemoteLogging.print(printer); println() print("log = "); _log.print(printer); println() print("getCategoryMinimalLogLevel = "); _getCategoryMinimalLogLevel.print(printer); println() } @@ -94,6 +100,7 @@ class LoggerModel private constructor( //deepClone override fun deepClone(): LoggerModel { return LoggerModel( + _initRemoteLogging.deepClonePolymorphic(), _log.deepClonePolymorphic(), _getCategoryMinimalLogLevel.deepClonePolymorphic() ) diff --git a/utbot-rd/src/main/kotlin/org/utbot/rd/generated/SynchronizationModel.Generated.kt b/utbot-rd/src/main/kotlin/org/utbot/rd/generated/SynchronizationModel.Generated.kt index 503f6dd889..06c65bcc62 100644 --- a/utbot-rd/src/main/kotlin/org/utbot/rd/generated/SynchronizationModel.Generated.kt +++ b/utbot-rd/src/main/kotlin/org/utbot/rd/generated/SynchronizationModel.Generated.kt @@ -19,7 +19,6 @@ import kotlin.jvm.JvmStatic */ class SynchronizationModel private constructor( private val _suspendTimeoutTimer: RdCall, - private val _initRemoteLogging: RdSignal, private val _synchronizationSignal: RdSignal, private val _stopProcess: RdSignal ) : RdExtBase() { @@ -48,7 +47,7 @@ class SynchronizationModel private constructor( } - const val serializationHash = 5881306106692642003L + const val serializationHash = -8851121542976813112L } override val serializersOwner: ISerializersOwner get() = SynchronizationModel @@ -56,7 +55,6 @@ class SynchronizationModel private constructor( //fields val suspendTimeoutTimer: RdCall get() = _suspendTimeoutTimer - val initRemoteLogging: IAsyncSignal get() = _initRemoteLogging val synchronizationSignal: IAsyncSignal get() = _synchronizationSignal /** @@ -67,14 +65,12 @@ class SynchronizationModel private constructor( //initializer init { _suspendTimeoutTimer.async = true - _initRemoteLogging.async = true _synchronizationSignal.async = true _stopProcess.async = true } init { bindableChildren.add("suspendTimeoutTimer" to _suspendTimeoutTimer) - bindableChildren.add("initRemoteLogging" to _initRemoteLogging) bindableChildren.add("synchronizationSignal" to _synchronizationSignal) bindableChildren.add("stopProcess" to _stopProcess) } @@ -83,7 +79,6 @@ class SynchronizationModel private constructor( private constructor( ) : this( RdCall(FrameworkMarshallers.Bool, FrameworkMarshallers.Void), - RdSignal(FrameworkMarshallers.Void), RdSignal(FrameworkMarshallers.String), RdSignal(FrameworkMarshallers.Void) ) @@ -95,7 +90,6 @@ class SynchronizationModel private constructor( printer.println("SynchronizationModel (") printer.indent { print("suspendTimeoutTimer = "); _suspendTimeoutTimer.print(printer); println() - print("initRemoteLogging = "); _initRemoteLogging.print(printer); println() print("synchronizationSignal = "); _synchronizationSignal.print(printer); println() print("stopProcess = "); _stopProcess.print(printer); println() } @@ -105,7 +99,6 @@ class SynchronizationModel private constructor( override fun deepClone(): SynchronizationModel { return SynchronizationModel( _suspendTimeoutTimer.deepClonePolymorphic(), - _initRemoteLogging.deepClonePolymorphic(), _synchronizationSignal.deepClonePolymorphic(), _stopProcess.deepClonePolymorphic() ) diff --git a/utbot-rd/src/main/kotlin/org/utbot/rd/loggers/UtRdConsoleLogger.kt b/utbot-rd/src/main/kotlin/org/utbot/rd/loggers/UtRdConsoleLogger.kt index b696b5f55c..b1e5dfd01e 100644 --- a/utbot-rd/src/main/kotlin/org/utbot/rd/loggers/UtRdConsoleLogger.kt +++ b/utbot-rd/src/main/kotlin/org/utbot/rd/loggers/UtRdConsoleLogger.kt @@ -15,7 +15,7 @@ class UtRdConsoleLogger( } private fun format(category: String, level: LogLevel, message: Any?, throwable: Throwable?) : String { - val throwableToPrint = if (level < LogLevel.Error) throwable else throwable ?: Exception() //to print stacktrace + val throwableToPrint = if (level < LogLevel.Error) throwable else throwable ?: Exception("No exception was actually thrown, this exception is used purely to log trace") val rdCategory = if (category.isNotEmpty()) "RdCategory: ${category.substringAfterLast('.').padEnd(25)} | " else "" return "${LocalDateTime.now().format(timeFormatter)} | ${level.toString().uppercase().padEnd(5)} | $rdCategory${message?.toString()?:""} ${throwableToPrint?.getThrowableText()?.let { "| $it" }?:""}" } diff --git a/utbot-rd/src/main/kotlin/org/utbot/rd/loggers/UtRdKLogger.kt b/utbot-rd/src/main/kotlin/org/utbot/rd/loggers/UtRdKLogger.kt index b706757690..f1bd3f9349 100644 --- a/utbot-rd/src/main/kotlin/org/utbot/rd/loggers/UtRdKLogger.kt +++ b/utbot-rd/src/main/kotlin/org/utbot/rd/loggers/UtRdKLogger.kt @@ -24,7 +24,7 @@ class UtRdKLogger(private val realLogger: KLogger, val category: String) : Logge } private fun format(level: LogLevel, message: Any?, throwable: Throwable?): String { - val throwableToPrint = if (level < LogLevel.Error) throwable else throwable ?: Exception() //to print stacktrace + val throwableToPrint = if (level < LogLevel.Error) throwable else throwable ?: Exception("No exception was actually thrown, this exception is used purely to log trace") val rdCategory = if (category.isNotEmpty()) "RdCategory: ${category.substringAfterLast('.').padEnd(25)} | " else "" return "$rdCategory${message?.toString() ?: ""} ${throwableToPrint?.getThrowableText()?.let { "| $it" } ?: ""}" } diff --git a/utbot-rd/src/main/kotlin/org/utbot/rd/loggers/UtRdLogUtil.kt b/utbot-rd/src/main/kotlin/org/utbot/rd/loggers/UtRdLogUtil.kt index 9aae485486..4ed40d3f2f 100644 --- a/utbot-rd/src/main/kotlin/org/utbot/rd/loggers/UtRdLogUtil.kt +++ b/utbot-rd/src/main/kotlin/org/utbot/rd/loggers/UtRdLogUtil.kt @@ -4,7 +4,7 @@ import com.jetbrains.rd.util.* import com.jetbrains.rd.util.lifetime.Lifetime import mu.KLogger import org.utbot.common.LoggerWithLogMethod - +import org.utbot.rd.generated.LoggerModel fun Logger.withLevel(logLevel: LogLevel): LoggerWithLogMethod = LoggerWithLogMethod { this.log(logLevel, it) @@ -24,4 +24,21 @@ fun overrideDefaultRdLoggerFactoryWithKLogger(logger: KLogger) { if (Statics().get() !is UtRdKLoggerFactory) { Logger.set(Lifetime.Eternal, UtRdKLoggerFactory(logger)) } -} \ No newline at end of file +} + +fun LoggerModel.setup(rdLogger: UtRdKLogger, processLifetime: Lifetime) { + // currently we do not specify log level for different categories + // though it is possible with some additional map on categories -> consider performance + getCategoryMinimalLogLevel.set { _ -> + // this logLevel is obtained from KotlinLogger + rdLogger.logLevel.ordinal + } + + log.advise(processLifetime) { + val logLevel = UtRdRemoteLogger.logLevelValues[it.logLevelOrdinal] + // assume throwable already in message + rdLogger.log(logLevel, it.message, null) + } + + initRemoteLogging.fire(Unit) +} 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 bbfe08a25a..b94b75d16c 100644 --- a/utbot-rd/src/main/rdgen/org/utbot/rd/models/EngineProcessModel.kt +++ b/utbot-rd/src/main/rdgen/org/utbot/rd/models/EngineProcessModel.kt @@ -84,6 +84,11 @@ object EngineProcessModel : Ext(EngineProcessRoot) { val setupContextParams = structdef { field("classpathForUrlsClassloader", immutableList(PredefinedType.string)) } + val getSpringBeanQualifiedNamesParams = structdef { + field("classpath", array(PredefinedType.string)) + field("config", PredefinedType.string) + field("useSpringAnalyzer", PredefinedType.bool) + } val methodDescription = structdef { field("name", PredefinedType.string) field("containingClass", PredefinedType.string.nullable) @@ -124,6 +129,7 @@ object EngineProcessModel : Ext(EngineProcessRoot) { } init { call("setupUtContext", setupContextParams, PredefinedType.void).async + call("getSpringBeanQualifiedNames", getSpringBeanQualifiedNamesParams, array(PredefinedType.string)).async call("createTestGenerator", testGeneratorParams, PredefinedType.void).async call("isCancelled", PredefinedType.void, PredefinedType.bool).async call("generate", generateParams, generateResult).async diff --git a/utbot-rd/src/main/rdgen/org/utbot/rd/models/LoggerModel.kt b/utbot-rd/src/main/rdgen/org/utbot/rd/models/LoggerModel.kt index 7ca4d8e6b7..c3f2e93e4f 100644 --- a/utbot-rd/src/main/rdgen/org/utbot/rd/models/LoggerModel.kt +++ b/utbot-rd/src/main/rdgen/org/utbot/rd/models/LoggerModel.kt @@ -13,6 +13,7 @@ object LoggerModel : Ext(LoggerRoot) { } init { + signal("initRemoteLogging", PredefinedType.void).async signal("log", logArguments).async call( "getCategoryMinimalLogLevel", 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 new file mode 100644 index 0000000000..4a6dc98a64 --- /dev/null +++ b/utbot-rd/src/main/rdgen/org/utbot/rd/models/SpringAnalyzerModel.kt @@ -0,0 +1,24 @@ +@file:Suppress("unused") +package org.utbot.rd.models + +import com.jetbrains.rd.generator.nova.* + +object SpringAnalyzerRoot : Root() + +object SpringAnalyzerProcessModel : Ext(SpringAnalyzerRoot) { + val springAnalyzerParams = structdef { + field("classpath", array(PredefinedType.string)) + field("configuration", PredefinedType.string) + field("propertyFilesPaths", array(PredefinedType.string)) + field("xmlConfigurationPaths", array(PredefinedType.string)) + field("useSpringAnalyzer", PredefinedType.bool) + } + + val springAnalyzerResult = structdef { + field("beanTypes", array(PredefinedType.string)) + } + + init { + call("analyze", springAnalyzerParams, springAnalyzerResult).async + } +} diff --git a/utbot-rd/src/main/rdgen/org/utbot/rd/models/SynchronizationModel.kt b/utbot-rd/src/main/rdgen/org/utbot/rd/models/SynchronizationModel.kt index 077173ba23..f0d86d23fa 100644 --- a/utbot-rd/src/main/rdgen/org/utbot/rd/models/SynchronizationModel.kt +++ b/utbot-rd/src/main/rdgen/org/utbot/rd/models/SynchronizationModel.kt @@ -8,7 +8,6 @@ object SynchronizationRoot: Root() object SynchronizationModel: Ext(SynchronizationRoot) { init { call("suspendTimeoutTimer", PredefinedType.bool, PredefinedType.void).async - signal("initRemoteLogging", PredefinedType.void).async signal("synchronizationSignal", PredefinedType.string).async signal("StopProcess", PredefinedType.void).apply { async diff --git a/utbot-spring-analyzer/build.gradle.kts b/utbot-spring-analyzer/build.gradle.kts index 2d085bfa62..8327393282 100644 --- a/utbot-spring-analyzer/build.gradle.kts +++ b/utbot-spring-analyzer/build.gradle.kts @@ -1,30 +1,43 @@ -import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCacheFileTransformer import com.github.jengelman.gradle.plugins.shadow.transformers.PropertiesFileTransformer +val springBootVersion: String by rootProject +val rdVersion: String by rootProject +val commonsLoggingVersion: String by rootProject +val kotlinLoggingVersion: String by rootProject +val commonsIOVersion: String by rootProject + plugins { - id("org.springframework.boot") version "2.7.8" - id("io.spring.dependency-management") version "1.1.0" id("com.github.johnrengelman.shadow") version "7.1.2" id("java") application } java { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 } dependencies { - implementation("org.springframework.boot:spring-boot-starter") - implementation("org.springframework.boot:spring-boot-starter-web") + // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot + implementation("org.springframework.boot:spring-boot:$springBootVersion") + + implementation(project(":utbot-rd")) + implementation(project(":utbot-core")) + implementation(project(":utbot-framework-api")) + implementation("com.jetbrains.rd:rd-framework:$rdVersion") + implementation("com.jetbrains.rd:rd-core:$rdVersion") + implementation("commons-logging:commons-logging:$commonsLoggingVersion") + implementation("io.github.microutils:kotlin-logging:$kotlinLoggingVersion") + implementation("commons-io:commons-io:$commonsIOVersion") } application { - mainClass.set("org.utbot.spring.ApplicationRunnerKt") + mainClass.set("org.utbot.spring.process.SpringAnalyzerProcessMainKt") } // see more details about this task -- https://github.com/spring-projects/spring-boot/issues/1828 -tasks.withType(ShadowJar::class.java) { +tasks.shadowJar { isZip64 = true // Required for Spring mergeServiceFiles() @@ -35,4 +48,16 @@ tasks.withType(ShadowJar::class.java) { paths = listOf("META-INF/spring.factories") mergeStrategy = "append" }) + + transform(Log4j2PluginsCacheFileTransformer::class.java) + archiveFileName.set("utbot-spring-analyzer-shadow.jar") +} + +val springAnalyzerJar: Configuration by configurations.creating { + isCanBeResolved = false + isCanBeConsumed = true +} + +artifacts { + add(springAnalyzerJar.name, tasks.shadowJar) } diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzers/SpringApplicationAnalyzer.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzers/SpringApplicationAnalyzer.kt index fd75d46171..1ee6143d3f 100644 --- a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzers/SpringApplicationAnalyzer.kt +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzers/SpringApplicationAnalyzer.kt @@ -8,13 +8,14 @@ import org.utbot.spring.configurators.ApplicationConfigurationType.XmlConfigurat import org.utbot.spring.configurators.ApplicationConfigurator import org.utbot.spring.data.ApplicationData import org.utbot.spring.utils.FakeFileManager +import org.utbot.spring.postProcessors.UtBotBeanFactoryPostProcessor import java.io.File class SpringApplicationAnalyzer( private val applicationData: ApplicationData ) { - fun analyze() { + fun analyze(): List { val fakeFileManager = FakeFileManager(applicationData.propertyFilesPaths + applicationData.xmlConfigurationPaths) fakeFileManager.createTempFiles() @@ -38,6 +39,7 @@ class SpringApplicationAnalyzer( } finally { fakeFileManager.deleteTempFiles() } + return UtBotBeanFactoryPostProcessor.beanQualifiedNames } private fun findConfigurationType(applicationData: ApplicationData): ApplicationConfigurationType { diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/config/TestApplicationConfiguration.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/config/TestApplicationConfiguration.kt index 3b4d255553..b783fdc3b0 100644 --- a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/config/TestApplicationConfiguration.kt +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/config/TestApplicationConfiguration.kt @@ -11,7 +11,6 @@ import org.utbot.spring.postProcessors.UtBotBeanFactoryPostProcessor open class TestApplicationConfiguration { @Bean - open fun utBotBeanFactoryPostProcessor(): BeanFactoryPostProcessor { - return UtBotBeanFactoryPostProcessor() - } + open fun utBotBeanFactoryPostProcessor(): BeanFactoryPostProcessor = + UtBotBeanFactoryPostProcessor } \ No newline at end of file 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 new file mode 100644 index 0000000000..1274cfe070 --- /dev/null +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/generated/SpringAnalyzerProcessModel.Generated.kt @@ -0,0 +1,231 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE","EXPERIMENTAL_UNSIGNED_LITERALS","PackageDirectoryMismatch","UnusedImport","unused","LocalVariableName","CanBeVal","PropertyName","EnumEntryName","ClassName","ObjectPropertyName","UnnecessaryVariable","SpellCheckingInspection") +package org.utbot.spring.generated + +import com.jetbrains.rd.framework.* +import com.jetbrains.rd.framework.base.* +import com.jetbrains.rd.framework.impl.* + +import com.jetbrains.rd.util.lifetime.* +import com.jetbrains.rd.util.reactive.* +import com.jetbrains.rd.util.string.* +import com.jetbrains.rd.util.* +import kotlin.reflect.KClass +import kotlin.jvm.JvmStatic + + + +/** + * #### Generated from [SpringAnalyzerModel.kt:8] + */ +class SpringAnalyzerProcessModel private constructor( + private val _analyze: RdCall +) : RdExtBase() { + //companion + + companion object : ISerializersOwner { + + override fun registerSerializersCore(serializers: ISerializers) { + serializers.register(SpringAnalyzerParams) + serializers.register(SpringAnalyzerResult) + } + + + @JvmStatic + @JvmName("internalCreateModel") + @Deprecated("Use create instead", ReplaceWith("create(lifetime, protocol)")) + internal fun createModel(lifetime: Lifetime, protocol: IProtocol): SpringAnalyzerProcessModel { + @Suppress("DEPRECATION") + return create(lifetime, protocol) + } + + @JvmStatic + @Deprecated("Use protocol.springAnalyzerProcessModel or revise the extension scope instead", ReplaceWith("protocol.springAnalyzerProcessModel")) + fun create(lifetime: Lifetime, protocol: IProtocol): SpringAnalyzerProcessModel { + SpringAnalyzerRoot.register(protocol.serializers) + + return SpringAnalyzerProcessModel() + } + + + const val serializationHash = 528909252282504878L + + } + override val serializersOwner: ISerializersOwner get() = SpringAnalyzerProcessModel + override val serializationHash: Long get() = SpringAnalyzerProcessModel.serializationHash + + //fields + val analyze: RdCall get() = _analyze + //methods + //initializer + init { + _analyze.async = true + } + + init { + bindableChildren.add("analyze" to _analyze) + } + + //secondary constructor + private constructor( + ) : this( + RdCall(SpringAnalyzerParams, SpringAnalyzerResult) + ) + + //equals trait + //hash code trait + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("SpringAnalyzerProcessModel (") + printer.indent { + print("analyze = "); _analyze.print(printer); println() + } + printer.print(")") + } + //deepClone + override fun deepClone(): SpringAnalyzerProcessModel { + return SpringAnalyzerProcessModel( + _analyze.deepClonePolymorphic() + ) + } + //contexts +} +val IProtocol.springAnalyzerProcessModel get() = getOrCreateExtension(SpringAnalyzerProcessModel::class) { @Suppress("DEPRECATION") SpringAnalyzerProcessModel.create(lifetime, this) } + + + +/** + * #### Generated from [SpringAnalyzerModel.kt:9] + */ +data class SpringAnalyzerParams ( + val classpath: Array, + val configuration: String, + val propertyFilesPaths: Array, + val xmlConfigurationPaths: Array, + val useSpringAnalyzer: Boolean +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = SpringAnalyzerParams::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): SpringAnalyzerParams { + val classpath = buffer.readArray {buffer.readString()} + val configuration = buffer.readString() + val propertyFilesPaths = buffer.readArray {buffer.readString()} + val xmlConfigurationPaths = buffer.readArray {buffer.readString()} + val useSpringAnalyzer = buffer.readBool() + return SpringAnalyzerParams(classpath, configuration, propertyFilesPaths, xmlConfigurationPaths, useSpringAnalyzer) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: SpringAnalyzerParams) { + buffer.writeArray(value.classpath) { buffer.writeString(it) } + buffer.writeString(value.configuration) + buffer.writeArray(value.propertyFilesPaths) { buffer.writeString(it) } + buffer.writeArray(value.xmlConfigurationPaths) { buffer.writeString(it) } + buffer.writeBool(value.useSpringAnalyzer) + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as SpringAnalyzerParams + + if (!(classpath contentDeepEquals other.classpath)) return false + if (configuration != other.configuration) return false + if (!(propertyFilesPaths contentDeepEquals other.propertyFilesPaths)) return false + if (!(xmlConfigurationPaths contentDeepEquals other.xmlConfigurationPaths)) return false + if (useSpringAnalyzer != other.useSpringAnalyzer) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + classpath.contentDeepHashCode() + __r = __r*31 + configuration.hashCode() + __r = __r*31 + propertyFilesPaths.contentDeepHashCode() + __r = __r*31 + xmlConfigurationPaths.contentDeepHashCode() + __r = __r*31 + useSpringAnalyzer.hashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("SpringAnalyzerParams (") + printer.indent { + print("classpath = "); classpath.print(printer); println() + print("configuration = "); configuration.print(printer); println() + print("propertyFilesPaths = "); propertyFilesPaths.print(printer); println() + print("xmlConfigurationPaths = "); xmlConfigurationPaths.print(printer); println() + print("useSpringAnalyzer = "); useSpringAnalyzer.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + +/** + * #### Generated from [SpringAnalyzerModel.kt:17] + */ +data class SpringAnalyzerResult ( + val beanTypes: Array +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = SpringAnalyzerResult::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): SpringAnalyzerResult { + val beanTypes = buffer.readArray {buffer.readString()} + return SpringAnalyzerResult(beanTypes) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: SpringAnalyzerResult) { + buffer.writeArray(value.beanTypes) { buffer.writeString(it) } + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as SpringAnalyzerResult + + if (!(beanTypes contentDeepEquals other.beanTypes)) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + beanTypes.contentDeepHashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("SpringAnalyzerResult (") + printer.indent { + print("beanTypes = "); beanTypes.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/generated/SpringAnalyzerRoot.Generated.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/generated/SpringAnalyzerRoot.Generated.kt new file mode 100644 index 0000000000..2ad0d56bac --- /dev/null +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/generated/SpringAnalyzerRoot.Generated.kt @@ -0,0 +1,58 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE","EXPERIMENTAL_UNSIGNED_LITERALS","PackageDirectoryMismatch","UnusedImport","unused","LocalVariableName","CanBeVal","PropertyName","EnumEntryName","ClassName","ObjectPropertyName","UnnecessaryVariable","SpellCheckingInspection") +package org.utbot.spring.generated + +import com.jetbrains.rd.framework.* +import com.jetbrains.rd.framework.base.* +import com.jetbrains.rd.framework.impl.* + +import com.jetbrains.rd.util.lifetime.* +import com.jetbrains.rd.util.reactive.* +import com.jetbrains.rd.util.string.* +import com.jetbrains.rd.util.* +import kotlin.reflect.KClass +import kotlin.jvm.JvmStatic + + + +/** + * #### Generated from [SpringAnalyzerModel.kt:6] + */ +class SpringAnalyzerRoot private constructor( +) : RdExtBase() { + //companion + + companion object : ISerializersOwner { + + override fun registerSerializersCore(serializers: ISerializers) { + SpringAnalyzerRoot.register(serializers) + SpringAnalyzerProcessModel.register(serializers) + } + + + + + + const val serializationHash = -4315357569975275049L + + } + override val serializersOwner: ISerializersOwner get() = SpringAnalyzerRoot + override val serializationHash: Long get() = SpringAnalyzerRoot.serializationHash + + //fields + //methods + //initializer + //secondary constructor + //equals trait + //hash code trait + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("SpringAnalyzerRoot (") + printer.print(")") + } + //deepClone + override fun deepClone(): SpringAnalyzerRoot { + return SpringAnalyzerRoot( + ) + } + //contexts +} diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/loggers/RDApacheCommonsLogFactory.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/loggers/RDApacheCommonsLogFactory.kt new file mode 100644 index 0000000000..9fd5f67c9c --- /dev/null +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/loggers/RDApacheCommonsLogFactory.kt @@ -0,0 +1,34 @@ +package org.utbot.spring.loggers + +import com.jetbrains.rd.util.LogLevel +import com.jetbrains.rd.util.getLogger +import org.apache.commons.logging.Log +import org.apache.commons.logging.impl.LogFactoryImpl + +@Suppress("unused") // used via -Dorg.apache.commons.logging.LogFactory=org.utbot.spring.loggers.RDApacheCommonsLogFactory +class RDApacheCommonsLogFactory : LogFactoryImpl() { + override fun getInstance(name: String): Log { + val logger = getLogger(category = name) + return object : Log { + override fun trace(message: Any?) = logger.log(LogLevel.Trace, message, throwable = null) + override fun trace(message: Any?, t: Throwable?) = logger.log(LogLevel.Trace, message, throwable = t) + override fun debug(message: Any?) = logger.log(LogLevel.Debug, message, throwable = null) + override fun debug(message: Any?, t: Throwable?) = logger.log(LogLevel.Debug, message, throwable = t) + override fun info(message: Any?) = logger.log(LogLevel.Info, message, throwable = null) + override fun info(message: Any?, t: Throwable?) = logger.log(LogLevel.Info, message, throwable = t) + override fun warn(message: Any?) = logger.log(LogLevel.Warn, message, throwable = null) + override fun warn(message: Any?, t: Throwable?) = logger.log(LogLevel.Warn, message, throwable = t) + override fun error(message: Any?) = logger.log(LogLevel.Error, message, throwable = null) + override fun error(message: Any?, t: Throwable?) = logger.log(LogLevel.Error, message, throwable = t) + override fun fatal(message: Any?) = logger.log(LogLevel.Fatal, message, throwable = null) + override fun fatal(message: Any?, t: Throwable?) = logger.log(LogLevel.Fatal, message, throwable = t) + + override fun isTraceEnabled() = logger.isEnabled(LogLevel.Trace) + override fun isDebugEnabled() = logger.isEnabled(LogLevel.Debug) + override fun isInfoEnabled() = logger.isEnabled(LogLevel.Info) + override fun isErrorEnabled() = logger.isEnabled(LogLevel.Error) + override fun isFatalEnabled() = logger.isEnabled(LogLevel.Fatal) + override fun isWarnEnabled() = logger.isEnabled(LogLevel.Warn) + } + } +} \ No newline at end of file diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/postProcessors/UtBotBeanFactoryPostProcessor.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/postProcessors/UtBotBeanFactoryPostProcessor.kt index 98ffca5f39..b0276dbac2 100644 --- a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/postProcessors/UtBotBeanFactoryPostProcessor.kt +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/postProcessors/UtBotBeanFactoryPostProcessor.kt @@ -6,11 +6,9 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory import org.springframework.beans.factory.support.BeanDefinitionRegistry import org.springframework.core.PriorityOrdered -import java.io.File -import java.io.FileWriter -import java.util.Arrays - -class UtBotBeanFactoryPostProcessor : BeanFactoryPostProcessor, PriorityOrdered { +object UtBotBeanFactoryPostProcessor : BeanFactoryPostProcessor, PriorityOrdered { + var beanQualifiedNames: List = emptyList() + private set /** * Sets the priority of post processor to highest to avoid side effects from others. @@ -21,8 +19,7 @@ class UtBotBeanFactoryPostProcessor : BeanFactoryPostProcessor, PriorityOrdered println("Started post-processing bean factory in UtBot") val beanClassNames = findBeanClassNames(beanFactory) - //TODO: will be replaced with more appropriate IPC approach. - writeToFile(beanClassNames) + beanQualifiedNames = beanClassNames.distinct() // After desired post-processing is completed we destroy bean definitions // to avoid further possible actions with beans that may be unsafe. @@ -59,28 +56,4 @@ class UtBotBeanFactoryPostProcessor : BeanFactoryPostProcessor, PriorityOrdered beanRegistry.removeBeanDefinition(beanDefinitionName) } } - - private fun writeToFile(beanClassNames: ArrayList) { - try { - val springBeansFile = File("SpringBeans.txt") - val fileWriter = FileWriter(springBeansFile) - - val distinctClassNames = beanClassNames.stream() - .distinct() - .toArray() - Arrays.sort(distinctClassNames) - - for (beanClassName in distinctClassNames) { - fileWriter.append(beanClassName.toString()) - fileWriter.append("\n") - } - - fileWriter.flush() - fileWriter.close() - - println("Storing bean information completed successfully") - } catch (e: Throwable) { - println("Storing bean information failed with exception $e") - } - } } 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 new file mode 100644 index 0000000000..e7381e939f --- /dev/null +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/process/SpringAnalyzerProcess.kt @@ -0,0 +1,115 @@ +package org.utbot.spring.process + +import com.jetbrains.rd.util.lifetime.LifetimeDefinition +import kotlinx.coroutines.runBlocking +import mu.KotlinLogging +import org.apache.commons.io.FileUtils +import org.utbot.common.getPid +import org.utbot.common.utBotTempDirectory +import org.utbot.framework.UtSettings +import org.utbot.framework.process.AbstractRDProcessCompanion +import org.utbot.rd.ProcessWithRdServer +import org.utbot.rd.exceptions.InstantProcessDeathException +import org.utbot.rd.generated.LoggerModel +import org.utbot.rd.generated.loggerModel +import org.utbot.rd.loggers.UtRdKLogger +import org.utbot.rd.loggers.setup +import org.utbot.rd.onSchedulerBlocking +import org.utbot.rd.startBlocking +import org.utbot.rd.startUtProcessWithRdServer +import org.utbot.rd.terminateOnException +import org.utbot.spring.generated.SpringAnalyzerParams +import org.utbot.spring.generated.SpringAnalyzerProcessModel +import org.utbot.spring.generated.springAnalyzerProcessModel +import java.nio.file.Files + +class SpringAnalyzerProcessInstantDeathException : + InstantProcessDeathException( + UtSettings.springAnalyzerProcessDebugPort, + UtSettings.runSpringAnalyzerProcessWithDebug + ) + +private const val SPRING_ANALYZER_JAR_FILENAME = "utbot-spring-analyzer-shadow.jar" +private const val SPRING_ANALYZER_JAR_PATH = "lib/$SPRING_ANALYZER_JAR_FILENAME" +private const val UNKNOWN_MODIFICATION_TIME = 0L +private val logger = KotlinLogging.logger {} +private val rdLogger = UtRdKLogger(logger, "") + +private val springAnalyzerJarFile = Files.createDirectories(utBotTempDirectory.toFile().resolve("spring-analyzer").toPath()) + .toFile().resolve(SPRING_ANALYZER_JAR_FILENAME).also { jarFile -> + val resource = SpringAnalyzerProcess::class.java.classLoader.getResource(SPRING_ANALYZER_JAR_PATH) + ?: error("Unable to find \"$SPRING_ANALYZER_JAR_PATH\" in resources, make sure it's on the classpath") + val resourceConnection = resource.openConnection() + val lastResourceModification = try { + resourceConnection.lastModified + } finally { + resourceConnection.getInputStream().close() + } + if ( + !jarFile.exists() || + jarFile.lastModified() == UNKNOWN_MODIFICATION_TIME || + lastResourceModification == UNKNOWN_MODIFICATION_TIME || + jarFile.lastModified() < lastResourceModification + ) + FileUtils.copyURLToFile(resource, jarFile) + } + +class SpringAnalyzerProcess private constructor( + rdProcess: ProcessWithRdServer +) : ProcessWithRdServer by rdProcess { + + companion object : AbstractRDProcessCompanion( + debugPort = UtSettings.springAnalyzerProcessDebugPort, + runWithDebug = UtSettings.runSpringAnalyzerProcessWithDebug, + suspendExecutionInDebugMode = UtSettings.suspendSpringAnalyzerProcessExecutionInDebugMode, + processSpecificCommandLineArgs = { + listOf( + "-Dorg.apache.commons.logging.LogFactory=org.utbot.spring.loggers.RDApacheCommonsLogFactory", + "-jar", + springAnalyzerJarFile.path + ) + }) { + + fun createBlocking() = runBlocking { SpringAnalyzerProcess() } + + suspend operator fun invoke(): SpringAnalyzerProcess = LifetimeDefinition().terminateOnException { lifetime -> + val rdProcess = startUtProcessWithRdServer(lifetime) { port -> + val cmd = obtainProcessCommandLine(port) + val process = ProcessBuilder(cmd) + .directory(Files.createTempDirectory(utBotTempDirectory, "spring-analyzer").toFile()) + .start() + + logger.info { "Spring Analyzer process started with PID = ${process.getPid}" } + + if (!process.isAlive) throw SpringAnalyzerProcessInstantDeathException() + + process + } + rdProcess.awaitProcessReady() + val proc = SpringAnalyzerProcess(rdProcess) + proc.loggerModel.setup(rdLogger, proc.lifetime) + return proc + } + } + + private val springAnalyzerModel: SpringAnalyzerProcessModel = onSchedulerBlocking { protocol.springAnalyzerProcessModel } + private val loggerModel: LoggerModel = onSchedulerBlocking { protocol.loggerModel } + + fun getBeanQualifiedNames( + classpath: List, + configuration: String, + propertyFilesPaths: List, + xmlConfigurationPaths: List, + useSpringAnalyzer: Boolean + ): List { + val params = SpringAnalyzerParams( + classpath.toTypedArray(), + configuration, + propertyFilesPaths.toTypedArray(), + xmlConfigurationPaths.toTypedArray(), + useSpringAnalyzer + ) + val result = springAnalyzerModel.analyze.startBlocking(params) + return result.beanTypes.toList() + } +} 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 new file mode 100644 index 0000000000..8c4eab7b0a --- /dev/null +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/process/SpringAnalyzerProcessMain.kt @@ -0,0 +1,56 @@ +package org.utbot.spring.process + +import com.jetbrains.rd.framework.IProtocol +import com.jetbrains.rd.util.Logger +import com.jetbrains.rd.util.getLogger +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.rd.ClientProtocolBuilder +import org.utbot.rd.IdleWatchdog +import org.utbot.rd.RdSettingsContainerFactory +import org.utbot.rd.generated.loggerModel +import org.utbot.rd.generated.settingsModel +import org.utbot.rd.loggers.UtRdRemoteLoggerFactory +import org.utbot.spring.analyzers.SpringApplicationAnalyzer +import org.utbot.spring.data.ApplicationData +import org.utbot.spring.generated.SpringAnalyzerProcessModel +import org.utbot.spring.generated.SpringAnalyzerResult +import org.utbot.spring.generated.springAnalyzerProcessModel +import java.io.File +import kotlin.time.Duration.Companion.seconds + +private val messageFromMainTimeoutMillis = 120.seconds +private val logger = getLogger() + +@Suppress("unused") +object SpringAnalyzerProcessMain + +suspend fun main(args: Array) = + ClientProtocolBuilder().withProtocolTimeout(messageFromMainTimeoutMillis).start(args) { + loggerModel.initRemoteLogging.adviseOnce(lifetime) { + Logger.set(Lifetime.Eternal, UtRdRemoteLoggerFactory(loggerModel)) + logger.info { "-----------------------------------------------------------------------" } + logger.info { "------------------NEW SPRING ANALYZER PROCESS STARTED------------------" } + logger.info { "-----------------------------------------------------------------------" } + } + AbstractSettings.setupFactory(RdSettingsContainerFactory(protocol.settingsModel)) + springAnalyzerProcessModel.setup(it, protocol) + } + +private fun SpringAnalyzerProcessModel.setup(watchdog: IdleWatchdog, realProtocol: IProtocol) { + watchdog.measureTimeForActiveCall(analyze, "Analyzing Spring Application") { params -> + SpringAnalyzerResult( + if (!params.useSpringAnalyzer) emptyArray() + else SpringApplicationAnalyzer( + ApplicationData( + params.classpath.toList().map { File(it).toURI().toURL() }.toTypedArray(), + params.configuration, + params.propertyFilesPaths.toList(), + params.xmlConfigurationPaths.toList() + ) + ).analyze().toTypedArray() + ) + } +}