mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-10-02 04:35:22 +07:00
[evaluation-plugin] LME-463 Implement Gradle test runner
GitOrigin-RevId: 167190c4a967626abb5704932bd84a5c1b040930
This commit is contained in:
committed by
intellij-monorepo-bot
parent
2f8b206433
commit
66f67ad877
@@ -30,6 +30,7 @@ jvm_library(
|
||||
"//platform/lang-core",
|
||||
"//platform/projectModel-api:projectModel",
|
||||
"//platform/refactoring",
|
||||
"//platform/smRunner",
|
||||
],
|
||||
runtime_deps = [":java_resources"]
|
||||
)
|
||||
@@ -60,6 +61,8 @@ jvm_library(
|
||||
"//platform/testFramework:testFramework_test_lib",
|
||||
"@lib//:kotlinx-coroutines-test",
|
||||
"//java/testFramework",
|
||||
"//platform/smRunner",
|
||||
"//platform/smRunner:smRunner_test_lib",
|
||||
],
|
||||
runtime_deps = [":java_resources"]
|
||||
)
|
||||
|
||||
@@ -28,5 +28,6 @@
|
||||
<orderEntry type="module" module-name="intellij.platform.testFramework" scope="TEST" />
|
||||
<orderEntry type="library" scope="TEST" name="kotlinx-coroutines-test" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.java.testFramework" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.platform.smRunner" />
|
||||
</component>
|
||||
</module>
|
||||
+1
-1
@@ -21,7 +21,7 @@
|
||||
<completionEvaluationVisitor implementation="com.intellij.cce.java.visitor.JavaSelfIdentificationVisitor"/>
|
||||
<apiCallExtractor implementation="com.intellij.cce.java.chat.JavaApiCallExtractorProvider"/>
|
||||
<exposedApiExtractor implementation="com.intellij.cce.java.evaluable.JavaExposedApiExtractor"/>
|
||||
<testRunner implementation="com.intellij.cce.java.test.JavaTestRunnerForMaven"/>
|
||||
<testRunner implementation="com.intellij.cce.java.test.JavaTestRunner"/>
|
||||
<completionEvaluationVisitor implementation="com.intellij.cce.java.visitor.JavaSandboxFeatureVisitor"/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package com.intellij.cce.java.test
|
||||
|
||||
import com.intellij.cce.core.Language
|
||||
import com.intellij.cce.test.TestRunRequest
|
||||
import com.intellij.cce.test.TestRunResult
|
||||
import com.intellij.cce.test.TestRunner
|
||||
import com.intellij.cce.test.TestRunnerParams
|
||||
import com.intellij.openapi.diagnostic.fileLogger
|
||||
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.idea.maven.project.MavenProjectsManager
|
||||
import org.jetbrains.plugins.gradle.util.GradleConstants
|
||||
|
||||
internal val LOG = fileLogger()
|
||||
|
||||
class JavaTestRunner : TestRunner {
|
||||
override fun isApplicable(params: TestRunnerParams): Boolean =
|
||||
params.language == Language.JAVA ||
|
||||
params.language == Language.KOTLIN
|
||||
|
||||
override suspend fun runTests(request: TestRunRequest): TestRunResult {
|
||||
LOG.info("Running tests: ${request.tests.joinToString()}")
|
||||
if (request.tests.isEmpty()) {
|
||||
return TestRunResult(0, emptyList(), emptyList(), true, true, "")
|
||||
}
|
||||
|
||||
val moduleTests = request.tests
|
||||
.map {
|
||||
val parts = it.split(":")
|
||||
if (parts.size == 1) null to parts[0]
|
||||
else if (parts.size == 2) parts[0] to parts[1]
|
||||
else throw IllegalArgumentException("Test name has invalid format: $it")
|
||||
}
|
||||
.groupBy { it.first }
|
||||
.map { ModuleTests(it.key, it.value.map { it.second }) }
|
||||
|
||||
if (isMaven(request.project)) {
|
||||
return JavaTestRunnerForMaven.run(request.project, moduleTests)
|
||||
}
|
||||
|
||||
if (isGradle(request.project)) {
|
||||
return JavaTestRunnerForGradle.run(request.project, moduleTests)
|
||||
}
|
||||
|
||||
throw IllegalStateException("Unknown build system. Project: ${request.project.basePath}")
|
||||
}
|
||||
}
|
||||
|
||||
internal data class ModuleTests(val module: String?, val tests: List<String>)
|
||||
|
||||
private fun isMaven(project: Project): Boolean =
|
||||
MavenProjectsManager.getInstance(project).hasProjects()
|
||||
|
||||
private fun isGradle(project: Project): Boolean =
|
||||
ExternalSystemApiUtil.getSettings(project, GradleConstants.SYSTEM_ID).getLinkedProjectsSettings().isNotEmpty()
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
package com.intellij.cce.java.test
|
||||
|
||||
import com.intellij.cce.test.TestRunResult
|
||||
import com.intellij.execution.RunManager
|
||||
import com.intellij.execution.RunnerAndConfigurationSettings
|
||||
import com.intellij.execution.configurations.ConfigurationTypeUtil
|
||||
import com.intellij.execution.executors.DefaultRunExecutor
|
||||
import com.intellij.execution.impl.DefaultJavaProgramRunner
|
||||
import com.intellij.execution.runners.ExecutionEnvironment
|
||||
import com.intellij.openapi.extensions.impl.ExtensionPointImpl
|
||||
import com.intellij.openapi.externalSystem.execution.ExternalSystemExecutionConsoleManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.openapi.util.use
|
||||
import org.jetbrains.plugins.gradle.service.execution.GradleExternalTaskConfigurationType
|
||||
import org.jetbrains.plugins.gradle.service.execution.GradleRunConfiguration
|
||||
import org.jetbrains.plugins.gradle.service.task.GradleTaskManagerExtension
|
||||
|
||||
internal object JavaTestRunnerForGradle {
|
||||
@Suppress("TestOnlyProblems")
|
||||
suspend fun run(project: Project, moduleTests: List<ModuleTests>): TestRunResult {
|
||||
return Disposer.newDisposable().use { disposable ->
|
||||
// `KotlinWasmBrowserDebugGradleTaskManagerExtension` can break older gradle versions
|
||||
val testExtensions = GradleTaskManagerExtension.EP_NAME.extensionList.filterNot { it.javaClass.name.contains("Wasm") }
|
||||
(GradleTaskManagerExtension.EP_NAME.point as ExtensionPointImpl<GradleTaskManagerExtension>).maskAll(testExtensions, disposable, true)
|
||||
|
||||
// Default gradle test console doesn't provide output to ProcessListener
|
||||
(ExternalSystemExecutionConsoleManager.EP_NAME.point as ExtensionPointImpl<ExternalSystemExecutionConsoleManager<*, *>>)
|
||||
.maskAll(listOf(), disposable, true)
|
||||
|
||||
doRunTests(project, moduleTests)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun doRunTests(project: Project, moduleTests: List<ModuleTests>): TestRunResult {
|
||||
|
||||
val runner = DefaultJavaProgramRunner.getInstance()
|
||||
val executor = DefaultRunExecutor.getRunExecutorInstance()
|
||||
val configSettings = configSettings(project, moduleTests)
|
||||
val environment = ExecutionEnvironment(executor, runner, configSettings, project)
|
||||
|
||||
val results = RunConfigurationResults.compute { callback ->
|
||||
environment.callback = callback
|
||||
runner.execute(environment)
|
||||
}
|
||||
|
||||
val allTests = mutableSetOf<String>()
|
||||
val failedTests = mutableSetOf<String>()
|
||||
val output = results.output.lines().joinToString("\n") { line ->
|
||||
if (line.contains("<event type='afterTest'>")) {
|
||||
val className = readAttribute(line, "className")
|
||||
val resultType = readAttribute(line, "resultType")
|
||||
val displayName = readAttribute(line, "displayName")
|
||||
val success = resultType == "SUCCESS"
|
||||
|
||||
allTests.add(className)
|
||||
if (!success) {
|
||||
failedTests.add(className)
|
||||
}
|
||||
|
||||
val highlightPrefix = if (success) " <<<++++<<< " else " <<<----<<< "
|
||||
|
||||
line + "${highlightPrefix}[$resultType] $className:$displayName"
|
||||
}
|
||||
else line
|
||||
}
|
||||
|
||||
return TestRunResult(results.exitCode, (allTests - failedTests).toList(), failedTests.toList(), true, true, output)
|
||||
}
|
||||
|
||||
private fun configSettings(project: Project, moduleTests: List<ModuleTests>): RunnerAndConfigurationSettings {
|
||||
val settings = RunManager.getInstance(project).createConfiguration(
|
||||
"gradle-tests",
|
||||
ConfigurationTypeUtil.findConfigurationType(GradleExternalTaskConfigurationType::class.java).factory
|
||||
)
|
||||
val runConfiguration = settings.configuration as GradleRunConfiguration
|
||||
runConfiguration.isRunAsTest = true
|
||||
runConfiguration.settings.scriptParameters = moduleTests.joinToString(" ") { (moduleName, testNames) ->
|
||||
if (moduleName != null) ":$moduleName:test ${testNames.joinToString(" ") { "--tests $it" }}"
|
||||
else ":test ${testNames.joinToString(" ") { "--tests $it" }}"
|
||||
}
|
||||
return settings
|
||||
}
|
||||
|
||||
private fun readAttribute(xml: String, name: String): String {
|
||||
val startTag = "$name='"
|
||||
val startIndex = xml.indexOf(startTag) + startTag.length
|
||||
val endIndex = xml.indexOf("'", startIndex)
|
||||
return if (startIndex >= startTag.length && endIndex > startIndex) {
|
||||
xml.substring(startIndex, endIndex)
|
||||
}
|
||||
else {
|
||||
throw IllegalArgumentException("Attribute '$name' not found in the given XML")
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
-65
@@ -1,38 +1,16 @@
|
||||
package com.intellij.cce.java.test
|
||||
|
||||
|
||||
import com.intellij.cce.core.Language
|
||||
import com.intellij.cce.test.TestRunRequest
|
||||
import com.intellij.cce.test.TestRunResult
|
||||
import com.intellij.cce.test.TestRunner
|
||||
import com.intellij.cce.test.TestRunnerParams
|
||||
import com.intellij.execution.process.ProcessEvent
|
||||
import com.intellij.execution.process.ProcessListener
|
||||
import com.intellij.execution.runners.ProgramRunner
|
||||
import com.intellij.openapi.diagnostic.fileLogger
|
||||
import com.intellij.openapi.progress.runBlockingCancellable
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.project.guessProjectDir
|
||||
import com.intellij.openapi.util.Key
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import org.jetbrains.idea.maven.execution.MavenRunConfigurationType
|
||||
import org.jetbrains.idea.maven.execution.MavenRunnerParameters
|
||||
import org.jetbrains.idea.maven.execution.MavenRunnerSettings
|
||||
|
||||
private val LOG = fileLogger()
|
||||
internal object JavaTestRunnerForMaven {
|
||||
|
||||
internal class JavaTestRunnerForMaven : TestRunner {
|
||||
override fun isApplicable(params: TestRunnerParams): Boolean {
|
||||
return params.language == Language.JAVA
|
||||
|| params.language == Language.KOTLIN // TODO temporary solution for docker testing
|
||||
}
|
||||
|
||||
override fun runTests(request: TestRunRequest): TestRunResult {
|
||||
LOG.info("Running tests: ${request.tests.joinToString()}")
|
||||
if (request.tests.isEmpty()) {
|
||||
return TestRunResult(0, emptyList(), emptyList(), true, true, "")
|
||||
}
|
||||
|
||||
val project = request.project
|
||||
suspend fun run(project: Project, moduleTests: List<ModuleTests>): TestRunResult {
|
||||
|
||||
val projectDir = project.guessProjectDir()!!
|
||||
|
||||
@@ -41,33 +19,10 @@ internal class JavaTestRunnerForMaven : TestRunner {
|
||||
/* pomFileName = */ "",
|
||||
/* goals = */ listOf("test"),
|
||||
/* explicitEnabledProfiles = */ emptyList<String>())
|
||||
val deferred = CompletableDeferred<Int>()
|
||||
|
||||
val sb = StringBuilder()
|
||||
|
||||
val callback = ProgramRunner.Callback { descriptor ->
|
||||
LOG.info("processStarted $descriptor")
|
||||
val processHandler = descriptor.processHandler ?: error("processHandler is null")
|
||||
processHandler.addProcessListener(object : ProcessListener {
|
||||
override fun onTextAvailable(event: ProcessEvent, outputType: Key<*>) {
|
||||
sb.append(event.text)
|
||||
}
|
||||
|
||||
override fun processTerminated(event: ProcessEvent) {
|
||||
LOG.info("processTerminated. exitCode=${event.exitCode}")
|
||||
deferred.complete(event.exitCode)
|
||||
}
|
||||
|
||||
override fun processNotStarted() {
|
||||
LOG.error("processNotStarted")
|
||||
deferred.complete(-1)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// in multi-module projects tests will be prefixed with module:test
|
||||
request.tests.map { it.substringBeforeLast(":", "") }
|
||||
.filter { it.isNotBlank() }
|
||||
moduleTests
|
||||
.mapNotNull { it.module }
|
||||
.also {
|
||||
if (it.isNotEmpty()) {
|
||||
params.projectsCmdOptionValues = it
|
||||
@@ -75,28 +30,19 @@ internal class JavaTestRunnerForMaven : TestRunner {
|
||||
}
|
||||
|
||||
val runnerSettings = MavenRunnerSettings().also {
|
||||
if (request.tests.any()) {
|
||||
//todo check
|
||||
it.setVmOptions("-Dtest=${request.tests.joinToString(separator = ",") { it.substringAfterLast(":") }}")
|
||||
}
|
||||
//todo check
|
||||
it.setVmOptions("-Dtest=${moduleTests.flatMap { it.tests }.joinToString(separator = ",")}")
|
||||
}
|
||||
|
||||
MavenRunConfigurationType.runConfiguration(project,
|
||||
params,
|
||||
null,
|
||||
runnerSettings,
|
||||
callback)
|
||||
|
||||
LOG.info("await for process termination")
|
||||
val exitCode = runBlockingCancellable {
|
||||
deferred.await()
|
||||
val results = RunConfigurationResults.compute { callback ->
|
||||
MavenRunConfigurationType.runConfiguration(project, params, null, runnerSettings, callback)
|
||||
}
|
||||
|
||||
val output = sb.toString()
|
||||
val output = results.output
|
||||
val compilationSuccessful = MavenOutputParser.compilationSuccessful(output)
|
||||
val projectIsResolvable = MavenOutputParser.checkIfProjectIsResolvable(output)
|
||||
val (passed, failed) = MavenOutputParser.parse(output)
|
||||
return TestRunResult(exitCode, passed, failed, compilationSuccessful, projectIsResolvable, output)
|
||||
return TestRunResult(results.exitCode, passed, failed, compilationSuccessful, projectIsResolvable, output)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package com.intellij.cce.java.test
|
||||
|
||||
import com.intellij.execution.process.ProcessEvent
|
||||
import com.intellij.execution.process.ProcessListener
|
||||
import com.intellij.execution.runners.ProgramRunner
|
||||
import com.intellij.openapi.util.Key
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
|
||||
internal data class RunConfigurationResults(
|
||||
val exitCode: Int,
|
||||
val output: String
|
||||
) {
|
||||
companion object {
|
||||
suspend fun compute(f: (ProgramRunner.Callback) -> Unit): RunConfigurationResults {
|
||||
val deferred = CompletableDeferred<Int>()
|
||||
val sb = StringBuilder()
|
||||
|
||||
val callback = ProgramRunner.Callback { descriptor ->
|
||||
LOG.info("processStarted $descriptor")
|
||||
val processHandler = descriptor.processHandler ?: error("processHandler is null")
|
||||
processHandler.addProcessListener(object : ProcessListener {
|
||||
override fun onTextAvailable(event: ProcessEvent, outputType: Key<*>) {
|
||||
sb.append(event.text)
|
||||
}
|
||||
|
||||
override fun processTerminated(event: ProcessEvent) {
|
||||
LOG.info("processTerminated. exitCode=${event.exitCode}")
|
||||
deferred.complete(event.exitCode)
|
||||
}
|
||||
|
||||
override fun processNotStarted() {
|
||||
LOG.error("processNotStarted")
|
||||
deferred.complete(-1)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
f(callback)
|
||||
|
||||
LOG.info("await for process termination")
|
||||
|
||||
val exitCode = deferred.await()
|
||||
|
||||
return RunConfigurationResults(exitCode, sb.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user