mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
build scripts: an option to present existing coverage data files as a single report
IJI-2636 IJI-2672 GitOrigin-RevId: f84b55650360951b0f046065ac84a9c18909082b
This commit is contained in:
committed by
intellij-monorepo-bot
parent
11cf5bf265
commit
f58d8773fa
@@ -2,6 +2,7 @@
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.jetbrains.intellij.build.BuildPaths.Companion.COMMUNITY_ROOT
|
||||
import org.jetbrains.intellij.build.TestingOptions
|
||||
import org.jetbrains.intellij.build.TestingTasks
|
||||
import org.jetbrains.intellij.build.impl.createCompilationContext
|
||||
|
||||
@@ -22,7 +23,9 @@ object CommunityRunTestsBuildTarget {
|
||||
projectHome = COMMUNITY_ROOT.communityRoot,
|
||||
defaultOutputRoot = COMMUNITY_ROOT.communityRoot.resolve("out/tests")
|
||||
)
|
||||
TestingTasks.create(context).runTests(defaultMainModule = "intellij.idea.community.main")
|
||||
val options = TestingOptions()
|
||||
options.mainModule = options.mainModule ?: "intellij.idea.community.main"
|
||||
TestingTasks.create(context, options).runTests()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
@file:Suppress("ReplaceJavaStaticMethodWithKotlinAnalog")
|
||||
|
||||
package org.jetbrains.intellij.build
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
@@ -29,10 +27,22 @@ interface TestingTasks {
|
||||
* @param defaultMainModule the main module to be used if [TestingOptions.mainModule] is not specified
|
||||
* @param rootExcludeCondition if not `null`, tests from modules which sources are fit this predicate will be skipped
|
||||
*/
|
||||
suspend fun runTests(additionalJvmOptions: List<String> = java.util.List.of(),
|
||||
additionalSystemProperties: Map<String, String> = java.util.Map.of(),
|
||||
defaultMainModule: String? = null,
|
||||
rootExcludeCondition: ((Path) -> Boolean)? = null)
|
||||
@Deprecated(message = "the `defaultMainModule` should be passed via `TestingOptions#mainModule`")
|
||||
suspend fun runTests(
|
||||
additionalJvmOptions: List<String> = emptyList(),
|
||||
additionalSystemProperties: Map<String, String> = emptyMap(),
|
||||
defaultMainModule: String? = null,
|
||||
rootExcludeCondition: ((Path) -> Boolean)? = null,
|
||||
)
|
||||
|
||||
/**
|
||||
* @param rootExcludeCondition if not `null`, tests from modules which sources are fit this predicate will be skipped
|
||||
*/
|
||||
suspend fun runTests(
|
||||
additionalJvmOptions: List<String> = emptyList(),
|
||||
additionalSystemProperties: Map<String, String> = emptyMap(),
|
||||
rootExcludeCondition: ((Path) -> Boolean)? = null,
|
||||
)
|
||||
|
||||
/**
|
||||
* Run all tests annotated with [com.intellij.testFramework.SkipInHeadlessEnvironment]
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.jetbrains.intellij.build.TestingOptions
|
||||
import org.jetbrains.intellij.build.TestingTasks
|
||||
import org.jetbrains.intellij.build.causal.CausalProfilingOptions
|
||||
import org.jetbrains.intellij.build.dependencies.TeamCityHelper
|
||||
import org.jetbrains.intellij.build.impl.coverage.Coverage
|
||||
import org.jetbrains.intellij.build.impl.coverage.CoverageImpl
|
||||
import org.jetbrains.intellij.build.io.ZipEntryProcessorResult
|
||||
import org.jetbrains.intellij.build.io.readZipFile
|
||||
@@ -72,7 +73,19 @@ private const val NO_TESTS_ERROR = 42
|
||||
|
||||
internal class TestingTasksImpl(context: CompilationContext, private val options: TestingOptions) : TestingTasks {
|
||||
private val context: CompilationContext = if (options.useArchivedCompiledClasses) context.asArchived else context
|
||||
override lateinit var coverage: CoverageImpl
|
||||
|
||||
override val coverage: Coverage by lazy {
|
||||
CoverageImpl(
|
||||
context = this.context,
|
||||
coveredModuleNames = runConfigurations
|
||||
.map { it.moduleName }
|
||||
.takeIf { it.any() }
|
||||
?: listOfNotNull(options.mainModule),
|
||||
coveredClasses = requireNotNull(options.coveredClassesPatterns) {
|
||||
"Test coverage is enabled but the classes pattern is not specified"
|
||||
}.splitToSequence(';').map(::Regex).toList(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun loadRunConfigurations(name: String): List<JUnitRunConfigurationProperties> {
|
||||
return try {
|
||||
@@ -99,31 +112,38 @@ internal class TestingTasksImpl(context: CompilationContext, private val options
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadTestRunConfigurations(): List<JUnitRunConfigurationProperties>? {
|
||||
val testConfigurationsOption = options.testConfigurations ?: return null
|
||||
return testConfigurationsOption
|
||||
.splitToSequence(';')
|
||||
.filter(String::isNotEmpty)
|
||||
.flatMap(::loadRunConfigurations)
|
||||
.toList()
|
||||
private val runConfigurations: List<JUnitRunConfigurationProperties> by lazy {
|
||||
options.testConfigurations
|
||||
?.splitToSequence(';')
|
||||
?.filter(String::isNotEmpty)
|
||||
?.flatMap(::loadRunConfigurations)
|
||||
?.toList() ?: emptyList()
|
||||
}
|
||||
|
||||
@Deprecated("the `defaultMainModule` should be passed via `TestingOptions#mainModule`")
|
||||
override suspend fun runTests(
|
||||
additionalJvmOptions: List<String>,
|
||||
additionalSystemProperties: Map<String, String>,
|
||||
defaultMainModule: String?,
|
||||
rootExcludeCondition: ((Path) -> Boolean)?,
|
||||
) {
|
||||
require(defaultMainModule == null) {
|
||||
"The `defaultMainModule` parameter is deprecated, please use `TestingOptions#mainModule` instead."
|
||||
}
|
||||
runTests(additionalJvmOptions, additionalSystemProperties, rootExcludeCondition)
|
||||
}
|
||||
|
||||
override suspend fun runTests(additionalJvmOptions: List<String>, additionalSystemProperties: Map<String, String>, rootExcludeCondition: ((Path) -> Boolean)?) {
|
||||
if (options.redirectStdOutToFile && !TeamCityHelper.isUnderTeamCity) {
|
||||
context.messages.warning("'${TestingOptions.REDIRECT_STDOUT_TO_FILE}' can be set only for a TeamCity build, ignored.")
|
||||
}
|
||||
if (TeamCityHelper.isUnderTeamCity && options.redirectStdOutToFile) {
|
||||
redirectStdOutToFile {
|
||||
runTestsImpl(additionalJvmOptions, additionalSystemProperties, defaultMainModule, rootExcludeCondition)
|
||||
runTestsImpl(additionalJvmOptions, additionalSystemProperties, rootExcludeCondition)
|
||||
}
|
||||
}
|
||||
else {
|
||||
runTestsImpl(additionalJvmOptions, additionalSystemProperties, defaultMainModule, rootExcludeCondition)
|
||||
runTestsImpl(additionalJvmOptions, additionalSystemProperties, rootExcludeCondition)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,7 +168,6 @@ internal class TestingTasksImpl(context: CompilationContext, private val options
|
||||
|
||||
private suspend fun runTestsImpl(additionalJvmOptions: List<String>,
|
||||
additionalSystemProperties: Map<String, String>,
|
||||
defaultMainModule: String?,
|
||||
rootExcludeCondition: ((Path) -> Boolean)?) {
|
||||
if (options.enableCoverage && options.isPerformanceTestsOnly) {
|
||||
context.messages.buildStatus("Skipping performance testing with Coverage, {build.status.text}")
|
||||
@@ -159,13 +178,12 @@ internal class TestingTasksImpl(context: CompilationContext, private val options
|
||||
return
|
||||
}
|
||||
|
||||
val mainModule = options.mainModule ?: defaultMainModule
|
||||
val mainModule = options.mainModule
|
||||
checkOptions(mainModule)
|
||||
|
||||
val runConfigurations = loadTestRunConfigurations()
|
||||
if (options.validateMainModule) {
|
||||
checkNotNull(mainModule)
|
||||
val withModuleMismatch = runConfigurations?.filter { it.moduleName != mainModule } ?: emptyList()
|
||||
val withModuleMismatch = runConfigurations.filter { it.moduleName != mainModule }
|
||||
if (withModuleMismatch.isNotEmpty()) {
|
||||
val errorMessage = withModuleMismatch.joinToString(
|
||||
prefix = "Run configuration module mismatch, expected '$mainModule' (set in option 'intellij.build.test.main.module'), actual:\n",
|
||||
@@ -181,7 +199,7 @@ internal class TestingTasksImpl(context: CompilationContext, private val options
|
||||
compilationTasks.buildProjectArtifacts(it)
|
||||
}
|
||||
|
||||
if (runConfigurations != null) {
|
||||
if (runConfigurations.any()) {
|
||||
compilationTasks.compileModules(listOf("intellij.tools.testsBootstrap"),
|
||||
listOf("intellij.platform.buildScripts") + runConfigurations.map { it.moduleName })
|
||||
compilationTasks.buildProjectArtifacts(runConfigurations.flatMapTo(LinkedHashSet()) { it.requiredArtifacts })
|
||||
@@ -211,16 +229,9 @@ internal class TestingTasksImpl(context: CompilationContext, private val options
|
||||
loadTestDiscovery(effectiveAdditionalJvmOptions, systemProperties)
|
||||
}
|
||||
if (options.enableCoverage) {
|
||||
coverage = CoverageImpl(
|
||||
context,
|
||||
coveredModuleNames = runConfigurations?.map { it.moduleName } ?: listOfNotNull(mainModule),
|
||||
coveredClasses = requireNotNull(options.coveredClassesPatterns) {
|
||||
"Test coverage is enabled but the classes pattern is not specified"
|
||||
}.splitToSequence(';').map(::Regex).toList(),
|
||||
)
|
||||
coverage.enable(jvmOptions = effectiveAdditionalJvmOptions, systemProperties = systemProperties)
|
||||
}
|
||||
if (runConfigurations == null) {
|
||||
if (runConfigurations.none()) {
|
||||
runTestsFromGroupsAndPatterns(effectiveAdditionalJvmOptions, checkNotNull(mainModule) {
|
||||
"Main module is not specified"
|
||||
}, rootExcludeCondition, systemProperties)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.intellij.build.impl.coverage
|
||||
|
||||
import com.intellij.rt.coverage.aggregate.api.AggregatorApi
|
||||
import com.intellij.rt.coverage.instrumentation.CoverageArgs
|
||||
import com.intellij.rt.coverage.report.api.Filters
|
||||
import com.intellij.rt.coverage.report.api.ReportApi
|
||||
@@ -8,6 +9,7 @@ import com.intellij.rt.coverage.util.ErrorReporter
|
||||
import com.intellij.util.io.Compressor
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.intellij.build.CompilationContext
|
||||
import org.jetbrains.intellij.build.dependencies.TeamCityHelper
|
||||
import org.jetbrains.intellij.build.telemetry.block
|
||||
import org.jetbrains.jps.model.java.JavaSourceRootType
|
||||
import org.jetbrains.jps.model.java.JpsJavaClasspathKind
|
||||
@@ -34,6 +36,8 @@ interface Coverage {
|
||||
fun enable(jvmOptions: MutableList<String>, systemProperties: MutableMap<String, String>)
|
||||
|
||||
suspend fun generateReport()
|
||||
|
||||
suspend fun aggregateAndReport(dataToMerge: List<Path>)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalPathApi::class)
|
||||
@@ -42,7 +46,9 @@ internal class CoverageImpl(
|
||||
coveredModuleNames: List<String>,
|
||||
private val coveredClasses: List<Regex>,
|
||||
) : Coverage {
|
||||
private val data: Path = context.paths.tempDir.resolve("coverage.ic")
|
||||
private val buildId: String = TeamCityHelper.allProperties["teamcity.build.id"] ?: ""
|
||||
|
||||
private val data: Path = context.paths.tempDir.resolve("coverage$buildId.ic")
|
||||
|
||||
init {
|
||||
require(coveredModuleNames.any()) {
|
||||
@@ -201,4 +207,18 @@ internal class CoverageImpl(
|
||||
publishReport()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun aggregateAndReport(dataToMerge: List<Path>) {
|
||||
require(dataToMerge.any()) {
|
||||
"No coverage data files to merge"
|
||||
}
|
||||
block("Merging coverage data files") {
|
||||
@Suppress("IO_FILE_USAGE")
|
||||
AggregatorApi.merge(
|
||||
dataToMerge.map { it.toFile() },
|
||||
data.toFile(),
|
||||
)
|
||||
}
|
||||
generateReport()
|
||||
}
|
||||
}
|
||||
+13
-2
@@ -13,6 +13,7 @@ import org.jsoup.Jsoup
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.io.TempDir
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.createFile
|
||||
import kotlin.io.path.extension
|
||||
import kotlin.io.path.name
|
||||
import kotlin.io.path.readText
|
||||
@@ -44,8 +45,7 @@ class CoverageTest {
|
||||
@Test
|
||||
fun test() {
|
||||
runBlocking(Dispatchers.Default) {
|
||||
val context = context()
|
||||
val tests = TestingTasks.create(context, testingOptions)
|
||||
val tests = TestingTasks.create(context(), testingOptions)
|
||||
if (isAlreadyUnderCoverage) return@runBlocking
|
||||
tests.runTests(additionalSystemProperties = mapOf(coverageFlag to "true"))
|
||||
assertCoverage(coveredClassName = coveredClass.simpleName, reportDir = tests.coverage.reportDir)
|
||||
@@ -73,4 +73,15 @@ class CoverageTest {
|
||||
"$coveredClassName is expected to be called by this test class but 0% coverage is detected"
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test aggregating reports`() {
|
||||
if (isAlreadyUnderCoverage) return
|
||||
runBlocking(Dispatchers.Default) {
|
||||
val tests = TestingTasks.create(context(), testingOptions)
|
||||
tests.coverage.aggregateAndReport((1..2).map {
|
||||
tempDir.resolve("coverage-report-$it.ic").createFile()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user