Skip to content

Commit a884f5c

Browse files
committed
Start Spring analyzer process from engine process to use RD logger
1 parent 4d529b9 commit a884f5c

File tree

25 files changed

+472
-345
lines changed

25 files changed

+472
-345
lines changed

gradle.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ pytorchNativeVersion=1.9.1
7777
shadowJarVersion=7.1.2
7878
openblasVersion=0.3.10-1.5.4
7979
arpackNgVersion=3.7.0-1.5.4
80+
commonsLoggingVersion=1.2
8081

8182
# configuration for build server
8283
#
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package org.utbot.common
2+
3+
import java.io.OutputStream
4+
import java.io.PrintStream
5+
6+
fun silentlyCloseStandardStreams() {
7+
// we should change out/err streams as not to spend time on user output
8+
// and also because rd default logging system writes some initial values to stdout, polluting it as well
9+
val tmpStream = PrintStream(object : OutputStream() {
10+
override fun write(b: Int) {}
11+
})
12+
val prevOut = System.out
13+
val prevError = System.err
14+
System.setOut(tmpStream)
15+
System.setErr(tmpStream)
16+
// stdin/stderr should be closed as not to leave hanging descriptors
17+
// and we cannot log any exceptions here as rd remote logging is still not configured
18+
// so we pass any exceptions
19+
silent { prevOut.close() }
20+
silent { prevError.close() }
21+
}

utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -297,13 +297,6 @@ object UtSettings : AbstractSettings(logger, defaultKeyForSettingsPath, defaultS
297297
// endregion
298298

299299
// region spring analyzer process debug
300-
/**
301-
* Path to custom log4j2 configuration file for SpringAnalyzerProcess.
302-
* By default utbot-intellij/src/main/resources/log4j2.xml is used.
303-
* Also default value is used if provided value is not a file.
304-
*/
305-
var springAnalyzerProcessLogConfigFile by getStringProperty("")
306-
307300
/**
308301
* The property is useful only for the IntelliJ IDEs.
309302
* If the property is set in true the spring analyzer process opens a debug port.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package org.utbot.framework.process
2+
3+
import org.utbot.common.osSpecificJavaExecutable
4+
import org.utbot.framework.plugin.services.JdkInfoService
5+
import org.utbot.rd.rdPortArgument
6+
import java.io.File
7+
import java.nio.file.Path
8+
import kotlin.io.path.pathString
9+
10+
private val javaExecutablePathString: Path
11+
get() = JdkInfoService.jdkInfoProvider.info.path.resolve("bin${File.separatorChar}${osSpecificJavaExecutable()}")
12+
13+
// TODO use it in EngineProcess and InstrumentedProcess
14+
fun withCommonProcessCommandLineArgs(
15+
processSpecificArgs: List<String>,
16+
debugPort: Int,
17+
runWithDebug: Boolean,
18+
suspendExecutionInDebugMode: Boolean,
19+
rdPort: Int
20+
): List<String> = buildList {
21+
val suspendValue = if (suspendExecutionInDebugMode) "y" else "n"
22+
val debugArgument = "-agentlib:jdwp=transport=dt_socket,server=n,suspend=${suspendValue},quiet=y,address=$debugPort"
23+
.takeIf { runWithDebug }
24+
25+
add(javaExecutablePathString.pathString)
26+
val javaVersionSpecificArgs = OpenModulesContainer.javaVersionSpecificArguments
27+
if (javaVersionSpecificArgs.isNotEmpty()) {
28+
addAll(javaVersionSpecificArgs)
29+
}
30+
debugArgument?.let { add(it) }
31+
addAll(processSpecificArgs)
32+
add(rdPortArgument(rdPort))
33+
}

utbot-framework/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ dependencies {
66
api project(':utbot-framework-api')
77
api project(':utbot-rd')
88

9+
implementation project(':utbot-spring-analyzer-model')
10+
911
implementation group: 'com.jetbrains.rd', name: 'rd-framework', version: rdVersion
1012
implementation group: 'com.jetbrains.rd', name: 'rd-core', version: rdVersion
1113

utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineProcessMain.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import org.utbot.rd.RdSettingsContainerFactory
3434
import org.utbot.rd.findRdPort
3535
import org.utbot.rd.generated.settingsModel
3636
import org.utbot.rd.loggers.UtRdKLoggerFactory
37+
import org.utbot.rd.terminateOnException
3738
import org.utbot.sarif.RdSourceFindingStrategyFacade
3839
import org.utbot.sarif.SarifReport
3940
import org.utbot.summary.summarizeAll
@@ -79,6 +80,20 @@ private fun EngineProcessModel.setup(kryoHelper: KryoHelper, watchdog: IdleWatch
7980
File(it).toURI().toURL()
8081
}.toTypedArray())))
8182
}
83+
watchdog.measureTimeForActiveCall(getSpringBeanQualifiedNames, "Getting Spring bean definitions") { params ->
84+
val springAnalyzerProcess = SpringAnalyzerProcess.createBlocking()
85+
val beans = springAnalyzerProcess.terminateOnException { _ ->
86+
springAnalyzerProcess.getBeanQualifiedNames(
87+
params.classpath.toList(),
88+
params.config,
89+
// TODO remove once spring-analyzer learns to find resources on its own, temporarily leaving it here for testing with hardcoded absolute paths
90+
propertyFilesPaths = emptyList(),
91+
xmlConfigurationPaths = emptyList()
92+
).toTypedArray()
93+
}
94+
springAnalyzerProcess.terminate()
95+
beans
96+
}
8297
watchdog.measureTimeForActiveCall(createTestGenerator, "Creating Test Generator") { params ->
8398
AnalyticsConfigureUtil.configureML()
8499
Instrumenter.adapter = RdInstrumenter(realProtocol.rdInstrumenterAdapter)
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package org.utbot.framework.process
2+
3+
import com.jetbrains.rd.util.lifetime.LifetimeDefinition
4+
import kotlinx.coroutines.runBlocking
5+
import mu.KotlinLogging
6+
import org.apache.commons.io.FileUtils
7+
import org.utbot.common.getPid
8+
import org.utbot.common.utBotTempDirectory
9+
import org.utbot.framework.UtSettings
10+
import org.utbot.rd.ProcessWithRdServer
11+
import org.utbot.rd.exceptions.InstantProcessDeathException
12+
import org.utbot.rd.generated.LoggerModel
13+
import org.utbot.rd.generated.loggerModel
14+
import org.utbot.rd.generated.synchronizationModel
15+
import org.utbot.rd.loggers.UtRdKLogger
16+
import org.utbot.rd.loggers.setupRdLogger
17+
import org.utbot.rd.onSchedulerBlocking
18+
import org.utbot.rd.startBlocking
19+
import org.utbot.rd.startUtProcessWithRdServer
20+
import org.utbot.rd.terminateOnException
21+
import org.utbot.spring.process.generated.SpringAnalyzerParams
22+
import org.utbot.spring.process.generated.SpringAnalyzerProcessModel
23+
import org.utbot.spring.process.generated.springAnalyzerProcessModel
24+
import java.nio.file.Files
25+
26+
class SpringAnalyzerProcessInstantDeathException :
27+
InstantProcessDeathException(UtSettings.springAnalyzerProcessDebugPort, UtSettings.runSpringAnalyzerProcessWithDebug)
28+
29+
private const val SPRING_ANALYZER_JAR_FILENAME = "utbot-spring-analyzer-shadow.jar"
30+
private val logger = KotlinLogging.logger {}
31+
private val rdLogger = UtRdKLogger(logger, "")
32+
33+
class SpringAnalyzerProcess private constructor(
34+
rdProcess: ProcessWithRdServer
35+
) : ProcessWithRdServer by rdProcess {
36+
37+
companion object {
38+
private fun obtainProcessSpecificCommandLineArgs(): List<String> {
39+
val jarFile =
40+
Files.createDirectories(utBotTempDirectory.toFile().resolve("spring-analyzer").toPath())
41+
.toFile().resolve(SPRING_ANALYZER_JAR_FILENAME)
42+
FileUtils.copyURLToFile(
43+
this::class.java.classLoader.getResource("lib/$SPRING_ANALYZER_JAR_FILENAME"),
44+
jarFile
45+
)
46+
return listOf(
47+
"-Dorg.apache.commons.logging.LogFactory=org.utbot.rd.loggers.RDApacheCommonsLogFactory",
48+
"-jar",
49+
jarFile.path
50+
)
51+
}
52+
53+
fun createBlocking() = runBlocking { SpringAnalyzerProcess() }
54+
55+
suspend operator fun invoke(): SpringAnalyzerProcess = LifetimeDefinition().terminateOnException { lifetime ->
56+
val rdProcess = startUtProcessWithRdServer(lifetime) { port ->
57+
val cmd = withCommonProcessCommandLineArgs(
58+
obtainProcessSpecificCommandLineArgs(),
59+
debugPort = UtSettings.springAnalyzerProcessDebugPort,
60+
runWithDebug = UtSettings.runSpringAnalyzerProcessWithDebug,
61+
suspendExecutionInDebugMode = UtSettings.suspendSpringAnalyzerProcessExecutionInDebugMode,
62+
rdPort = port
63+
)
64+
val process = ProcessBuilder(cmd)
65+
.directory(Files.createTempDirectory(utBotTempDirectory, "spring-analyzer").toFile())
66+
.start()
67+
68+
logger.info { "Spring Analyzer process started with PID = ${process.getPid}" }
69+
70+
if (!process.isAlive) throw SpringAnalyzerProcessInstantDeathException()
71+
72+
process
73+
}
74+
rdProcess.awaitProcessReady()
75+
val proc = SpringAnalyzerProcess(rdProcess)
76+
setupRdLogger(rdProcess, proc.loggerModel, rdLogger)
77+
return proc
78+
}
79+
}
80+
81+
private val springAnalyzerModel: SpringAnalyzerProcessModel = onSchedulerBlocking { protocol.springAnalyzerProcessModel }
82+
private val loggerModel: LoggerModel = onSchedulerBlocking { protocol.loggerModel }
83+
84+
init {
85+
lifetime.onTermination {
86+
protocol.synchronizationModel.stopProcess.fire(Unit)
87+
}
88+
}
89+
90+
fun getBeanQualifiedNames(
91+
classpath: List<String>,
92+
configuration: String,
93+
propertyFilesPaths: List<String>,
94+
xmlConfigurationPaths: List<String>
95+
): List<String> {
96+
val params = SpringAnalyzerParams(
97+
classpath.toTypedArray(),
98+
configuration,
99+
propertyFilesPaths.toTypedArray(),
100+
xmlConfigurationPaths.toTypedArray()
101+
)
102+
val result = springAnalyzerModel.analyze.startBlocking(params)
103+
return result.beanTypes.toList()
104+
}
105+
}

0 commit comments

Comments
 (0)