diff --git a/README.md b/README.md index 3f7f0e5a..08ed3d8a 100644 --- a/README.md +++ b/README.md @@ -224,6 +224,8 @@ Examples `my.package.ClassName` or `my.*.*Name` are allowed, while `my/package/C Exclusion rules have priority over inclusion ones. +Exclusion and inclusion rules from the [test task](#configuring-jvm-test-task) (if at least one of them is not empty) take precedence over rules from the [common class filter](#configuring-project). + ### Configuring project In the project in which the plugin is applied, you can configure instrumentation and default Kover tasks: diff --git a/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/AdaptersTests.kt b/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/AdaptersTests.kt index 57440f2b..d2bd5774 100644 --- a/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/AdaptersTests.kt +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/AdaptersTests.kt @@ -1,6 +1,5 @@ package kotlinx.kover.test.functional.cases -import kotlinx.kover.test.functional.cases.utils.assertFullyCovered import kotlinx.kover.test.functional.cases.utils.defaultXmlReport import kotlinx.kover.test.functional.cases.utils.defaultMergedXmlReport import kotlinx.kover.test.functional.core.BaseGradleScriptTest diff --git a/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/InstrumentationFilteringTests.kt b/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/InstrumentationFilteringTests.kt index ea7d857b..98c94f00 100644 --- a/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/InstrumentationFilteringTests.kt +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/InstrumentationFilteringTests.kt @@ -23,7 +23,6 @@ internal class InstrumentationFilteringTests : BaseGradleScriptTest() { classCounter("org.jetbrains.SecondClass").assertCovered() } } - } @Test @@ -45,6 +44,65 @@ internal class InstrumentationFilteringTests : BaseGradleScriptTest() { } } } + @Test + fun testExcludeByKoverExtension() { + val build = diverseBuild(ALL_LANGUAGES, ALL_ENGINES, ALL_TYPES) + build.addKoverRootProject { + sourcesFrom("simple") + kover { + filters { + classes { + excludes += "org.jetbrains.*Exa?ple*" + } + } + xmlReport { + overrideFilters { + classes { + // override class filter (discard all rules) to in order for all classes to be included in the report + } + } + } + } + } + val runner = build.prepare() + runner.run("build", "koverXmlReport") { + xml(defaultXmlReport()) { + classCounter("org.jetbrains.ExampleClass").assertFullyMissed() + classCounter("org.jetbrains.SecondClass").assertCovered() + } + } + } + + @Test + fun testExcludeIncludeByKoverExtension() { + val build = diverseBuild(ALL_LANGUAGES, ALL_ENGINES, ALL_TYPES) + build.addKoverRootProject { + sourcesFrom("simple") + kover { + filters { + classes { + includes += "org.jetbrains.*Cla?s" + excludes += "org.jetbrains.*Exa?ple*" + } + } + xmlReport { + overrideFilters { + classes { + // override class filter (discard all rules) to in order for all classes to be included in the report + } + } + } + } + } + val runner = build.prepare() + runner.run("build", "koverXmlReport") { + xml(defaultXmlReport()) { + classCounter("org.jetbrains.ExampleClass").assertFullyMissed() + classCounter("org.jetbrains.Unused").assertFullyMissed() + classCounter("org.jetbrains.SecondClass").assertCovered() + } + } + } @Test fun testDisableInstrumentationOfTask() { diff --git a/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/MultiProjectTests.kt b/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/MultiProjectTests.kt index 413b60b8..7b1279a9 100644 --- a/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/MultiProjectTests.kt +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/MultiProjectTests.kt @@ -71,36 +71,6 @@ internal class MultiProjectTests : BaseGradleScriptTest() { } } } -// -// @Test -// fun testLinkedProjectsReports() { -// builder("Testing the generation of project reports with running all tests for all projects") -// .types(ProjectType.KOTLIN_JVM, ProjectType.KOTLIN_MULTIPLATFORM) -// .engines(CoverageEngineVendor.INTELLIJ, CoverageEngineVendor.JACOCO) -// .configKover { runAllTestsForProjectTask = true } -// .sources("multiproject-user") -// .subproject(subprojectName) { -// sources("multiproject-common") -// } -// .build() -// .run("koverReport") { -// xml(defaultXmlReport()) { -// classCounter("org.jetbrains.CommonClass").assertAbsent() -// classCounter("org.jetbrains.CommonInternalClass").assertAbsent() -// classCounter("org.jetbrains.UserClass").assertFullyCovered() -// } -// -// subproject(subprojectName) { -// xml(defaultXmlReport()) { -// classCounter("org.jetbrains.UserClass").assertAbsent() -// -// // common class fully covered because calls from the root project are counted too -// classCounter("org.jetbrains.CommonClass").assertFullyCovered() -// classCounter("org.jetbrains.CommonInternalClass").assertFullyCovered() -// } -// } -// } -// } @Test fun testDisabledKover() { diff --git a/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/utils/Commons.kt b/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/utils/Commons.kt index 2d6b2b4a..edde69ac 100644 --- a/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/utils/Commons.kt +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/utils/Commons.kt @@ -4,22 +4,18 @@ package kotlinx.kover.test.functional.cases.utils -import kotlinx.kover.api.* -import kotlinx.kover.test.functional.core.* import kotlinx.kover.test.functional.core.RunResult import org.gradle.testkit.runner.* import kotlin.test.* internal fun RunResult.checkDefaultBinaryReport(mustExist: Boolean = true) { - val binary: String = defaultBinaryReport(engine, projectType) - if (mustExist) { - file(binary) { + file(defaultBinaryReport) { assertTrue { exists() } assertTrue { length() > 0 } } } else { - file(binary) { + file(defaultBinaryReport) { assertFalse { exists() } } } @@ -58,49 +54,3 @@ internal fun RunResult.checkReports(xmlPath: String, htmlPath: String, mustExist } } } - -internal fun RunResult.checkIntellijErrors(errorExpected: Boolean = false) { - if (engine != CoverageEngineVendor.INTELLIJ) return - - file(errorsDirectory()) { - if (this.exists() && !errorExpected) { - val errorLogs = this.listFiles()?.map { it.name } ?: emptyList() - throw AssertionError("Detected IntelliJ Coverage Engine errors: $errorLogs") - } - } -} - -internal fun Counter?.assertAbsent() { - assertNull(this) -} - -internal fun Counter?.assertFullyMissed() { - assertNotNull(this) - assertTrue { this.missed > 0 } - assertEquals(0, this.covered) -} - -internal fun Counter?.assertCovered() { - assertNotNull(this) - assertTrue { this.covered > 0 } -} - - -internal fun Counter?.assertTotal(count: Int) { - assertNotNull(this) - assertEquals(count, covered + missed) -} - -internal fun Counter?.assertCovered(covered: Int, missed: Int) { - assertNotNull(this) - assertEquals(covered, this.covered) - assertEquals(missed, this.missed) -} - -internal fun Counter?.assertFullyCovered() { - assertNotNull(this) - assertTrue { this.covered > 0 } - assertEquals(0, this.missed) -} - - diff --git a/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/utils/Defaults.kt b/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/utils/Defaults.kt index 09e836af..dbee6f1a 100644 --- a/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/utils/Defaults.kt +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/utils/Defaults.kt @@ -4,23 +4,17 @@ package kotlinx.kover.test.functional.cases.utils -import kotlinx.kover.api.* import kotlinx.kover.test.functional.core.ProjectType -internal fun defaultTestTask(engine: CoverageEngineVendor, projectType: ProjectType): String { - val extension = if (engine == CoverageEngineVendor.INTELLIJ) "ic" else "exec" +internal fun defaultTestTask(projectType: ProjectType): String { return when (projectType) { - ProjectType.KOTLIN_JVM -> "test.$extension" - ProjectType.KOTLIN_MULTIPLATFORM -> "jvmTest.$extension" - ProjectType.ANDROID -> "jvmTest.$extension" + ProjectType.KOTLIN_JVM -> "test" + ProjectType.KOTLIN_MULTIPLATFORM -> "jvmTest" + ProjectType.ANDROID -> "jvmTest" } } -internal fun defaultBinaryReport(engine: CoverageEngineVendor, projectType: ProjectType): String { - return "kover/" + defaultTestTask(engine, projectType) -} - internal fun defaultMergedXmlReport() = "reports/kover/merged/xml/report.xml" internal fun defaultMergedHtmlReport() = "reports/kover/merged/html" @@ -28,3 +22,5 @@ internal fun defaultXmlReport() = "reports/kover/xml/report.xml" internal fun defaultHtmlReport() = "reports/kover/html" internal fun errorsDirectory() = "kover/errors" + +internal fun binaryReportsDirectory() = "kover" diff --git a/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/Runner.kt b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/Runner.kt index f90583f9..7c1c8d36 100644 --- a/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/Runner.kt +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/Runner.kt @@ -10,6 +10,7 @@ import org.gradle.testkit.runner.* import org.w3c.dom.* import java.io.* import javax.xml.parsers.* +import kotlin.test.* internal class DiverseGradleRunner(private val projects: Map, private val extraArgs: List) : @@ -19,10 +20,10 @@ internal class DiverseGradleRunner(private val projects: Map val argsList = listOf(*args) + extraArgs projects.forEach { (slice, project) -> try { - val buildResult = project.runGradle(argsList) - RunResultImpl(buildResult, project).apply { checkIntellijErrors() }.apply(checker) - } catch (e: UnexpectedBuildFailure) { - throw AssertionError("Assertion error occurred in test for project $slice", e) + val gradleResult = project.runGradle(argsList) + RunResultImpl(slice, project, gradleResult).apply(checker) + } catch (e: Throwable) { + throw AssertionError("Error occurred in test for project $slice", e) } } return this @@ -34,7 +35,7 @@ internal class DiverseGradleRunner(private val projects: Map project.runGradleWithError(argsList) throw AssertionError("Assertion error expected in test for project $slice") } catch (e: UnexpectedBuildFailure) { - RunResultImpl(e.buildResult, project).apply { checkIntellijErrors() }.apply(errorChecker) + RunResultImpl(slice, project, e.buildResult).apply(errorChecker) } } return this @@ -44,7 +45,7 @@ internal class DiverseGradleRunner(private val projects: Map internal class SingleGradleRunnerImpl(private val projectDir: File) : GradleRunner { override fun run(vararg args: String, checker: RunResult.() -> Unit): SingleGradleRunnerImpl { val buildResult = projectDir.runGradle(listOf(*args)) - RunResultImpl(buildResult, projectDir).apply { checkIntellijErrors() }.apply(checker) + RunResultImpl(null, projectDir, buildResult).apply(checker) return this } @@ -53,7 +54,7 @@ internal class SingleGradleRunnerImpl(private val projectDir: File) : GradleRunn projectDir.runGradleWithError(listOf(*args)) throw AssertionError("Assertion error expected in test") } catch (e: UnexpectedBuildFailure) { - RunResultImpl(e.buildResult, projectDir).apply { checkIntellijErrors() }.apply(errorChecker) + RunResultImpl(null, projectDir, e.buildResult).apply(errorChecker) } return this } @@ -89,45 +90,38 @@ private fun org.gradle.testkit.runner.GradleRunner.addPluginTestRuntimeClasspath private class RunResultImpl( - private val result: BuildResult, + private val slice: ProjectSlice?, private val dir: File, + private val result: BuildResult, private val path: String = ":" ) : RunResult { val buildDir: File = File(dir, "build") + override val defaultBinaryReport: String + get() { + // IntelliJ is a default Engine + val extension = if (slice?.engine == CoverageEngineVendor.JACOCO) "exec" else "ic" + return binaryReportsDirectory() + "/" + defaultTestTask(slice?.type ?: ProjectType.KOTLIN_JVM) + "." + extension + } + private val buildScriptFile: File = buildFile() private val buildScript: String by lazy { buildScriptFile.readText() } - override val engine: CoverageEngineVendor by lazy { - if (buildScript.contains("set(kotlinx.kover.api.CoverageEngine.JACOCO)")) { - CoverageEngineVendor.JACOCO - } else { - CoverageEngineVendor.INTELLIJ - } + init { + checkIntellijErrors() } - override val projectType: ProjectType by lazy { - if (buildScriptFile.name.substringAfterLast(".") == "kts") { - if (buildScript.contains("""kotlin("jvm")""")) { - ProjectType.KOTLIN_JVM - } else if (buildScript.contains("""kotlin("multiplatform")""")) { - ProjectType.KOTLIN_MULTIPLATFORM - } else { - throw IllegalArgumentException("Impossible to determine the type of project") - } - } else { - if (buildScript.contains("""id "org.jetbrains.kotlin.jvm"""")) { - ProjectType.KOTLIN_JVM - } else if (buildScript.contains("""id "org.jetbrains.kotlin.multiplatform"""")) { - ProjectType.KOTLIN_MULTIPLATFORM - } else { - throw IllegalArgumentException("Impossible to determine the type of project") - } - } + override fun subproject(name: String, checker: RunResult.() -> Unit) { + RunResultImpl(slice, File(dir, name), result, "$path$name:").also(checker) } - override fun subproject(name: String, checker: RunResult.() -> Unit) { - RunResultImpl(result, File(dir, name), "$path$name:").also(checker) + private fun RunResult.checkIntellijErrors() { + file(errorsDirectory()) { + if (this.exists()) { + val errorLogs = this.listFiles()?.map { it.name } ?: return@file + throw AssertionError("Detected Coverage Agent errors: $errorLogs") + } + } } override fun output(checker: String.() -> Unit) { @@ -141,7 +135,7 @@ private class RunResultImpl( override fun xml(filename: String, checker: XmlReport.() -> Unit) { val xmlFile = File(buildDir, filename) if (!xmlFile.exists()) throw IllegalStateException("XML file '$filename' not found") - XmlReportImpl(xmlFile).checker() + XmlReportImpl(this, xmlFile).checker() } override fun outcome(taskName: String, checker: TaskOutcome.() -> Unit) { @@ -156,53 +150,88 @@ private class RunResultImpl( return File(dir, "build.gradle.kts") } } +private data class CounterValues(val missed: Int, val covered: Int) +private class CounterImpl(val context: RunResultImpl, val symbol: String, val type: String, val values: CounterValues?): Counter { + override fun assertAbsent() { + assertNull(values, "Counter '$symbol' with type '$type' isn't absent") + } + override fun assertFullyMissed() { + assertNotNull(values, "Counter '$symbol' with type '$type' isn't fully missed because it absent") + assertTrue(values.missed > 0, "Counter '$symbol' with type '$type' isn't fully missed") + assertEquals(0, values.covered, "Counter '$symbol' with type '$type' isn't fully missed") + } -private class XmlReportImpl(file: File) : XmlReport { + override fun assertCovered() { + assertNotNull(values, "Counter '$symbol' with type '$type' isn't covered because it absent") + assertTrue(values.covered > 0, "Counter '$symbol' with type '$type' isn't covered") + } + + override fun assertTotal(expectedTotal: Int) { + assertNotNull(values, "Counter '$symbol' with type '$type' is absent so total value can't be checked") + val actual = values.covered + values.missed + assertEquals(expectedTotal, actual, "Expected total value $expectedTotal but actual $actual for counter '$symbol' with type '$type'") + } + + override fun assertCovered(covered: Int, missed: Int) { + assertNotNull(values, "Counter '$symbol' with type '$type' is absent so covered can't be checked") + assertEquals(covered, values.covered, "Expected covered value $covered but actual ${values.covered} for counter '$symbol' with type '$type'") + assertEquals(missed, values.missed, "Expected covered value $missed but actual ${values.missed} for counter '$symbol' with type '$type'") + } + + override fun assertFullyCovered() { + assertNotNull(values, "Counter '$symbol' with type '$type' is absent so fully covered can't be checked") + assertTrue(values.covered > 0, "Counter '$symbol' with type '$type' isn't fully covered") + assertEquals(0, values.missed, "Counter '$symbol' with type '$type' isn't fully covered") + } +} + + +private class XmlReportImpl(val context: RunResultImpl, file: File) : XmlReport { private val document = DocumentBuilderFactory.newInstance() // This option disables checking the dtd file for JaCoCo XML file .also { it.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false) } .newDocumentBuilder().parse(file) - override fun classCounter(className: String, type: String): Counter? { + override fun classCounter(className: String, type: String): Counter { val correctedClassName = className.replace('.', '/') val packageName = correctedClassName.substringBeforeLast('/') val reportElement = ((document.getElementsByTagName("report").item(0)) as Element) - val counterElement: Element? = reportElement + val values = reportElement .filter("package", "name", packageName) ?.filter("class", "name", correctedClassName) ?.filter("counter", "type", type) + ?.let { + CounterValues( + it.getAttribute("missed").toInt(), + it.getAttribute("covered").toInt() + ) + } - return counterElement?.let { - Counter( - type, - it.getAttribute("missed").toInt(), - it.getAttribute("covered").toInt() - ) - } + return CounterImpl(context, className, type, values) } - override fun methodCounter(className: String, methodName: String, type: String): Counter? { + override fun methodCounter(className: String, methodName: String, type: String): Counter { val correctedClassName = className.replace('.', '/') val packageName = correctedClassName.substringBeforeLast('/') val reportElement = ((document.getElementsByTagName("report").item(0)) as Element) - val counterElement: Element? = reportElement + val values = reportElement .filter("package", "name", packageName) ?.filter("class", "name", correctedClassName) ?.filter("method", "name", methodName) ?.filter("counter", "type", type) + ?.let { + CounterValues( + it.getAttribute("missed").toInt(), + it.getAttribute("covered").toInt() + ) + } - return counterElement?.let { - Counter( - type, - it.getAttribute("missed").toInt(), - it.getAttribute("covered").toInt() - ) - } + return CounterImpl(context, "$className#$methodName", type, values) } } diff --git a/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/Types.kt b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/Types.kt index 602ed310..381c3666 100644 --- a/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/Types.kt +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/Types.kt @@ -123,9 +123,6 @@ internal interface GradleRunner { } internal interface RunResult { - val engine: CoverageEngineVendor - val projectType: ProjectType - fun subproject(name: String, checker: RunResult.() -> Unit) fun output(checker: String.() -> Unit) @@ -135,15 +132,26 @@ internal interface RunResult { fun xml(filename: String, checker: XmlReport.() -> Unit) fun outcome(taskName: String, checker: TaskOutcome.() -> Unit) + + val defaultBinaryReport: String } -internal class Counter(val type: String, val missed: Int, val covered: Int) { - val isEmpty: Boolean - get() = missed == 0 && covered == 0 +internal interface Counter { + fun assertAbsent() + + fun assertFullyMissed() + + fun assertCovered() + + fun assertTotal(expectedTotal: Int) + + fun assertCovered(covered: Int, missed: Int) + + fun assertFullyCovered() } internal interface XmlReport { - fun classCounter(className: String, type: String = "INSTRUCTION"): Counter? - fun methodCounter(className: String, methodName: String, type: String = "INSTRUCTION"): Counter? + fun classCounter(className: String, type: String = "INSTRUCTION"): Counter + fun methodCounter(className: String, methodName: String, type: String = "INSTRUCTION"): Counter } diff --git a/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/KoverExtensionWriter.kt b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/KoverExtensionWriter.kt index 8325dc99..21fd6966 100644 --- a/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/KoverExtensionWriter.kt +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/KoverExtensionWriter.kt @@ -11,13 +11,21 @@ import kotlinx.kover.test.functional.core.TestKoverProjectConfigState import java.io.* internal fun PrintWriter.printKover(kover: TestKoverProjectConfigState?, slice: ProjectSlice, indents: Int) { - if (kover == null) return + if (kover == null) { + if (slice.engine != null) { + indented(indents, "kover {") + printEngine(null, slice, indents + 1) + indented(indents, "}") + } + return + } indented(indents, "kover {") printDisabled(kover.isDisabled, slice, indents + 1) printEngine(kover.engine, slice, indents + 1) printInstrumentation(kover.instrumentation, slice, indents + 1) printFilters(kover.filters, slice, indents + 1) + printXmlReport(kover.xml, slice, indents + 1) printVerify(kover.verify, slice, indents + 1) indented(indents, "}") } @@ -78,6 +86,24 @@ private fun PrintWriter.printFilters(state: TestKoverProjectFiltersState, slice: indented(indents, "}") } +private fun PrintWriter.printXmlReport(state: TestKoverProjectXmlConfigState, slice: ProjectSlice, indents: Int) { + val overrideFilters = state.overrideFilters + if (state.onCheck == null && state.reportFile == null && overrideFilters == null) return + + indented(indents, "xmlReport {") + if (overrideFilters != null) { + indented(indents + 1, "overrideFilters {") + val classFilter = overrideFilters.classes + if (classFilter != null) { + indented(indents + 2, "classes {") + printClassFilter(classFilter, slice, indents + 3) + indented(indents + 2, "}") + } + indented(indents + 1, "}") + } + indented(indents, "}") +} + private fun PrintWriter.printInstrumentation(state: KoverProjectInstrumentation, slice: ProjectSlice, indents: Int) { if (state.excludeTasks.isEmpty()) return diff --git a/src/main/kotlin/kotlinx/kover/appliers/KoverTaskApplier.kt b/src/main/kotlin/kotlinx/kover/appliers/KoverTaskApplier.kt index 0e1853e1..6b7f7013 100644 --- a/src/main/kotlin/kotlinx/kover/appliers/KoverTaskApplier.kt +++ b/src/main/kotlin/kotlinx/kover/appliers/KoverTaskApplier.kt @@ -27,7 +27,17 @@ internal fun Test.applyToTestTask( projectExtension.instrumentation.excludeTasks.contains(name)) } - jvmArgumentProviders.add(CoverageArgumentProvider(this, engineProvider, disabledProvider, extension)) + val filtersProvider = project.filtersProvider(projectExtension, extension) + + jvmArgumentProviders.add( + CoverageArgumentProvider( + this, + filtersProvider, + engineProvider, + disabledProvider, + extension.reportFile + ) + ) val sourceErrorProvider = project.provider { File(extension.reportFile.get().asFile.parentFile, "coverage-error.log") @@ -43,10 +53,11 @@ private fun Task.createTaskExtension(projectExtension: KoverProjectConfig): Kove extensions.create(KoverNames.TASK_EXTENSION_NAME, KoverTaskExtension::class.java, project.objects) taskExtension.isDisabled.set(false) + val layout = project.layout taskExtension.reportFile.set(project.provider { val engine = projectExtension.engine.get() val suffix = if (engine.vendor == CoverageEngineVendor.INTELLIJ) ".ic" else ".exec" - project.layout.buildDirectory.get().file("kover/$name$suffix") + layout.buildDirectory.get().file("kover/$name$suffix") }) return taskExtension @@ -55,9 +66,10 @@ private fun Task.createTaskExtension(projectExtension: KoverProjectConfig): Kove private class CoverageArgumentProvider( private val task: Task, + @get:Nested val filtersProvider: Provider, @get:Nested val engineProvider: Provider, @get:Input val disabledProvider: Provider, - @get:Nested val taskExtension: KoverTaskExtension + @get:OutputFile val reportFileProvider: Provider ) : CommandLineArgumentProvider, Named { @Internal override fun getName(): String { @@ -69,11 +81,9 @@ private class CoverageArgumentProvider( return mutableListOf() } - val reportFile = taskExtension.reportFile.get().asFile - val classFilter = KoverClassFilter() + val reportFile = reportFileProvider.get().asFile + var filters = filtersProvider.get() - classFilter.includes += taskExtension.includes.get() - classFilter.excludes += taskExtension.excludes.get() /* The instrumentation of android classes often causes errors when using third-party frameworks (see https://github.com/Kotlin/kotlinx-kover/issues/89). @@ -83,10 +93,9 @@ private class CoverageArgumentProvider( FIXME Remove this code if the IntelliJ Agent stops changing project classes during instrumentation, see https://github.com/Kotlin/kotlinx-kover/issues/196 */ - classFilter.excludes += "android.*" - classFilter.excludes += "com.android.*" + filters = filters.appendExcludedTo("android.*", "com.android.*") - return EngineManager.buildAgentArgs(engineProvider.get(), task, reportFile, classFilter) + return EngineManager.buildAgentArgs(engineProvider.get(), task, reportFile, filters) } } @@ -127,3 +136,21 @@ private class MoveIntellijErrorLogAction( } } } + +private fun Project.filtersProvider( + projectConfig: KoverProjectConfig, + taskConfig: KoverTaskExtension +): Provider { + return provider { + val overrideExcludes = taskConfig.excludes.get() + val overrideIncludes = taskConfig.includes.get() + val commonClassFilter = projectConfig.filters.classes.orNull + + if (commonClassFilter == null || overrideIncludes.isNotEmpty() || overrideExcludes.isNotEmpty()) { + // the rules from the task take precedence over the common filters + AgentFilters(overrideIncludes, overrideExcludes) + } else { + AgentFilters(commonClassFilter.includes.toList(), commonClassFilter.excludes.toList()) + } + } +} diff --git a/src/main/kotlin/kotlinx/kover/engines/commons/AgentFilters.kt b/src/main/kotlin/kotlinx/kover/engines/commons/AgentFilters.kt new file mode 100644 index 00000000..3b00a98d --- /dev/null +++ b/src/main/kotlin/kotlinx/kover/engines/commons/AgentFilters.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.kover.engines.commons + +import org.gradle.api.tasks.Input + + +internal data class AgentFilters( + @get:Input val includesClasses: List, + @get:Input val excludesClasses: List +) { + fun appendExcludedTo(vararg excludesClasses: String): AgentFilters { + return AgentFilters( + this.includesClasses, + this.excludesClasses + excludesClasses + ) + } +} diff --git a/src/main/kotlin/kotlinx/kover/engines/commons/EngineManager.kt b/src/main/kotlin/kotlinx/kover/engines/commons/EngineManager.kt index 6dea8b3f..37506408 100644 --- a/src/main/kotlin/kotlinx/kover/engines/commons/EngineManager.kt +++ b/src/main/kotlin/kotlinx/kover/engines/commons/EngineManager.kt @@ -21,13 +21,13 @@ internal object EngineManager { details: EngineDetails, task: Task, reportFile: File, - classFilter: KoverClassFilter + filters: AgentFilters ): MutableList { return if (details.variant.vendor == CoverageEngineVendor.INTELLIJ) { - task.buildIntellijAgentJvmArgs(details.jarFile, reportFile, classFilter) + task.buildIntellijAgentJvmArgs(details.jarFile, reportFile, filters) } else { reportFile.parentFile.mkdirs() - task.buildJacocoAgentJvmArgs(details.jarFile, reportFile, classFilter) + task.buildJacocoAgentJvmArgs(details.jarFile, reportFile, filters) } } diff --git a/src/main/kotlin/kotlinx/kover/engines/intellij/IntellijAgent.kt b/src/main/kotlin/kotlinx/kover/engines/intellij/IntellijAgent.kt index 93716c33..1497558e 100644 --- a/src/main/kotlin/kotlinx/kover/engines/intellij/IntellijAgent.kt +++ b/src/main/kotlin/kotlinx/kover/engines/intellij/IntellijAgent.kt @@ -4,7 +4,6 @@ package kotlinx.kover.engines.intellij -import kotlinx.kover.api.* import kotlinx.kover.engines.commons.* import org.gradle.api.Task import java.io.* @@ -14,9 +13,9 @@ private const val calculateForUnloadedClasses = false // a flag to calculate cov private const val appendToDataFile = true // a flag to use data file as initial coverage private const val samplingMode = false //a flag to run coverage in sampling mode or in tracing mode otherwise -internal fun Task.buildIntellijAgentJvmArgs(jarFile: File, reportFile: File, classFilter: KoverClassFilter): MutableList { +internal fun Task.buildIntellijAgentJvmArgs(jarFile: File, reportFile: File, filters: AgentFilters): MutableList { val argsFile = File(temporaryDir, "intellijagent.args") - argsFile.writeAgentArgs(reportFile, classFilter) + argsFile.writeAgentArgs(reportFile, filters) return mutableListOf( "-javaagent:${jarFile.canonicalPath}=${argsFile.canonicalPath}", @@ -27,7 +26,7 @@ internal fun Task.buildIntellijAgentJvmArgs(jarFile: File, reportFile: File, cla ) } -private fun File.writeAgentArgs(reportFile: File, classFilter: KoverClassFilter) { +private fun File.writeAgentArgs(reportFile: File, filters: AgentFilters) { reportFile.parentFile.mkdirs() val binaryPath = reportFile.canonicalPath @@ -37,16 +36,16 @@ private fun File.writeAgentArgs(reportFile: File, classFilter: KoverClassFilter) pw.appendLine(calculateForUnloadedClasses.toString()) pw.appendLine(appendToDataFile.toString()) pw.appendLine(samplingMode.toString()) - classFilter.includes.forEach { i -> + filters.includesClasses.forEach { i -> pw.appendLine(i.wildcardsToRegex()) } - if (classFilter.excludes.isNotEmpty()) { + val excludesClasses = filters.excludesClasses + if (excludesClasses.isNotEmpty()) { pw.appendLine("-exclude") - } - - classFilter.excludes.forEach { e -> - pw.appendLine(e.wildcardsToRegex()) + excludesClasses.forEach { e -> + pw.appendLine(e.wildcardsToRegex()) + } } } } diff --git a/src/main/kotlin/kotlinx/kover/engines/jacoco/JacocoAgent.kt b/src/main/kotlin/kotlinx/kover/engines/jacoco/JacocoAgent.kt index 1bc31b02..f045ddfb 100644 --- a/src/main/kotlin/kotlinx/kover/engines/jacoco/JacocoAgent.kt +++ b/src/main/kotlin/kotlinx/kover/engines/jacoco/JacocoAgent.kt @@ -4,15 +4,15 @@ package kotlinx.kover.engines.jacoco -import kotlinx.kover.api.* +import kotlinx.kover.engines.commons.* import org.gradle.api.Task import java.io.* -internal fun Task.buildJacocoAgentJvmArgs(jarFile: File, reportFile: File, classFilter: KoverClassFilter): MutableList { +internal fun Task.buildJacocoAgentJvmArgs(jarFile: File, reportFile: File, filters: AgentFilters): MutableList { val agentArgs = listOfNotNull( "destfile=${reportFile.canonicalPath},append=true,inclnolocationclasses=false,dumponexit=true,output=file,jmx=false", - classFilter.includes.joinToFilterString("includes"), - classFilter.excludes.joinToFilterString("excludes") + filters.includesClasses.joinToFilterString("includes"), + filters.excludesClasses.joinToFilterString("excludes") ).joinToString(",") return mutableListOf("-javaagent:${jarFile.canonicalPath}=$agentArgs")