From 07707f87e65327cb1d4593789657f42fb3b15873 Mon Sep 17 00:00:00 2001 From: "Sergey.Shanshin" Date: Thu, 16 Jun 2022 12:22:27 +0300 Subject: [PATCH 01/10] Designed and implemented Kover API version 2 Resolves #19 Resolves #128 Resolves #168 --- .../main/kotlin/PublicationMavenCentral.kt | 2 +- gradle.properties | 2 +- .../test/functional/cases/AdaptersTests.kt | 4 +- .../cases/ConfigurationCacheTests.kt | 23 +- .../functional/cases/DefaultConfigTests.kt | 23 +- .../cases/InstrumentationFilteringTests.kt | 65 ++- .../functional/cases/MultiProjectTests.kt | 300 ++++++++------ .../functional/cases/ReportsCachingTests.kt | 95 ++--- .../functional/cases/ReportsFilteringTests.kt | 74 ++-- .../functional/cases/VerificationTests.kt | 138 ++++--- .../cases/counters/CountersValueTests.kt | 69 ++-- .../test/functional/cases/utils/Commons.kt | 2 +- .../test/functional/cases/utils/Defaults.kt | 12 +- .../functional/core/BaseGradleScriptTest.kt | 68 ++- .../kover/test/functional/core/Builder.kt | 294 ++++++++----- .../kover/test/functional/core/Runner.kt | 20 +- .../kover/test/functional/core/Types.kt | 120 ++++-- .../kover/test/functional/core/Writer.kt | 264 ------------ .../functional/core/writer/CommonWriter.kt | 64 +++ .../core/writer/KoverExtensionWriter.kt | 79 ++++ .../test/functional/core/writer/Languages.kt | 65 +++ .../core/writer/MergedExtensionWriter.kt | 47 +++ .../core/writer/SettingsFileWriter.kt | 37 ++ .../test/functional/core/writer/Writer.kt | 290 +++++++++++++ .../different-plugins/build.gradle.kts | 2 + .../subproject-multiplatform/build.gradle.kts | 1 + .../scripts/buildscripts/groovy/kjvm/root | 13 - .../buildscripts/groovy/kjvm/subproject | 12 - .../scripts/buildscripts/groovy/kjvm/testTask | 5 - .../scripts/buildscripts/groovy/kmp/root | 26 -- .../buildscripts/groovy/kmp/subproject | 25 -- .../scripts/buildscripts/groovy/kmp/testTask | 5 - .../scripts/buildscripts/kotlin/kjvm/root | 13 - .../buildscripts/kotlin/kjvm/subproject | 12 - .../scripts/buildscripts/kotlin/kjvm/testTask | 5 - .../scripts/buildscripts/kotlin/kmp/root | 26 -- .../buildscripts/kotlin/kmp/subproject | 25 -- .../scripts/buildscripts/kotlin/kmp/testTask | 5 - .../scripts/settings/settings.gradle | 9 - .../scripts/settings/settings.gradle.kts | 9 - src/main/kotlin/kotlinx/kover/KoverPlugin.kt | 391 +----------------- src/main/kotlin/kotlinx/kover/Providers.kt | 135 ------ .../kover/adapters/AndroidPluginAdapter.kt | 33 -- .../adapters/KotlinAndroidPluginAdapter.kt | 37 -- .../kover/adapters/OldJavaPluginAdapter.kt | 30 -- .../kover/adapters/PluginAdaptersFactory.kt | 22 - .../adapters/api/CompilationPluginAdapter.kt | 29 -- .../kotlinx/kover/api/CoverageEngines.kt | 41 ++ .../kotlin/kotlinx/kover/api/KoverConfig.kt | 322 +++++++++++++++ .../kotlinx/kover/api/KoverConstants.kt | 28 +- .../kotlinx/kover/api/KoverExtension.kt | 73 ---- .../kotlinx/kover/api/KoverTaskExtension.kt | 29 +- src/main/kotlin/kotlinx/kover/api/Rules.kt | 80 ---- .../kover/appliers/KoverMergedApplier.kt | 253 ++++++++++++ .../kover/appliers/KoverProjectApplier.kt | 231 +++++++++++ .../kover/appliers/KoverTaskApplier.kt | 130 ++++++ .../kover/engines/commons/AgentsFactory.kt | 19 - .../kover/engines/commons/CoverageAgent.kt | 11 - .../kover/engines/commons/EngineManager.kt | 82 ++++ .../kotlinx/kover/engines/commons/Reports.kt | 28 +- .../kover/engines/intellij/IntellijAgent.kt | 109 ++--- .../kover/engines/intellij/IntellijEngine.kt | 40 ++ .../kover/engines/intellij/IntellijReports.kt | 23 +- .../engines/intellij/IntellijVerification.kt | 174 +++++--- .../kover/engines/intellij/Versions.kt | 40 -- .../kover/engines/jacoco/JacocoAgent.kt | 67 +-- .../kover/engines/jacoco/JacocoEngine.kt | 10 + .../kover/engines/jacoco/JacocoReports.kt | 113 ++--- .../kotlin/kotlinx/kover/lookup/DirsLookup.kt | 72 ++++ .../lookup/adapters/AndroidPluginAdapter.kt | 25 ++ .../adapters/KotlinAndroidPluginAdapter.kt | 33 ++ .../KotlinMultiplatformPluginAdapter.kt | 58 ++- .../lookup/adapters/OldJavaPluginAdapter.kt | 29 ++ .../kover/tasks/KoverCollectingTask.kt | 49 --- .../kotlinx/kover/tasks/KoverHtmlReport.kt | 77 ---- .../kotlinx/kover/tasks/KoverHtmlTask.kt | 37 ++ .../kotlinx/kover/tasks/KoverMergedTask.kt | 96 ----- .../kotlinx/kover/tasks/KoverProjectTask.kt | 73 ---- .../kotlinx/kover/tasks/KoverReportTask.kt | 64 +++ .../kotlinx/kover/tasks/KoverVerification.kt | 107 ----- .../kover/tasks/KoverVerificationTask.kt | 58 +++ .../kotlinx/kover/tasks/KoverXmlReport.kt | 71 ---- .../kotlinx/kover/tasks/KoverXmlTask.kt | 27 ++ 83 files changed, 3157 insertions(+), 2649 deletions(-) delete mode 100644 src/functionalTest/kotlin/kotlinx/kover/test/functional/core/Writer.kt create mode 100644 src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/CommonWriter.kt create mode 100644 src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/KoverExtensionWriter.kt create mode 100644 src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/Languages.kt create mode 100644 src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/MergedExtensionWriter.kt create mode 100644 src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/SettingsFileWriter.kt create mode 100644 src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/Writer.kt delete mode 100644 src/functionalTest/templates/scripts/buildscripts/groovy/kjvm/root delete mode 100644 src/functionalTest/templates/scripts/buildscripts/groovy/kjvm/subproject delete mode 100644 src/functionalTest/templates/scripts/buildscripts/groovy/kjvm/testTask delete mode 100644 src/functionalTest/templates/scripts/buildscripts/groovy/kmp/root delete mode 100644 src/functionalTest/templates/scripts/buildscripts/groovy/kmp/subproject delete mode 100644 src/functionalTest/templates/scripts/buildscripts/groovy/kmp/testTask delete mode 100644 src/functionalTest/templates/scripts/buildscripts/kotlin/kjvm/root delete mode 100644 src/functionalTest/templates/scripts/buildscripts/kotlin/kjvm/subproject delete mode 100644 src/functionalTest/templates/scripts/buildscripts/kotlin/kjvm/testTask delete mode 100644 src/functionalTest/templates/scripts/buildscripts/kotlin/kmp/root delete mode 100644 src/functionalTest/templates/scripts/buildscripts/kotlin/kmp/subproject delete mode 100644 src/functionalTest/templates/scripts/buildscripts/kotlin/kmp/testTask delete mode 100644 src/functionalTest/templates/scripts/settings/settings.gradle delete mode 100644 src/functionalTest/templates/scripts/settings/settings.gradle.kts delete mode 100644 src/main/kotlin/kotlinx/kover/Providers.kt delete mode 100644 src/main/kotlin/kotlinx/kover/adapters/AndroidPluginAdapter.kt delete mode 100644 src/main/kotlin/kotlinx/kover/adapters/KotlinAndroidPluginAdapter.kt delete mode 100644 src/main/kotlin/kotlinx/kover/adapters/OldJavaPluginAdapter.kt delete mode 100644 src/main/kotlin/kotlinx/kover/adapters/PluginAdaptersFactory.kt delete mode 100644 src/main/kotlin/kotlinx/kover/adapters/api/CompilationPluginAdapter.kt create mode 100644 src/main/kotlin/kotlinx/kover/api/CoverageEngines.kt create mode 100644 src/main/kotlin/kotlinx/kover/api/KoverConfig.kt delete mode 100644 src/main/kotlin/kotlinx/kover/api/KoverExtension.kt delete mode 100644 src/main/kotlin/kotlinx/kover/api/Rules.kt create mode 100644 src/main/kotlin/kotlinx/kover/appliers/KoverMergedApplier.kt create mode 100644 src/main/kotlin/kotlinx/kover/appliers/KoverProjectApplier.kt create mode 100644 src/main/kotlin/kotlinx/kover/appliers/KoverTaskApplier.kt delete mode 100644 src/main/kotlin/kotlinx/kover/engines/commons/AgentsFactory.kt delete mode 100644 src/main/kotlin/kotlinx/kover/engines/commons/CoverageAgent.kt create mode 100644 src/main/kotlin/kotlinx/kover/engines/commons/EngineManager.kt create mode 100644 src/main/kotlin/kotlinx/kover/engines/intellij/IntellijEngine.kt delete mode 100644 src/main/kotlin/kotlinx/kover/engines/intellij/Versions.kt create mode 100644 src/main/kotlin/kotlinx/kover/engines/jacoco/JacocoEngine.kt create mode 100644 src/main/kotlin/kotlinx/kover/lookup/DirsLookup.kt create mode 100644 src/main/kotlin/kotlinx/kover/lookup/adapters/AndroidPluginAdapter.kt create mode 100644 src/main/kotlin/kotlinx/kover/lookup/adapters/KotlinAndroidPluginAdapter.kt rename src/main/kotlin/kotlinx/kover/{ => lookup}/adapters/KotlinMultiplatformPluginAdapter.kt (50%) create mode 100644 src/main/kotlin/kotlinx/kover/lookup/adapters/OldJavaPluginAdapter.kt delete mode 100644 src/main/kotlin/kotlinx/kover/tasks/KoverCollectingTask.kt delete mode 100644 src/main/kotlin/kotlinx/kover/tasks/KoverHtmlReport.kt create mode 100644 src/main/kotlin/kotlinx/kover/tasks/KoverHtmlTask.kt delete mode 100644 src/main/kotlin/kotlinx/kover/tasks/KoverMergedTask.kt delete mode 100644 src/main/kotlin/kotlinx/kover/tasks/KoverProjectTask.kt create mode 100644 src/main/kotlin/kotlinx/kover/tasks/KoverReportTask.kt delete mode 100644 src/main/kotlin/kotlinx/kover/tasks/KoverVerification.kt create mode 100644 src/main/kotlin/kotlinx/kover/tasks/KoverVerificationTask.kt delete mode 100644 src/main/kotlin/kotlinx/kover/tasks/KoverXmlReport.kt create mode 100644 src/main/kotlin/kotlinx/kover/tasks/KoverXmlTask.kt diff --git a/buildSrc/src/main/kotlin/PublicationMavenCentral.kt b/buildSrc/src/main/kotlin/PublicationMavenCentral.kt index ce2ee67c..14d0f56e 100644 --- a/buildSrc/src/main/kotlin/PublicationMavenCentral.kt +++ b/buildSrc/src/main/kotlin/PublicationMavenCentral.kt @@ -1,5 +1,5 @@ /* - * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ import org.gradle.api.* diff --git a/gradle.properties b/gradle.properties index 92f2dfd3..0a3cd728 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=0.5.1 +version=0.6.0-SNAPSHOT group=org.jetbrains.kotlinx kotlin.code.style=official 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 4b392e11..57440f2b 100644 --- a/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/AdaptersTests.kt +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/AdaptersTests.kt @@ -14,7 +14,7 @@ internal class AdaptersTests : BaseGradleScriptTest() { Classes from plugins applied in subproject not accessible for Kover in root project. Therefore, Kover is forced to use reflection to work with extensions of the kotlin multiplatform plugin. */ - internalSample("different-plugins") + sampleBuild("different-plugins") .run("koverMergedXmlReport") { xml(defaultMergedXmlReport()) { classCounter("org.jetbrains.CommonClass").assertFullyCovered() @@ -22,7 +22,7 @@ internal class AdaptersTests : BaseGradleScriptTest() { } } - internalSample("different-plugins") + sampleBuild("different-plugins") .run("koverXmlReport") { subproject("subproject-multiplatform") { xml(defaultXmlReport()) { diff --git a/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/ConfigurationCacheTests.kt b/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/ConfigurationCacheTests.kt index 5c2d89bb..125a3be0 100644 --- a/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/ConfigurationCacheTests.kt +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/ConfigurationCacheTests.kt @@ -3,12 +3,25 @@ package kotlinx.kover.test.functional.cases import kotlinx.kover.test.functional.core.* import kotlin.test.* -internal class ConfigurationCacheTests: BaseGradleScriptTest() { +internal class ConfigurationCacheTests : BaseGradleScriptTest() { @Test fun testConfigCache() { - builder("Testing configuration cache support") - .sources("simple") - .build() - .run("build", "koverMergedReport", "koverMergedVerify", "koverReport", "koverVerify", "--configuration-cache") + val build = diverseBuild() + build.addKoverRootProject { + sourcesFrom("simple") + koverMerged { + enable() + } + } + + val runner = build.prepare() + runner.run( + "build", + "koverMergedReport", + "koverMergedVerify", + "koverReport", + "koverVerify", + "--configuration-cache" + ) } } diff --git a/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/DefaultConfigTests.kt b/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/DefaultConfigTests.kt index a95f1a07..75486281 100644 --- a/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/DefaultConfigTests.kt +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/DefaultConfigTests.kt @@ -7,15 +7,20 @@ import kotlin.test.* internal class DefaultConfigTests : BaseGradleScriptTest() { @Test fun testImplicitConfigs() { - builder("Test implicit default settings") - .languages(GradleScriptLanguage.GROOVY, GradleScriptLanguage.KOTLIN) - .types(ProjectType.KOTLIN_JVM, ProjectType.KOTLIN_MULTIPLATFORM) - .sources("simple") - .build() - .run("build") { - checkDefaultBinaryReport() - checkDefaultMergedReports() - } + val build = diverseBuild( + languages = ALL_LANGUAGES, + types = ALL_TYPES + ) + build.addKoverRootProject { + sourcesFrom("simple") + } + val runner = build.prepare() + + runner.run("koverReport") { + checkDefaultBinaryReport() + checkDefaultReports() + } + } } 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 b318bbed..2b2f7449 100644 --- a/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/InstrumentationFilteringTests.kt +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/InstrumentationFilteringTests.kt @@ -1,6 +1,5 @@ package kotlinx.kover.test.functional.cases -import kotlinx.kover.api.* import kotlinx.kover.test.functional.cases.utils.* import kotlinx.kover.test.functional.core.* import kotlinx.kover.test.functional.core.BaseGradleScriptTest @@ -10,47 +9,41 @@ internal class InstrumentationFilteringTests : BaseGradleScriptTest() { @Test fun testExclude() { - builder("Test exclusion of classes from instrumentation") - .languages(GradleScriptLanguage.KOTLIN, GradleScriptLanguage.GROOVY) - .types(ProjectType.KOTLIN_JVM, ProjectType.KOTLIN_MULTIPLATFORM) - .engines(CoverageEngine.INTELLIJ, CoverageEngine.JACOCO) - .sources("simple") - .configTest( - """excludes = listOf("org.jetbrains.*Exa?ple*")""", - """excludes = ['org.jetbrains.*Exa?ple*']""" - ) - .build() - .run("build") { - xml(defaultMergedXmlReport()) { - classCounter("org.jetbrains.ExampleClass").assertFullyMissed() - classCounter("org.jetbrains.SecondClass").assertCovered() - } + val build = diverseBuild(ALL_LANGUAGES, ALL_ENGINES, ALL_TYPES) + build.addKoverRootProject { + sourcesFrom("simple") + testTasks { + excludes("org.jetbrains.*Exa?ple*") } + } + val runner = build.prepare() + runner.run("build", "koverXmlReport") { + xml(defaultXmlReport()) { + classCounter("org.jetbrains.ExampleClass").assertFullyMissed() + classCounter("org.jetbrains.SecondClass").assertCovered() + } + } + } @Test fun testExcludeInclude() { - builder("Test inclusion and exclusion of classes in instrumentation") - .languages(GradleScriptLanguage.KOTLIN, GradleScriptLanguage.GROOVY) - .types(ProjectType.KOTLIN_JVM, ProjectType.KOTLIN_MULTIPLATFORM) - .engines(CoverageEngine.INTELLIJ, CoverageEngine.JACOCO) - .sources("simple") - .configTest( - """includes = listOf("org.jetbrains.*Cla?s")""", - """includes = ['org.jetbrains.*Cla?s']""" - ) - .configTest( - """excludes = listOf("org.jetbrains.*Exa?ple*")""", - """excludes = ['org.jetbrains.*Exa?ple*']""" - ) - .build() - .run("build") { - xml(defaultMergedXmlReport()) { - classCounter("org.jetbrains.ExampleClass").assertFullyMissed() - classCounter("org.jetbrains.Unused").assertFullyMissed() - classCounter("org.jetbrains.SecondClass").assertCovered() - } + val build = diverseBuild(ALL_LANGUAGES, ALL_ENGINES, ALL_TYPES) + build.addKoverRootProject { + sourcesFrom("simple") + testTasks { + includes("org.jetbrains.*Cla?s") + excludes("org.jetbrains.*Exa?ple*") + } + } + 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() } + } } } 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 48b7d475..e4cbdcc8 100644 --- a/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/MultiProjectTests.kt +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/MultiProjectTests.kt @@ -1,175 +1,223 @@ package kotlinx.kover.test.functional.cases -import kotlinx.kover.api.* import kotlinx.kover.test.functional.cases.utils.* import kotlinx.kover.test.functional.cases.utils.defaultMergedXmlReport +import kotlinx.kover.test.functional.core.* import kotlinx.kover.test.functional.core.BaseGradleScriptTest -import kotlinx.kover.test.functional.core.ProjectType import org.gradle.testkit.runner.* import kotlin.test.* internal class MultiProjectTests : BaseGradleScriptTest() { private val subprojectName = "common" + private val rootName = "kover-functional-test" + @Test fun testMergedReports() { - builder("Testing the generation of merged reports") - .types(ProjectType.KOTLIN_JVM, ProjectType.KOTLIN_MULTIPLATFORM) - .engines(CoverageEngine.INTELLIJ, CoverageEngine.JACOCO) - .sources("multiproject-user") - .subproject(subprojectName) { - sources("multiproject-common") - } - .build() - .run("build") { - xml(defaultMergedXmlReport()) { - classCounter("org.jetbrains.CommonClass").assertFullyCovered() - classCounter("org.jetbrains.CommonInternalClass").assertFullyCovered() - classCounter("org.jetbrains.UserClass").assertFullyCovered() - } - } - } + val build = diverseBuild(engines = ALL_ENGINES, types = ALL_TYPES) + val subPath = build.addKoverSubproject(subprojectName) { + sourcesFrom("multiproject-common") + } - @Test - fun testIsolatedProjectsReports() { - builder("Testing the generation of project reports with running tests only for this project") - .types(ProjectType.KOTLIN_JVM, ProjectType.KOTLIN_MULTIPLATFORM) - .engines(CoverageEngine.INTELLIJ, CoverageEngine.JACOCO) - .sources("multiproject-user") - .subproject(subprojectName) { - sources("multiproject-common") + build.addKoverRootProject { + sourcesFrom("multiproject-user") + subproject(subPath) + + koverMerged { + enable() } - .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 covered partially because calls from the root project are not counted - classCounter("org.jetbrains.CommonClass").assertCovered() - classCounter("org.jetbrains.CommonInternalClass").assertCovered() - } - } + val runner = build.prepare() + runner.run(":koverMergedXmlReport") { + xml(defaultMergedXmlReport()) { + classCounter("org.jetbrains.CommonClass").assertFullyCovered() + classCounter("org.jetbrains.CommonInternalClass").assertFullyCovered() + classCounter("org.jetbrains.UserClass").assertFullyCovered() } + } } @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(CoverageEngine.INTELLIJ, CoverageEngine.JACOCO) - .configKover { runAllTestsForProjectTask = true } - .sources("multiproject-user") - .subproject(subprojectName) { - sources("multiproject-common") + fun testIsolatedProjectsReports() { + val build = diverseBuild(engines = ALL_ENGINES, types = ALL_TYPES) + val subPath = build.addKoverSubproject(subprojectName) { + sourcesFrom("multiproject-common") + } + + build.addKoverRootProject { + sourcesFrom("multiproject-user") + subproject(subPath) + } + + + val runner = build.prepare() + runner.run("koverXmlReport") { + xml(defaultXmlReport()) { + classCounter("org.jetbrains.CommonClass").assertAbsent() + classCounter("org.jetbrains.CommonInternalClass").assertAbsent() + classCounter("org.jetbrains.UserClass").assertFullyCovered() } - .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() + 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() - } + // common class covered partially because calls from the root project are not counted + classCounter("org.jetbrains.CommonClass").assertCovered() + classCounter("org.jetbrains.CommonInternalClass").assertCovered() } } + } } +// +// @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() { - builder("Testing disabling whole Kover") - .sources("multiproject-user") - .subproject(subprojectName) { - sources("multiproject-common") + val build = diverseBuild(engines = ALL_ENGINES, types = ALL_TYPES) + val subPath = build.addKoverSubproject(subprojectName) { + sourcesFrom("multiproject-common") + kover { + isDisabled = true } - .configKover { disabled = true } - .build() - .run("build", "koverHtmlReport") { - checkDefaultBinaryReport(false) - checkOutcome("koverMergedHtmlReport", TaskOutcome.SKIPPED) - checkOutcome("koverMergedVerify", TaskOutcome.SKIPPED) + } + build.addKoverRootProject { + sourcesFrom("multiproject-user") + subproject(subPath) + kover { + isDisabled = true + } + } + + val runner = build.prepare() + runner.run("koverReport", "koverVerify") { + checkDefaultBinaryReport(false) + + checkOutcome("koverHtmlReport", TaskOutcome.SKIPPED) + checkOutcome("koverXmlReport", TaskOutcome.SKIPPED) + checkOutcome("koverVerify", TaskOutcome.SKIPPED) + + subproject(subprojectName) { + checkDefaultBinaryReport(false) checkOutcome("koverHtmlReport", TaskOutcome.SKIPPED) + checkOutcome("koverXmlReport", TaskOutcome.SKIPPED) checkOutcome("koverVerify", TaskOutcome.SKIPPED) - checkOutcome("koverMergedHtmlReport", TaskOutcome.SKIPPED) - - subproject(subprojectName) { - checkDefaultBinaryReport(false) - checkOutcome("koverHtmlReport", TaskOutcome.SKIPPED) - checkOutcome("koverVerify", TaskOutcome.SKIPPED) - } } + } } @Test - fun testDisableSubproject() { - builder("Testing disabling one of subproject") - .sources("multiproject-user") - .subproject(subprojectName) { - sources("multiproject-common") - } - .configKover { disabledProjects += subprojectName } - .build() - .run("build", "koverReport") { - checkDefaultBinaryReport() - checkDefaultMergedReports() - checkDefaultReports() - xml(defaultMergedXmlReport()) { - classCounter("org.jetbrains.UserClass").assertFullyCovered() - - // classes from disabled project should not be included in the merged report - classCounter("org.jetbrains.CommonClass").assertAbsent() - classCounter("org.jetbrains.CommonInternalClass").assertAbsent() + fun testIncludeProject() { + val build = diverseBuild(engines = ALL_ENGINES, types = ALL_TYPES) + val subPath = build.addKoverSubproject(subprojectName) { + sourcesFrom("multiproject-common") + } + + build.addKoverRootProject { + sourcesFrom("multiproject-user") + subproject(subPath) + + koverMerged { + enable() + filters { + projects { + includes += rootName + } } + } + } + + val runner = build.prepare() + runner.run("koverMergedReport") { + checkDefaultBinaryReport() + checkDefaultReports(false) + checkDefaultMergedReports() + xml(defaultMergedXmlReport()) { + classCounter("org.jetbrains.UserClass").assertFullyCovered() + + // classes from disabled project should not be included in the merged report + classCounter("org.jetbrains.CommonClass").assertAbsent() + classCounter("org.jetbrains.CommonInternalClass").assertAbsent() + } - subproject(subprojectName) { - checkDefaultBinaryReport(false) - checkDefaultMergedReports(false) - checkDefaultReports(false) - } + subproject(subprojectName) { + checkDefaultBinaryReport(false) + checkDefaultMergedReports(false) + checkDefaultReports(false) } + } } @Test - fun testDisableSubprojectByPath() { - builder("Testing disabling one of subproject by path") - .sources("multiproject-user") - .subproject(subprojectName) { - sources("multiproject-common") - } - .configKover { disabledProjects += ":$subprojectName" } - .build() - .run("build", "koverReport") { - checkDefaultBinaryReport() - checkDefaultMergedReports() - checkDefaultReports() - xml(defaultMergedXmlReport()) { - classCounter("org.jetbrains.UserClass").assertFullyCovered() - - // classes from disabled project should not be included in the merged report - classCounter("org.jetbrains.CommonClass").assertAbsent() - classCounter("org.jetbrains.CommonInternalClass").assertAbsent() + fun testIncludeProjectByPath() { + val build = diverseBuild(engines = ALL_ENGINES, types = ALL_TYPES) + val subPath = build.addKoverSubproject(subprojectName) { + sourcesFrom("multiproject-common") + } + + build.addKoverRootProject { + sourcesFrom("multiproject-user") + subproject(subPath) + + koverMerged { + enable() + filters { + projects { + includes += ":" + } } + } + } + + val runner = build.prepare() + runner.run("koverMergedReport") { + checkDefaultBinaryReport() + checkDefaultReports(false) + checkDefaultMergedReports() + xml(defaultMergedXmlReport()) { + classCounter("org.jetbrains.UserClass").assertFullyCovered() + + // classes from disabled project should not be included in the merged report + classCounter("org.jetbrains.CommonClass").assertAbsent() + classCounter("org.jetbrains.CommonInternalClass").assertAbsent() + } - subproject(subprojectName) { - checkDefaultBinaryReport(false) - checkDefaultMergedReports(false) - checkDefaultReports(false) - } + subproject(subprojectName) { + checkDefaultBinaryReport(false) + checkDefaultMergedReports(false) + checkDefaultReports(false) } + } } } diff --git a/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/ReportsCachingTests.kt b/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/ReportsCachingTests.kt index f1491a82..f2ea6cd8 100644 --- a/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/ReportsCachingTests.kt +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/ReportsCachingTests.kt @@ -1,7 +1,8 @@ package kotlinx.kover.test.functional.cases -import kotlinx.kover.api.* import kotlinx.kover.test.functional.cases.utils.* +import kotlinx.kover.test.functional.core.* +import kotlinx.kover.test.functional.core.ALL_ENGINES import kotlinx.kover.test.functional.core.BaseGradleScriptTest import org.gradle.testkit.runner.* import kotlin.test.* @@ -9,56 +10,56 @@ import kotlin.test.* internal class ReportsCachingTests : BaseGradleScriptTest() { @Test fun testCaching() { - builder("Test caching of merged reports") - .engines(CoverageEngine.INTELLIJ, CoverageEngine.JACOCO) - .sources("simple") - .withLocalCache() - .build() - .run("build", "--build-cache") { - checkDefaultBinaryReport() - checkDefaultMergedReports() - checkOutcome("test", TaskOutcome.SUCCESS) - checkOutcome("koverMergedXmlReport", TaskOutcome.SUCCESS) - checkOutcome("koverMergedHtmlReport", TaskOutcome.SUCCESS) - } - .run("clean", "--build-cache") { - checkDefaultBinaryReport(false) - checkDefaultMergedReports(false) - } - .run("build", "--build-cache") { - checkDefaultBinaryReport() - checkDefaultMergedReports() - checkOutcome("test", TaskOutcome.FROM_CACHE) - checkOutcome("koverMergedXmlReport", TaskOutcome.FROM_CACHE) - checkOutcome("koverMergedHtmlReport", TaskOutcome.FROM_CACHE) - } + val build = diverseBuild(engines = ALL_ENGINES, withCache = true) + build.addKoverRootProject { + sourcesFrom("simple") + } + val runner = build.prepare() + runner.run("koverReport") { + checkDefaultBinaryReport() + checkDefaultReports() + checkOutcome("test", TaskOutcome.SUCCESS) + checkOutcome("koverXmlReport", TaskOutcome.SUCCESS) + checkOutcome("koverHtmlReport", TaskOutcome.SUCCESS) + } + runner.run("clean") { + checkDefaultBinaryReport(false) + checkDefaultReports(false) + } + runner.run("koverReport") { + checkDefaultBinaryReport() + checkDefaultReports() + checkOutcome("test", TaskOutcome.FROM_CACHE) + checkOutcome("koverXmlReport", TaskOutcome.FROM_CACHE) + checkOutcome("koverHtmlReport", TaskOutcome.FROM_CACHE) + } } @Test fun testProjectReportCaching() { - builder("Test caching projects reports") - .engines(CoverageEngine.INTELLIJ, CoverageEngine.JACOCO) - .sources("simple") - .withLocalCache() - .build() - .run("koverReport", "--build-cache") { - checkDefaultBinaryReport() - checkDefaultReports() - checkOutcome("test", TaskOutcome.SUCCESS) - checkOutcome("koverXmlReport", TaskOutcome.SUCCESS) - checkOutcome("koverHtmlReport", TaskOutcome.SUCCESS) - } - .run("clean", "--build-cache") { - checkDefaultBinaryReport(false) - checkDefaultReports(false) - } - .run("koverReport", "--build-cache") { - checkDefaultBinaryReport() - checkDefaultReports() - checkOutcome("test", TaskOutcome.FROM_CACHE) - checkOutcome("koverXmlReport", TaskOutcome.FROM_CACHE) - checkOutcome("koverHtmlReport", TaskOutcome.FROM_CACHE) - } + val build = diverseBuild(engines = ALL_ENGINES, withCache = true) + build.addKoverRootProject { + sourcesFrom("simple") + } + val runner = build.prepare() + runner.run("koverReport") { + checkDefaultBinaryReport() + checkDefaultReports() + checkOutcome("test", TaskOutcome.SUCCESS) + checkOutcome("koverXmlReport", TaskOutcome.SUCCESS) + checkOutcome("koverHtmlReport", TaskOutcome.SUCCESS) + } + runner.run("clean") { + checkDefaultBinaryReport(false) + checkDefaultReports(false) + } + runner.run("koverReport") { + checkDefaultBinaryReport() + checkDefaultReports() + checkOutcome("test", TaskOutcome.FROM_CACHE) + checkOutcome("koverXmlReport", TaskOutcome.FROM_CACHE) + checkOutcome("koverHtmlReport", TaskOutcome.FROM_CACHE) + } } } diff --git a/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/ReportsFilteringTests.kt b/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/ReportsFilteringTests.kt index c310e411..8d5e4ce2 100644 --- a/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/ReportsFilteringTests.kt +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/ReportsFilteringTests.kt @@ -9,54 +9,50 @@ internal class ReportsFilteringTests : BaseGradleScriptTest() { @Test fun testExclude() { - builder("Test exclusion of classes from XML report") - .languages(GradleScriptLanguage.KOTLIN, GradleScriptLanguage.GROOVY) - .sources("simple") - .config( - """ - tasks.koverMergedXmlReport { - excludes = listOf("org.jetbrains.*Exa?ple*") - }""".trimIndent(), - """ - tasks.koverMergedXmlReport { - excludes = ['org.jetbrains.*Exa?ple*'] - }""".trimIndent() - ) - .build() - .run("build") { - xml(defaultMergedXmlReport()) { - classCounter("org.jetbrains.ExampleClass").assertAbsent() - classCounter("org.jetbrains.SecondClass").assertCovered() + val build = diverseBuild(languages = ALL_LANGUAGES) + build.addKoverRootProject { + sourcesFrom("simple") + + kover { + filters { + classes { + excludes += "org.jetbrains.*Exa?ple*" + } } } + } + val runner = build.prepare() + runner.run("koverXmlReport") { + xml(defaultXmlReport()) { + classCounter("org.jetbrains.ExampleClass").assertAbsent() + classCounter("org.jetbrains.SecondClass").assertCovered() + } + } } @Test fun testExcludeInclude() { - builder("Test inclusion and exclusion of classes in XML report") - .languages(GradleScriptLanguage.KOTLIN, GradleScriptLanguage.GROOVY) - .sources("simple") - .config( - """ - tasks.koverMergedXmlReport { - includes = listOf("org.jetbrains.*Cla?s") - excludes = listOf("org.jetbrains.*Exa?ple*") - }""".trimIndent(), + val build = diverseBuild(languages = ALL_LANGUAGES) + build.addKoverRootProject { + sourcesFrom("simple") - """ - tasks.koverMergedXmlReport { - includes = ['org.jetbrains.*Cla?s'] - excludes = ['org.jetbrains.*Exa?ple*'] - }""".trimIndent() - ) - .build() - .run("build") { - xml(defaultMergedXmlReport()) { - classCounter("org.jetbrains.ExampleClass").assertAbsent() - classCounter("org.jetbrains.Unused").assertAbsent() - classCounter("org.jetbrains.SecondClass").assertFullyCovered() + kover { + filters { + classes { + excludes += "org.jetbrains.*Exa?ple*" + includes += "org.jetbrains.*Cla?s" + } } } + } + val runner = build.prepare() + runner.run("koverXmlReport") { + xml(defaultXmlReport()) { + classCounter("org.jetbrains.ExampleClass").assertAbsent() + classCounter("org.jetbrains.Unused").assertAbsent() + classCounter("org.jetbrains.SecondClass").assertFullyCovered() + } + } } } diff --git a/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/VerificationTests.kt b/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/VerificationTests.kt index b423b3b1..678599c5 100644 --- a/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/VerificationTests.kt +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/VerificationTests.kt @@ -12,84 +12,102 @@ import kotlin.test.* internal class VerificationTests : BaseGradleScriptTest() { @Test fun testVerified() { - builder("Test verification passed") - .languages(GradleScriptLanguage.KOTLIN, GradleScriptLanguage.GROOVY) - .engines(CoverageEngine.INTELLIJ, CoverageEngine.JACOCO) - .sources("simple") - .rule("test rule") { - bound { - minValue = 50 - maxValue = 60 - } - bound { - valueType = VerificationValueType.COVERED_LINES_COUNT - minValue = 2 - maxValue = 10 + val build = diverseBuild(languages = ALL_LANGUAGES, engines = ALL_ENGINES) + build.addKoverRootProject { + sourcesFrom("simple") + + kover { + verify { + rule { + name = "test rule" + bound { + minValue = 50 + maxValue = 60 + } + bound { + valueType = VerificationValueType.COVERED_COUNT + minValue = 2 + maxValue = 10 + } + } } } - .build() - .run("koverVerify") + } + + build.prepare().run("koverVerify", "--stacktrace") } @Test fun testNotVerifiedIntelliJ() { - builder("Test verification failed for IntelliJ Engine") - .languages(GradleScriptLanguage.KOTLIN, GradleScriptLanguage.GROOVY) - .engines(CoverageEngine.INTELLIJ) - .sources("simple") - .rule("test rule") { - bound { - minValue = 58 - maxValue = 60 - } - bound { - valueType = VerificationValueType.COVERED_LINES_COUNT - minValue = 2 - maxValue = 3 + val build = diverseBuild(languages = ALL_LANGUAGES, engines = listOf(CoverageEngineVendor.INTELLIJ)) + build.addKoverRootProject { + sourcesFrom("simple") + + kover { + verify { + rule { + name = "test rule" + bound { + minValue = 58 + maxValue = 60 + } + bound { + valueType = VerificationValueType.COVERED_COUNT + minValue = 2 + maxValue = 3 + } + } } } - .build() - .runWithError("koverVerify") { - output { - assertTrue { - this.contains( - "> Rule 'test rule' violated:\n" + - " covered lines percentage is 57.142900, but expected minimum is 58\n" + - " covered lines count is 4, but expected maximum is 3" - ) - } + } + + build.prepare().runWithError("koverVerify") { + output { + assertTrue { + this.contains( + "> Rule 'test rule' violated:\n" + + " covered lines percentage is 57.142900, but expected minimum is 58\n" + + " covered lines count is 4, but expected maximum is 3" + ) } } + } } @Test fun testNotVerifiedJaCoCo() { - builder("Test verification failed for JaCoCo Engine") - .languages(GradleScriptLanguage.KOTLIN, GradleScriptLanguage.GROOVY) - .engines(CoverageEngine.JACOCO) - .sources("simple") - .rule("test rule") { - bound { - minValue = 58 - maxValue = 60 - } - bound { - valueType = VerificationValueType.COVERED_LINES_COUNT - minValue = 2 - maxValue = 3 + val build = diverseBuild(languages = ALL_LANGUAGES, engines = listOf(CoverageEngineVendor.JACOCO)) + build.addKoverRootProject { + sourcesFrom("simple") + + kover { + verify { + rule { + name = "test rule" + bound { + minValue = 58 + maxValue = 60 + } + bound { + valueType = VerificationValueType.COVERED_COUNT + minValue = 2 + maxValue = 3 + } + } } } - .build() - .runWithError("koverVerify") { - output { - assertTrue { - this.contains( - "[ant:jacocoReport] Rule violated for bundle :: lines covered ratio is 0.50, but expected minimum is 0.58\n" + - "[ant:jacocoReport] Rule violated for bundle :: lines covered count is 4, but expected maximum is 3" - ) - } + } + + build.prepare().runWithError("koverVerify") { + output { + assertTrue { + this.contains( + "[ant:jacocoReport] Rule violated for bundle :: lines covered ratio is 0.50, but expected minimum is 0.58\n" + + "[ant:jacocoReport] Rule violated for bundle :: lines covered count is 4, but expected maximum is 3" + ) } } + } } diff --git a/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/counters/CountersValueTests.kt b/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/counters/CountersValueTests.kt index 8a9e3bb1..792564fc 100644 --- a/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/counters/CountersValueTests.kt +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/counters/CountersValueTests.kt @@ -2,44 +2,49 @@ package kotlinx.kover.test.functional.cases.counters import kotlinx.kover.api.* import kotlinx.kover.test.functional.cases.utils.* +import kotlinx.kover.test.functional.core.* import kotlinx.kover.test.functional.core.BaseGradleScriptTest import kotlin.test.* internal class CountersValueTests : BaseGradleScriptTest() { @Test fun testBasicCounterCases() { - builder("Testing of basic counting capabilities by an IntelliJ Agent") - .engines(CoverageEngine.INTELLIJ) - .sources("counters") - .build() - .run("build") { - xml(defaultMergedXmlReport()) { - // test on branch counter - methodCounter("org.jetbrains.MyBranchedClass", "foo", type = "BRANCH").assertCovered(1, 3) - - // test on constructor of used sealed classes - methodCounter("org.jetbrains.Sealed", "").assertFullyCovered() - methodCounter("org.jetbrains.SealedWithInit", "").assertFullyCovered() - methodCounter("org.jetbrains.SealedWithConstructor", "").assertFullyCovered() - - // test on empty objects - classCounter("org.jetbrains.UnusedObject").assertFullyMissed() - classCounter("org.jetbrains.UsedObject").assertFullyCovered() - - // deprecated functions (ERROR and HIDDEN) should not be included in the report. - methodCounter("org.jetbrains.Different", "deprecatedError").assertAbsent() - methodCounter("org.jetbrains.Different", "deprecatedHidden").assertAbsent() - // WARNING deprecated functions should be included in the report. - methodCounter("org.jetbrains.Different", "deprecatedWarn").assertFullyMissed() - - // empty functions must be included in the report with line counter - methodCounter("org.jetbrains.Different", "emptyFun", type = "LINE").assertFullyMissed() - - // Instruction counters - // helloWorld contains 4 instructions. `return` ignored by reporter and the first instruction is not covered because of a bug in the IR compiler - // FIXME after https://youtrack.jetbrains.com/issue/KT-51080 is fixed, the number should become 3 - methodCounter("org.jetbrains.Different", "helloWorld").assertTotal(2) - } + val build = diverseBuild( + engines = listOf(CoverageEngineVendor.INTELLIJ) + ) + build.addKoverRootProject { + sourcesFrom("counters") + } + + val runner = build.prepare() + runner.run("koverXmlReport") { + xml(defaultXmlReport()) { + // test on branch counter + methodCounter("org.jetbrains.MyBranchedClass", "foo", type = "BRANCH").assertCovered(1, 3) + + // test on constructor of used sealed classes + methodCounter("org.jetbrains.Sealed", "").assertFullyCovered() + methodCounter("org.jetbrains.SealedWithInit", "").assertFullyCovered() + methodCounter("org.jetbrains.SealedWithConstructor", "").assertFullyCovered() + + // test on empty objects + classCounter("org.jetbrains.UnusedObject").assertFullyMissed() + classCounter("org.jetbrains.UsedObject").assertFullyCovered() + + // deprecated functions (ERROR and HIDDEN) should not be included in the report. + methodCounter("org.jetbrains.Different", "deprecatedError").assertAbsent() + methodCounter("org.jetbrains.Different", "deprecatedHidden").assertAbsent() + // WARNING deprecated functions should be included in the report. + methodCounter("org.jetbrains.Different", "deprecatedWarn").assertFullyMissed() + + // empty functions must be included in the report with line counter + methodCounter("org.jetbrains.Different", "emptyFun", type = "LINE").assertFullyMissed() + + // Instruction counters + // helloWorld contains 4 instructions. `return` ignored by reporter and the first instruction is not covered because of a bug in the IR compiler + // FIXME after https://youtrack.jetbrains.com/issue/KT-51080 is fixed, the number should become 3 + methodCounter("org.jetbrains.Different", "helloWorld").assertTotal(2) } + } } } 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 48383530..2d6b2b4a 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 @@ -60,7 +60,7 @@ internal fun RunResult.checkReports(xmlPath: String, htmlPath: String, mustExist } internal fun RunResult.checkIntellijErrors(errorExpected: Boolean = false) { - if (engine != CoverageEngine.INTELLIJ) return + if (engine != CoverageEngineVendor.INTELLIJ) return file(errorsDirectory()) { if (this.exists() && !errorExpected) { 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 947f69a3..0311dda9 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,8 +4,8 @@ import kotlinx.kover.api.* import kotlinx.kover.test.functional.core.ProjectType -internal fun defaultBinaryReport(engine: CoverageEngine, projectType: ProjectType): String { - val extension = if (engine == CoverageEngine.INTELLIJ) "ic" else "exec" +internal fun defaultBinaryReport(engine: CoverageEngineVendor, projectType: ProjectType): String { + val extension = if (engine == CoverageEngineVendor.INTELLIJ) "ic" else "exec" return when (projectType) { ProjectType.KOTLIN_JVM -> "kover/test.$extension" ProjectType.KOTLIN_MULTIPLATFORM -> "kover/jvmTest.$extension" @@ -13,10 +13,10 @@ internal fun defaultBinaryReport(engine: CoverageEngine, projectType: ProjectTyp } } -internal fun defaultMergedXmlReport() = "reports/kover/report.xml" -internal fun defaultMergedHtmlReport() = "reports/kover/html" +internal fun defaultMergedXmlReport() = "reports/kover/merged/xml/report.xml" +internal fun defaultMergedHtmlReport() = "reports/kover/merged/html" -internal fun defaultXmlReport() = "reports/kover/project-xml/report.xml" -internal fun defaultHtmlReport() = "reports/kover/project-html" +internal fun defaultXmlReport() = "reports/kover/xml/report.xml" +internal fun defaultHtmlReport() = "reports/kover/html" internal fun errorsDirectory() = "kover/errors" diff --git a/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/BaseGradleScriptTest.kt b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/BaseGradleScriptTest.kt index f5e2a3ba..de6b430b 100644 --- a/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/BaseGradleScriptTest.kt +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/BaseGradleScriptTest.kt @@ -1,19 +1,79 @@ package kotlinx.kover.test.functional.core +import kotlinx.kover.api.* import org.junit.* import org.junit.rules.* +/** + * Kotlin by default. + */ +internal val DEFAULT_LANGUAGE = listOf(GradleScriptLanguage.KOTLIN) + +/** + * Used engine from configuration if specified. Otherwise, used [DefaultIntellijEngine] engine. + */ +internal val DEFAULT_ENGINE = listOf() + +/** + * Kotlin JVM project by default. + */ +internal val DEFAULT_TYPE = listOf(ProjectType.KOTLIN_JVM) + +internal val ALL_LANGUAGES = listOf(GradleScriptLanguage.KOTLIN, GradleScriptLanguage.GROOVY) +internal val ALL_ENGINES = listOf(CoverageEngineVendor.INTELLIJ, CoverageEngineVendor.JACOCO) +internal val ALL_TYPES = listOf(ProjectType.KOTLIN_JVM, ProjectType.KOTLIN_MULTIPLATFORM) + internal open class BaseGradleScriptTest { @Rule @JvmField internal val rootFolder: TemporaryFolder = TemporaryFolder() - fun builder(description: String): TestCaseBuilder { - return createBuilder(rootFolder.root, description) + + internal fun diverseBuild( + languages: List = DEFAULT_LANGUAGE, + engines: List = DEFAULT_ENGINE, + types: List = DEFAULT_TYPE, + withCache: Boolean = false + ): DiverseBuild { + return DiverseBuildState(rootFolder.root, languages, engines, types, withCache) + } + + internal fun sampleBuild(templateName: String): GradleRunner { + return createInternalSample(templateName, rootFolder.root) + } +} + + +internal fun DiverseBuild.addKoverRootProject(builder: ProjectBuilder.() -> Unit) { + addProject("root", ":") { + plugins { + kotlin("1.6.20") + kover("DEV") + } + + repositories { + repository("mavenCentral()") + } + + builder() } - fun internalSample(name: String): GradleRunner { - return createInternalSample(name, rootFolder.root) +} + +internal fun DiverseBuild.addKoverSubproject(name: String, builder: ProjectBuilder.() -> Unit): String { + addProject(name, ":$name") { + plugins { + kotlin() + kover() + } + + repositories { + repository("mavenCentral()") + } + + builder() } + + return ":$name" } diff --git a/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/Builder.kt b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/Builder.kt index b638381c..52388956 100644 --- a/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/Builder.kt +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/Builder.kt @@ -5,163 +5,247 @@ package kotlinx.kover.test.functional.core import kotlinx.kover.api.* +import kotlinx.kover.test.functional.core.writer.initSlice import org.gradle.api.* import java.io.* -internal fun createBuilder(rootDir: File, description: String): TestCaseBuilder { - return TestCaseBuilderImpl(rootDir, description) -} +internal class DiverseBuildState( + private val rootDir: File, + private val languages: List, + private val engines: List, + private val types: List, + private val withCache: Boolean +) : DiverseBuild { + private val projects: MutableMap = mutableMapOf() -internal class CommonBuilderState(val description: String) { - var pluginVersion: String? = null - val languages: MutableSet = mutableSetOf() - val types: MutableSet = mutableSetOf() - val engines: MutableSet = mutableSetOf() - val koverConfig: KoverRootConfig = KoverRootConfig() - val rootProject: ProjectBuilderState = ProjectBuilderState() - val subprojects: MutableMap = mutableMapOf() - var localCache: Boolean = false -} + override fun addProject(name: String, path: String, builder: ProjectBuilder.() -> Unit) { + projects[path] = ProjectBuilderState(name).also(builder) + } + + override fun prepare(): GradleRunner { + val initSlices: MutableMap = mutableMapOf() + + val targetEngines = engines.ifEmpty { listOf(null) } + + + languages.forEach { language -> + types.forEach { type -> + targetEngines.forEach { engine -> + val slice = ProjectSlice(language, type, engine) + initSlices[slice] = initSlice(rootDir, slice, projects, withCache) + } + } + } -internal class ProjectBuilderState { - val sourceTemplates: MutableList = mutableListOf() - val scripts: MutableList = mutableListOf() - val testScripts: MutableList = mutableListOf() - val dependencies: MutableList = mutableListOf() - val rules: MutableList = mutableListOf() - val mainSources: MutableMap = mutableMapOf() - val testSources: MutableMap = mutableMapOf() + val extraArgs = if (withCache) { + listOf("--build-cache") + } else { + listOf() + } + + return DiverseGradleRunner(initSlices, extraArgs) + } } -internal data class GradleScript(val kotlin: String, val groovy: String) -private class TestCaseBuilderImpl( - val rootDir: File, - description: String, - private val state: CommonBuilderState = CommonBuilderState(description) -) : ProjectBuilderImpl(state.rootProject), TestCaseBuilder { +internal class ProjectBuilderState(val name: String) : ProjectBuilder { + val sourceTemplates: MutableSet = mutableSetOf() + val plugins: PluginsState = PluginsState() + val repositories: RepositoriesState = RepositoriesState() + var kover: TestKoverProjectConfigState? = null + var merged: TestKoverMergedConfigState? = null + val testTasks: TestTaskConfigState = TestTaskConfigState() + val subprojects: MutableList = mutableListOf() - override fun languages(vararg languages: GradleScriptLanguage) = also { - state.languages += languages + override fun plugins(block: Plugins.() -> Unit) { + plugins.also(block) } - override fun engines(vararg engines: CoverageEngine) = also { - state.engines += engines + override fun repositories(block: Repositories.() -> Unit) { + repositories.also(block) } - override fun types(vararg types: ProjectType) = also { - state.types += types + override fun kover(config: TestKoverProjectConfig.() -> Unit) { + kover = TestKoverProjectConfigState().also(config) } - override fun withLocalCache(): TestCaseBuilder = also { - state.localCache = true + override fun koverMerged(config: TestKoverMergedConfig.() -> Unit) { + merged = TestKoverMergedConfigState().also(config) } - override fun configKover(config: KoverRootConfig.() -> Unit) = also { - state.koverConfig.config() + override fun subproject(path: String) { + subprojects += path } - override fun subproject(name: String, builder: ProjectBuilder<*>.() -> Unit) = also { - val projectState = state.subprojects.computeIfAbsent(name) { ProjectBuilderState() } - @Suppress("UPPER_BOUND_VIOLATED_WARNING") - ProjectBuilderImpl>(projectState).builder() + override fun testTasks(block: TestTaskConfig.() -> Unit) { + testTasks.also(block) } - override fun build(): GradleRunner { - if (state.languages.isEmpty()) { - state.languages += GradleScriptLanguage.KOTLIN - } - if (state.types.isEmpty()) { - state.types += ProjectType.KOTLIN_JVM - } - if (state.engines.isEmpty()) { - state.engines += null - } - if (state.pluginVersion == null) { - state.pluginVersion = "0.5.0" - } + override fun sourcesFrom(template: String) { + sourceTemplates += template + } +} - val projects: MutableMap = mutableMapOf() +internal class TestKoverProjectConfigState : TestKoverProjectConfig { + override var isDisabled: Boolean? = null + override var engine: CoverageEngineVariant? = null + val filters: TestKoverProjectFiltersState = TestKoverProjectFiltersState() + val instrumentation: KoverProjectInstrumentation = KoverProjectInstrumentation() + val xml: TestKoverProjectXmlConfigState = TestKoverProjectXmlConfigState() + val html: TestKoverProjectHtmlConfigState = TestKoverProjectHtmlConfigState() + val verify: TestKoverVerifyConfigState = TestKoverVerifyConfigState() + override fun filters(config: TestKoverProjectFilters.() -> Unit) { + filters.also(config) + } - state.languages.forEach { language -> - state.types.forEach { type -> - state.engines.forEach { engine -> - val slice = ProjectSlice(language, type, engine ?: CoverageEngine.INTELLIJ) - projects[slice] = state.createProject(rootDir, slice) - } - } - } + override fun instrumentation(config: KoverProjectInstrumentation.() -> Unit) { + instrumentation.also(config) + } - return GradleRunnerImpl(projects) + override fun xmlReport(config: TestKoverProjectXmlConfig.() -> Unit) { + xml.also(config) } + override fun htmlReport(config: TestKoverProjectHtmlConfig.() -> Unit) { + html.also(config) + } + + override fun verify(config: TestKoverVerifyConfig.() -> Unit) { + verify.also(config) + } } +internal class TestKoverProjectFiltersState : TestKoverProjectFilters { + var classes: KoverClassFilters? = null + var sourcesets: KoverSourceSetFilters? = null -@Suppress("UNCHECKED_CAST") -private open class ProjectBuilderImpl>(val projectState: ProjectBuilderState) : ProjectBuilder { + override fun classes(config: KoverClassFilters.() -> Unit) { + classes = KoverClassFilters().also(config) + } - override fun rule(name: String?, builder: RuleBuilder.() -> Unit): B { - projectState.rules += TestVerificationRule(projectState.rules.size, name).apply(builder) - return this as B + override fun sourcesets(config: KoverSourceSetFilters.() -> Unit) { + sourcesets = KoverSourceSetFilters().also(config) } +} + +internal class TestKoverProjectXmlConfigState : TestKoverProjectXmlConfig { + override var onCheck: Boolean? = null + override var reportFile: File? = null + var overrideFilters: TestKoverProjectFiltersState? = null - override fun configTest(script: String): B { - projectState.testScripts += GradleScript(script, script) - return this as B + override fun overrideFilters(config: TestKoverProjectFilters.() -> Unit) { + if (overrideFilters == null) { + overrideFilters = TestKoverProjectFiltersState() + } + overrideFilters!!.also(config) } +} - override fun configTest(kotlin: String, groovy: String): B { - projectState.testScripts += GradleScript(kotlin, groovy) - return this as B +internal class TestKoverProjectHtmlConfigState : TestKoverProjectHtmlConfig { + override var onCheck: Boolean? = null + override var reportDir: File? = null + var overrideFilters: TestKoverProjectFiltersState? = null + + override fun overrideFilters(config: TestKoverProjectFilters.() -> Unit) { + if (overrideFilters == null) { + overrideFilters = TestKoverProjectFiltersState() + } + overrideFilters!!.also(config) } +} - override fun config(script: String): B { - projectState.scripts += GradleScript(script, script) - return this as B +internal class TestKoverVerifyConfigState : TestKoverVerifyConfig { + override val onCheck: Boolean? = null + val rules: MutableList = mutableListOf() + + override fun rule(config: TestVerificationRule.() -> Unit) { + rules += TestVerificationRule().also(config) } +} + +internal class TestVerificationRule { + var isEnabled: Boolean? = null + var name: String? = null + var target: VerificationTarget? = null + + var overrideClassFilters: KoverClassFilters? = null + val bounds: MutableList = mutableListOf() - override fun config(kotlin: String, groovy: String): B { - projectState.scripts += GradleScript(kotlin, groovy) - return this as B + fun overrideClassFilters(config: Action) { + overrideClassFilters = KoverClassFilters().also { config.execute(it) } } - override fun dependency(script: String): B { - projectState.dependencies += GradleScript(script, script) - return this as B + fun bound(configureBound: VerificationBoundState.() -> Unit) { + bounds += VerificationBoundState().also(configureBound) } +} + +internal class VerificationBoundState { + var minValue: Int? = null + var maxValue: Int? = null + var counter: CounterType? = null + var valueType: VerificationValueType? = null +} - override fun dependency(kotlin: String, groovy: String): B { - projectState.dependencies += GradleScript(kotlin, groovy) - return this as B +internal class TestTaskConfigState : TestTaskConfig { + var excludes: List? = null + var includes: List? = null + override fun excludes(vararg classes: String) { + excludes = classes.toList() } - override fun sources(template: String): B { - projectState.sourceTemplates += template - return this as B + override fun includes(vararg classes: String) { + includes = classes.toList() } + } -private data class TestVerificationRule( - override val id: Int, - override var name: String? -) : VerificationRule, RuleBuilder { - override val bounds: MutableList = mutableListOf() - override fun bound(configureBound: Action) { - bounds += TestVerificationBound(bounds.size).also { configureBound.execute(it) } +internal class RepositoriesState : Repositories { + val repositories: MutableList = mutableListOf() + override fun repository(name: String) { + repositories += name } - override fun bound(builder: VerificationBound.() -> Unit) { - bounds += TestVerificationBound(bounds.size).apply(builder) +} + +internal class TestKoverMergedConfigState : TestKoverMergedConfig { + var enabled: Boolean = false + val filters: TestKoverMergedFiltersState = TestKoverMergedFiltersState() + override fun enable() { + enabled = true + } + + override fun filters(config: TestKoverMergedFilters.() -> Unit) { + filters.also(config) } } -private data class TestVerificationBound( - override val id: Int, - override var minValue: Int? = null, - override var maxValue: Int? = null, - override var valueType: VerificationValueType = VerificationValueType.COVERED_LINES_PERCENTAGE -) : VerificationBound +internal class TestKoverMergedFiltersState : TestKoverMergedFilters { + var classes: KoverClassFilters? = null + var projects: KoverProjectsFilters? = null + override fun classes(config: KoverClassFilters.() -> Unit) { + classes = KoverClassFilters().also(config) + } + override fun projects(config: KoverProjectsFilters.() -> Unit) { + projects = KoverProjectsFilters().also(config) + } +} + +internal class PluginsState : Plugins { + var useKotlin: Boolean = false + var useKover: Boolean = false + var kotlinVersion: String? = null + var koverVersion: String? = null + + override fun kotlin(version: String?) { + useKotlin = true + kotlinVersion = version + } + override fun kover(version: String?) { + useKover = true + koverVersion = version + } +} 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 9966d57a..f90583f9 100644 --- a/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/Runner.kt +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/Runner.kt @@ -12,11 +12,11 @@ import java.io.* import javax.xml.parsers.* -internal class GradleRunnerImpl(private val projects: Map) : +internal class DiverseGradleRunner(private val projects: Map, private val extraArgs: List) : GradleRunner { - override fun run(vararg args: String, checker: RunResult.() -> Unit): GradleRunnerImpl { - val argsList = listOf(*args) + override fun run(vararg args: String, checker: RunResult.() -> Unit): DiverseGradleRunner { + val argsList = listOf(*args) + extraArgs projects.forEach { (slice, project) -> try { val buildResult = project.runGradle(argsList) @@ -27,8 +27,8 @@ internal class GradleRunnerImpl(private val projects: Map) : } return this } - override fun runWithError(vararg args: String, errorChecker: RunResult.() -> Unit): GradleRunnerImpl { - val argsList = listOf(*args) + override fun runWithError(vararg args: String, errorChecker: RunResult.() -> Unit): DiverseGradleRunner { + val argsList = listOf(*args) + extraArgs projects.forEach { (slice, project) -> try { project.runGradleWithError(argsList) @@ -98,11 +98,11 @@ private class RunResultImpl( private val buildScriptFile: File = buildFile() private val buildScript: String by lazy { buildScriptFile.readText() } - override val engine: CoverageEngine by lazy { + override val engine: CoverageEngineVendor by lazy { if (buildScript.contains("set(kotlinx.kover.api.CoverageEngine.JACOCO)")) { - CoverageEngine.JACOCO + CoverageEngineVendor.JACOCO } else { - CoverageEngine.INTELLIJ + CoverageEngineVendor.INTELLIJ } } @@ -116,9 +116,9 @@ private class RunResultImpl( throw IllegalArgumentException("Impossible to determine the type of project") } } else { - if (buildScript.contains("""id 'org.jetbrains.kotlin.jvm'""")) { + if (buildScript.contains("""id "org.jetbrains.kotlin.jvm"""")) { ProjectType.KOTLIN_JVM - } else if (buildScript.contains("""id 'org.jetbrains.kotlin.multiplatform'""")) { + } else if (buildScript.contains("""id "org.jetbrains.kotlin.multiplatform"""")) { ProjectType.KOTLIN_MULTIPLATFORM } else { throw IllegalArgumentException("Impossible to determine the type of project") 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 4ee1689a..b41be670 100644 --- a/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/Types.kt +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/Types.kt @@ -12,55 +12,109 @@ internal enum class GradleScriptLanguage { KOTLIN, GROOVY } internal enum class ProjectType { KOTLIN_JVM, KOTLIN_MULTIPLATFORM, ANDROID } -internal interface ProjectBuilder> { - fun sources(template: String): B +internal interface DiverseBuild { + fun addProject(name: String, path: String, builder: ProjectBuilder.() -> Unit) + fun prepare(): GradleRunner +} + +internal interface ProjectBuilder { + fun plugins(block: Plugins.() -> Unit) + + fun repositories(block: Repositories.() -> Unit) - fun rule(name: String? = null, builder: RuleBuilder.() -> Unit): B + fun kover(config: TestKoverProjectConfig.() -> Unit) - fun configTest(script: String): B - fun configTest(kotlin: String, groovy: String): B + fun koverMerged(config: TestKoverMergedConfig.() -> Unit) - fun config(script: String): B - fun config(kotlin: String, groovy: String): B + fun testTasks(block: TestTaskConfig.() -> Unit) - fun dependency(script: String): B - fun dependency(kotlin: String, groovy: String): B + fun subproject(path: String) + + fun sourcesFrom(template: String) } -internal interface RuleBuilder { - fun bound(builder: VerificationBound.() -> Unit) + +interface Plugins { + fun kotlin(version: String? = null) + fun kover(version: String? = null) } -internal interface TestCaseBuilder : ProjectBuilder { - fun languages(vararg languages: GradleScriptLanguage): TestCaseBuilder - fun engines(vararg engines: CoverageEngine): TestCaseBuilder - fun types(vararg types: ProjectType): TestCaseBuilder +interface Repositories { + fun repository(name: String) +} + +interface TestTaskConfig { + fun excludes(vararg classes: String) + fun includes(vararg classes: String) +} - fun withLocalCache(): TestCaseBuilder +/** + * Same as [KoverProjectConfig] + */ +internal interface TestKoverProjectConfig { + var isDisabled: Boolean? - fun configKover(config: KoverRootConfig.() -> Unit): TestCaseBuilder + var engine: CoverageEngineVariant? - fun subproject(name: String, builder: ProjectBuilder<*>.() -> Unit): TestCaseBuilder + fun filters(config: TestKoverProjectFilters.() -> Unit) - fun build(): GradleRunner + fun instrumentation(config: KoverProjectInstrumentation.() -> Unit) + + fun xmlReport(config: TestKoverProjectXmlConfig.() -> Unit) + + fun htmlReport(config: TestKoverProjectHtmlConfig.() -> Unit) + + fun verify(config: TestKoverVerifyConfig.() -> Unit) } -internal data class ProjectSlice(val language: GradleScriptLanguage, val type: ProjectType, val engine: CoverageEngine?) { - fun encodedString(): String { - return "${language.ordinal}_${type.ordinal}_${engine?.ordinal?:"default"}" - } + +internal interface TestKoverVerifyConfig { + val onCheck: Boolean? + + fun rule(config: TestVerificationRule.() -> Unit) +} + +internal interface TestKoverProjectXmlConfig { + var onCheck: Boolean? + var reportFile: File? + + fun overrideFilters(config: TestKoverProjectFilters.() -> Unit) +} + +internal interface TestKoverProjectHtmlConfig { + var onCheck: Boolean? + var reportDir: File? + + fun overrideFilters(config: TestKoverProjectFilters.() -> Unit) } -internal data class KoverRootConfig( - var disabled: Boolean? = null, - var intellijVersion: String? = null, - var jacocoVersion: String? = null, - var generateReportOnCheck: Boolean? = null, - var runAllTestsForProjectTask: Boolean? = null, - val disabledProjects: MutableSet = mutableSetOf() +internal interface TestKoverProjectFilters { + fun classes(config: KoverClassFilters.() -> Unit) + + fun sourcesets(config: KoverSourceSetFilters.() -> Unit) +} + +internal interface TestKoverMergedConfig { + public fun enable() + + public fun filters(config: TestKoverMergedFilters.() -> Unit) +} + +public interface TestKoverMergedFilters { + public fun classes(config: KoverClassFilters.() -> Unit) + + public fun projects(config: KoverProjectsFilters.() -> Unit) +} + + +internal data class ProjectSlice( + val language: GradleScriptLanguage, + val type: ProjectType, + val engine: CoverageEngineVendor? ) { - val isDefault = - disabled == null && intellijVersion == null && jacocoVersion == null && generateReportOnCheck == null + fun encodedString(): String { + return "${language.ordinal}_${type.ordinal}_${engine?.ordinal ?: "default"}" + } } internal interface GradleRunner { @@ -69,7 +123,7 @@ internal interface GradleRunner { } internal interface RunResult { - val engine: CoverageEngine + val engine: CoverageEngineVendor val projectType: ProjectType fun subproject(name: String, checker: RunResult.() -> Unit) diff --git a/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/Writer.kt b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/Writer.kt deleted file mode 100644 index 4e068906..00000000 --- a/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/Writer.kt +++ /dev/null @@ -1,264 +0,0 @@ -/* - * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.kover.test.functional.core - -import kotlinx.kover.api.* -import kotlinx.kover.test.functional.core.GradleScriptLanguage.KOTLIN -import java.io.* - -private const val TEMPLATES_PATH = "src/functionalTest/templates" -private const val BUILD_SCRIPTS_PATH = "$TEMPLATES_PATH/scripts/buildscripts" -private const val SETTINGS_PATH = "$TEMPLATES_PATH/scripts/settings" -private const val SOURCES_PATH = "$TEMPLATES_PATH/sources" - -internal fun CommonBuilderState.createProject(rootDir: File, slice: ProjectSlice): File { - val projectDir = File(rootDir, slice.encodedString()).also { it.mkdirs() } - - val extension = slice.scriptExtension - - val buildScript = loadScriptTemplate(true, slice) - .processRootBuildScript(this, slice) - .processProjectBuildScript(rootProject, slice, subprojects.keys) - - File(projectDir, "build.$extension").writeText(buildScript) - File(projectDir, "settings.$extension").writeText(buildSettings(slice)) - - rootProject.writeSources(projectDir, slice) - - subprojects.forEach { (name, state) -> state.writeSubproject(File(projectDir, name), slice) } - - return projectDir -} - -private fun ProjectBuilderState.writeSubproject(directory: File, slice: ProjectSlice) { - directory.mkdirs() - - val extension = slice.scriptExtension - - val buildScript = loadScriptTemplate(false, slice).processProjectBuildScript(this, slice) - - File(directory, "build.$extension").writeText(buildScript) - - writeSources(directory, slice) -} - - -private val ProjectSlice.scriptExtension get() = if (language == KOTLIN) "gradle.kts" else "gradle" - -private val ProjectSlice.srcPath: String - get() { - return when (type) { - ProjectType.KOTLIN_JVM -> "src/main" - ProjectType.KOTLIN_MULTIPLATFORM -> "src/jvmMain" - ProjectType.ANDROID -> "src/jvmMain" - } - } - -private val ProjectSlice.testPath: String - get() { - return when (type) { - ProjectType.KOTLIN_JVM -> "src/test" - ProjectType.KOTLIN_MULTIPLATFORM -> "src/jvmTest" - ProjectType.ANDROID -> "src/jvmTest" - } - } - - -private fun String.processRootBuildScript(state: CommonBuilderState, slice: ProjectSlice): String { - return replace("//PLUGIN_VERSION", state.pluginVersion!!) - .replace("//KOVER", state.buildRootExtension(slice)) -} - -private fun String.processProjectBuildScript( - state: ProjectBuilderState, - slice: ProjectSlice, - subprojects: Set = emptySet() -): String { - return replace("//REPOSITORIES", "") - .replace("//DEPENDENCIES", state.buildDependencies(slice, subprojects)) - .replace("//SCRIPTS", state.buildScripts(slice)) - .replace("//TEST_TASK", state.buildTestTask(slice)) - .replace("//VERIFICATIONS", state.buildVerifications(slice)) -} - - -private fun ProjectBuilderState.writeSources(projectDir: File, slice: ProjectSlice) { - fun File.processDir(result: MutableMap, targetRootPath: String, relativePath: String = "") { - listFiles()?.forEach { file -> - val filePath = "$relativePath/${file.name}" - if (file.isDirectory) { - file.processDir(result, targetRootPath, filePath) - } else if (file.exists() && file.length() > 0) { - val targetFile = File(projectDir, "$targetRootPath/$filePath") - targetFile.parentFile.mkdirs() - file.copyTo(targetFile) - } - } - } - - val srcPath = slice.srcPath - val testPath = slice.testPath - - sourceTemplates.forEach { template -> - File(SOURCES_PATH, "$template/main").processDir(mainSources, srcPath) - File(SOURCES_PATH, "$template/test").processDir(testSources, testPath) - } -} - -private fun ProjectSlice.scriptPath(): String { - val languageString = if (language == KOTLIN) "kotlin" else "groovy" - val typeString = when (type) { - ProjectType.KOTLIN_JVM -> "kjvm" - ProjectType.KOTLIN_MULTIPLATFORM -> "kmp" - ProjectType.ANDROID -> "android" - } - return "$BUILD_SCRIPTS_PATH/$languageString/$typeString" -} - -private fun buildSubprojectsIncludes(subprojects: Set): String { - if (subprojects.isEmpty()) return "" - - return subprojects.joinToString("\n", "\n", "\n") { - """include("$it")""" - } -} - -private fun CommonBuilderState.buildExtraSettings(): String { - return if (localCache) { - """ -buildCache { - local { - directory = "${"$"}settingsDir/build-cache" - } -} -""" - } else { - "" - } -} - -private fun CommonBuilderState.buildRootExtension(slice: ProjectSlice): String { - if (slice.engine == null && koverConfig.isDefault) { - return "" - } - - val builder = StringBuilder() - builder.appendLine() - builder.appendLine("kover {") - - if (koverConfig.disabled != null) { - val property = if (slice.language == KOTLIN) "isDisabled" else "disabled" - builder.appendLine("$property = ${koverConfig.disabled}") - } - - if (slice.engine == CoverageEngine.INTELLIJ) { - builder.appendLine(" coverageEngine.set(kotlinx.kover.api.CoverageEngine.INTELLIJ)") - if (koverConfig.intellijVersion != null) { - builder.appendLine(""" intellijEngineVersion.set("${koverConfig.intellijVersion}")""") - } - } - if (slice.engine == CoverageEngine.JACOCO) { - builder.appendLine(" coverageEngine.set(kotlinx.kover.api.CoverageEngine.JACOCO)") - if (koverConfig.jacocoVersion != null) { - builder.appendLine(""" jacocoEngineVersion.set("${koverConfig.jacocoVersion}")""") - } - } - - if (koverConfig.disabledProjects.isNotEmpty()) { - val prefix = if (slice.language == KOTLIN) "setOf(" else "[" - val postfix = if (slice.language == KOTLIN) ")" else "]" - val value = koverConfig.disabledProjects.joinToString(prefix = prefix, postfix = postfix) { "\"$it\"" } - builder.appendLine(" disabledProjects = $value") - } - - if (koverConfig.runAllTestsForProjectTask != null) { - builder.appendLine(" runAllTestsForProjectTask = ${koverConfig.runAllTestsForProjectTask}") - } - - builder.appendLine("}") - - return builder.toString() -} - -private fun ProjectBuilderState.buildTestTask(slice: ProjectSlice): String { - if (testScripts.isEmpty()) { - return "" - } - - val configs = testScripts.map { if (slice.language == KOTLIN) it.kotlin else it.groovy } - - return loadTestTaskTemplate(slice).replace("//KOVER_TEST_CONFIG", configs.joinToString("\n")) -} - -@Suppress("UNUSED_PARAMETER") -private fun ProjectBuilderState.buildVerifications(slice: ProjectSlice): String { - if (rules.isEmpty()) { - return "" - } - - val builder = StringBuilder() - builder.appendLine() - builder.appendLine("tasks.koverVerify {") - - for (rule in rules) { - builder.appendLine(" rule {") - rule.name?.also { builder.appendLine(""" name = "$it"""") } - for (bound in rule.bounds) { - builder.appendLine(" bound {") - bound.minValue?.let { builder.appendLine(" minValue = $it") } - bound.maxValue?.let { builder.appendLine(" maxValue = $it") } - if (bound.valueType != VerificationValueType.COVERED_LINES_PERCENTAGE) { - builder.appendLine(" valueType = kotlinx.kover.api.VerificationValueType.${bound.valueType}") - } - builder.appendLine(" }") - } - builder.appendLine(" }") - } - builder.appendLine("}") - - return builder.toString() -} - -private fun CommonBuilderState.buildSettings(slice: ProjectSlice): String { - return loadSettingsTemplate(slice) - .replace("//SUBPROJECTS", buildSubprojectsIncludes(subprojects.keys)) - .replace("//EXTRA_SETTINGS", buildExtraSettings()) -} - -private fun ProjectBuilderState.buildScripts(slice: ProjectSlice): String { - if (scripts.isEmpty()) { - return "" - } - val configs = scripts.map { if (slice.language == KOTLIN) it.kotlin else it.groovy } - return configs.joinToString("\n", "\n", "\n") -} - -private fun ProjectBuilderState.buildDependencies(slice: ProjectSlice, subprojects: Set): String { - if (dependencies.isEmpty() && subprojects.isEmpty()) { - return "" - } - - val configs: MutableList = mutableListOf(); - - configs += subprojects.map { - if (slice.language == KOTLIN) "implementation(project(\":$it\"))" else "implementation project(':$it')" - } - configs += dependencies.map { if (slice.language == KOTLIN) it.kotlin else it.groovy } - return configs.joinToString("\n", "\n", "\n") -} - - -private fun loadSettingsTemplate(slice: ProjectSlice): String { - return File("$SETTINGS_PATH/settings.${slice.scriptExtension}").readText() -} - -private fun loadScriptTemplate(root: Boolean, slice: ProjectSlice): String { - val filename = if (root) "root" else "subproject" - return File("${slice.scriptPath()}/$filename").readText() -} - -private fun loadTestTaskTemplate(slice: ProjectSlice): String { - return File("${slice.scriptPath()}/testTask").readText() -} diff --git a/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/CommonWriter.kt b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/CommonWriter.kt new file mode 100644 index 00000000..c63f4738 --- /dev/null +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/CommonWriter.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.kover.test.functional.core.writer + +import kotlinx.kover.api.* +import kotlinx.kover.test.functional.core.* +import java.io.PrintWriter + +internal fun PrintWriter.printClassFilters(classFilters: KoverClassFilters, slice: ProjectSlice, indents: Int) { + if (classFilters.excludes.isNotEmpty()) { + indented(indents, "excludes".addAllList(classFilters.excludes, slice.language)) + } + if (classFilters.includes.isNotEmpty()) { + indented(indents, "includes".addAllList(classFilters.includes, slice.language)) + } +} + +internal fun PrintWriter.printVerify(state: TestKoverVerifyConfigState, slice: ProjectSlice, indents: Int) { + val onCheck = state.onCheck + val rules = state.rules + if (onCheck == null && rules.isEmpty()) return + + indented(indents, "verify {") + if (onCheck != null) { + indented(indents + 1, "onCheck".setProperty(onCheck.toString(), slice.language)) + } + rules.forEach { rule -> + indented(indents + 1, "rule {") + if (rule.isEnabled != null) { + indented(indents + 2, "isEnabled = ${rule.isEnabled}") + } + if (rule.name != null) { + indented(indents + 2, "name = ${rule.name?.text(slice.language)}") + } + if (rule.target != null) { + indented(indents + 2, "target = ${rule.target?.enum(slice.language)}") + } + if (rule.overrideClassFilters != null) { + indented(indents + 2, "overrideClassFilters {") + printClassFilters(rule.overrideClassFilters!!, slice, indents + 3) + indented(indents + 2, "}") + } + rule.bounds.forEach { bound -> + indented(indents + 2, "bound {") + if (bound.minValue != null) { + indented(indents + 3, "minValue = ${bound.minValue}") + } + if (bound.maxValue != null) { + indented(indents + 3, "maxValue = ${bound.maxValue}") + } + if (bound.counter != null) { + indented(indents + 3, "counter = ${bound.counter?.enum(slice.language)}") + } + if (bound.valueType != null) { + indented(indents + 3, "valueType = ${bound.valueType?.enum(slice.language)}") + } + indented(indents + 2, "}") + } + indented(indents + 1, "}") + } + indented(indents, "}") +} 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 new file mode 100644 index 00000000..48ee9a30 --- /dev/null +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/KoverExtensionWriter.kt @@ -0,0 +1,79 @@ +/* + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.kover.test.functional.core.writer + +import kotlinx.kover.api.* +import kotlinx.kover.test.functional.core.* +import kotlinx.kover.test.functional.core.ProjectSlice +import kotlinx.kover.test.functional.core.TestKoverProjectConfigState +import java.io.* + +internal fun PrintWriter.printKover(kover: TestKoverProjectConfigState?, slice: ProjectSlice, indents: Int) { + if (kover == null) return + + indented(indents, "kover {") + printDisabled(kover.isDisabled, slice, indents + 1) + printEngine(kover.engine, slice, indents + 1) + printFilters(kover.filters, slice, indents + 1) + printVerify(kover.verify, slice, indents + 1) + indented(indents, "}") +} + + +private fun PrintWriter.printEngine(scriptEngine: CoverageEngineVariant?, slice: ProjectSlice, indents: Int) { + if (scriptEngine == null && slice.engine == null) return + + val value = if (slice.engine != null) { + val clazz = + if (slice.engine == CoverageEngineVendor.INTELLIJ) DefaultIntellijEngine::class else DefaultJacocoEngine::class + clazz.obj(slice.language) + } else { + val clazz = + if (scriptEngine!!.vendor == CoverageEngineVendor.INTELLIJ) IntellijEngine::class else JacocoEngine::class + val new = if (slice.language == GradleScriptLanguage.KOTLIN) { + clazz.qualifiedName + } else { + clazz.qualifiedName + } + "$new(\"${scriptEngine.version}\")" + } + + indented(indents, "engine".setProperty(value, slice.language)) +} + +private fun PrintWriter.printDisabled(isDisabled: Boolean?, slice: ProjectSlice, indents: Int) { + if (isDisabled == null) return + + if (slice.language == GradleScriptLanguage.KOTLIN) { + indented(indents, "isDisabled.set($isDisabled)") + } else { + indented(indents, "disabled = $isDisabled") + } +} + +private fun PrintWriter.printFilters(state: TestKoverProjectFiltersState, slice: ProjectSlice, indents: Int) { + val classes = state.classes + val sourcesets = state.sourcesets + if (sourcesets == null && classes == null) return + + indented(indents, "filters {") + if (classes != null && (classes.excludes.isNotEmpty() || classes.includes.isNotEmpty())) { + indented(indents + 1, "classes {") + printClassFilters(classes, slice, indents + 2) + indented(indents + 1, "}") + } + + if (sourcesets != null) { + indented(indents + 1, "sourcesets {") + if (sourcesets.excludes.isNotEmpty()) { + indented(indents + 2, "excludes".addAllList(sourcesets.excludes, slice.language)) + } + indented(indents + 2, "excludeTests = " + sourcesets.excludeTests) + indented(indents + 1, "}") + } + + indented(indents, "}") +} + diff --git a/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/Languages.kt b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/Languages.kt new file mode 100644 index 00000000..46efb89c --- /dev/null +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/Languages.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.kover.test.functional.core.writer + +import kotlinx.kover.test.functional.core.GradleScriptLanguage +import kotlinx.kover.test.functional.core.ProjectSlice +import java.io.PrintWriter +import kotlin.reflect.* + +internal val ProjectSlice.scriptExtension get() = if (language == GradleScriptLanguage.KOTLIN) "gradle.kts" else "gradle" + +internal fun Iterable.formatList(language: GradleScriptLanguage): String { + val prefix = if (language == GradleScriptLanguage.KOTLIN) "listOf(" else "[" + val postfix = if (language == GradleScriptLanguage.KOTLIN) ")" else "]" + + return prefix + this.joinToString(separator = ",") { "\"$it\"" } + postfix +} + +internal fun String.addAllList(list: Iterable, language: GradleScriptLanguage): String { + val listString = list.formatList(language) + return this + if (language == GradleScriptLanguage.KOTLIN) " += $listString" else ".addAll($listString)" +} + +internal fun KClass<*>.obj(language: GradleScriptLanguage): String { + return if (language == GradleScriptLanguage.KOTLIN) { + qualifiedName!! + } else { + "$qualifiedName.INSTANCE" + } +} + +internal fun String.setProperty(value: String, language: GradleScriptLanguage): String { + return this + if (language == GradleScriptLanguage.KOTLIN) ".set($value)" else " = $value" +} + +private fun indent(count: Int): String { + return when (count) { + 0 -> "" + 1 -> " " + 2 -> " " + 3 -> " " + 4 -> " " + 5 -> " " + else -> " ".repeat(count) + } +} + +@Suppress("UNUSED_PARAMETER") +internal fun String.text(language: GradleScriptLanguage): String { + return "\"$this\"" +} + +internal fun Enum<*>.enum(language: GradleScriptLanguage): String { + return if (language == GradleScriptLanguage.KOTLIN) { + this::class.qualifiedName + '.' + this.name + } else { + "\"$name\"" + } +} + +internal fun PrintWriter.indented(indents: Int, content: String) { + println(indent(indents) + content) +} diff --git a/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/MergedExtensionWriter.kt b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/MergedExtensionWriter.kt new file mode 100644 index 00000000..e5768c83 --- /dev/null +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/MergedExtensionWriter.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.kover.test.functional.core.writer + +import kotlinx.kover.test.functional.core.* +import kotlinx.kover.test.functional.core.ProjectSlice +import java.io.* + +internal fun PrintWriter.printKoverMerged(merged: TestKoverMergedConfigState?, slice: ProjectSlice, indents: Int) { + if (merged == null) return + + indented(indents, "koverMerged {") + printEnabled(merged.enabled, indents + 1) + printFilters(merged.filters, slice, indents + 1) + indented(indents, "}") +} + +private fun PrintWriter.printEnabled(isEnabled: Boolean, indents: Int) { + if (isEnabled) { + indented(indents, "enable()") + } +} + +private fun PrintWriter.printFilters(state: TestKoverMergedFiltersState, slice: ProjectSlice, indents: Int) { + val classes = state.classes + val projects = state.projects + if (projects == null && classes == null) return + + indented(indents, "filters {") + if (classes != null && (classes.excludes.isNotEmpty() || classes.includes.isNotEmpty())) { + indented(indents + 1, "classes {") + printClassFilters(classes, slice, indents + 2) + indented(indents + 1, "}") + } + + if (projects != null) { + indented(indents + 1, "projects {") + if (projects.includes.isNotEmpty()) { + indented(indents + 2, "includes".addAllList(projects.includes, slice.language)) + } + indented(indents + 1, "}") + } + + indented(indents, "}") +} diff --git a/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/SettingsFileWriter.kt b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/SettingsFileWriter.kt new file mode 100644 index 00000000..5af72278 --- /dev/null +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/SettingsFileWriter.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.kover.test.functional.core.writer + +import kotlinx.kover.test.functional.core.* +import java.io.* + +internal fun generateSettingsFile( + sliceDir: File, + slice: ProjectSlice, + projects: Map, + localCache: Boolean +) { + File(sliceDir, "settings.${slice.scriptExtension}").printWriter().use { + it.println("""rootProject.name = "kover-functional-test"""") + it.println() + projects.keys.forEach { path -> + if (path != ":") { + it.println("""include("$path")""") + } + } + + if (localCache) { + it.println(LOCAL_CACHE) + } + } +} + +private const val LOCAL_CACHE = """ +buildCache { + local { + directory = "${"$"}settingsDir/build-cache" + } +} +""" diff --git a/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/Writer.kt b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/Writer.kt new file mode 100644 index 00000000..3d83cb30 --- /dev/null +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/Writer.kt @@ -0,0 +1,290 @@ +/* + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.kover.test.functional.core.writer + +import kotlinx.kover.test.functional.core.GradleScriptLanguage.GROOVY +import kotlinx.kover.test.functional.core.GradleScriptLanguage.KOTLIN +import kotlinx.kover.test.functional.core.PluginsState +import kotlinx.kover.test.functional.core.ProjectBuilderState +import kotlinx.kover.test.functional.core.ProjectSlice +import kotlinx.kover.test.functional.core.ProjectType +import kotlinx.kover.test.functional.core.RepositoriesState +import kotlinx.kover.test.functional.core.TestTaskConfigState +import java.io.* + +private const val SAMPLES_PATH = "src/functionalTest/templates" +private const val SAMPLES_SOURCES_PATH = "$SAMPLES_PATH/sources" + +internal fun initSlice( + rootDir: File, + slice: ProjectSlice, + projects: Map, + localCache: Boolean +): File { + val sliceDir = File(rootDir, slice.encodedString()).also { it.mkdirs() } + generateSettingsFile(sliceDir, slice, projects, localCache) + projects.forEach { (path, state) -> state.generateProject(sliceDir, path, slice) } + return sliceDir +} + +private fun ProjectBuilderState.generateProject(sliceDir: File, path: String, slice: ProjectSlice) { + val subpath = path.replace(':', '/') + val projectDir = File(sliceDir, subpath).also { it.mkdirs() } + copySources(projectDir, slice) + File(projectDir, "build.${slice.scriptExtension}").printWriter().use { + it.printPlugins(plugins, slice) + it.printRepositories(repositories) + it.printDependencies(subprojects, slice) + it.printKover(kover, slice, 0) + it.printKoverMerged(merged, slice, 0) + it.printTestTasks(testTasks, slice) + } +} + + +private fun PrintWriter.printPlugins(plugins: PluginsState, slice: ProjectSlice) { + if (!plugins.useKotlin && !plugins.useKover) return + + println("plugins {") + if (plugins.useKotlin) { + when { + slice.language == GROOVY && slice.type == ProjectType.KOTLIN_JVM -> print(""" id "org.jetbrains.kotlin.jvm"""") + slice.language == GROOVY && slice.type == ProjectType.KOTLIN_MULTIPLATFORM -> print(""" id "org.jetbrains.kotlin.multiplatform"""") + slice.language == KOTLIN && slice.type == ProjectType.KOTLIN_JVM -> print(""" kotlin("jvm")""") + slice.language == KOTLIN && slice.type == ProjectType.KOTLIN_MULTIPLATFORM -> print(""" kotlin("multiplatform")""") + else -> throw Exception("Unsupported test combination: language ${slice.language} and project type ${slice.type}") + } + plugins.kotlinVersion?.let { print(""" version "$it"""") } + println() + } + + if (plugins.useKover) { + if (slice.language == KOTLIN) { + print(""" id("org.jetbrains.kotlinx.kover")""") + } else { + print(""" id "org.jetbrains.kotlinx.kover"""") + } + plugins.koverVersion?.let { print(""" version "$it"""") } + println() + } + println("}") +} + +private fun PrintWriter.printRepositories(repositories: RepositoriesState) { + if (repositories.repositories.isEmpty()) return + + println("repositories {") + repositories.repositories.forEach { + print(" ") + println(it) + } + println("}") +} + +private fun PrintWriter.printDependencies(subprojects: List, slice: ProjectSlice) { + val subprojectsPart = subprojects.joinToString(separator = "\n") { + if (slice.language == KOTLIN) "implementation(project(\"$it\"))" else "implementation project('$it')" + } + + val template = when { + slice.language == GROOVY && slice.type == ProjectType.KOTLIN_JVM -> GROOVY_JVM_DEPS + slice.language == GROOVY && slice.type == ProjectType.KOTLIN_MULTIPLATFORM -> GROOVY_KMP_DEPS + slice.language == KOTLIN && slice.type == ProjectType.KOTLIN_JVM -> KOTLIN_JVM_DEPS + slice.language == KOTLIN && slice.type == ProjectType.KOTLIN_MULTIPLATFORM -> KOTLIN_KMP_DEPS + else -> throw Exception("Unsupported test combination: language ${slice.language} and project type ${slice.type}") + } + println(template.replace(DEPS_PLACEHOLDER, subprojectsPart)) +} + +private fun PrintWriter.printTestTasks(state: TestTaskConfigState, slice: ProjectSlice) { + if (state.excludes == null && state.includes == null) return + + val testTaskName = when { + slice.type == ProjectType.KOTLIN_JVM -> "test" + slice.type == ProjectType.KOTLIN_MULTIPLATFORM && slice.language == KOTLIN -> "named(\"jvmTest\").configure" + slice.type == ProjectType.KOTLIN_MULTIPLATFORM -> "jvmTest" + else -> throw Exception("Project with type ${slice.type} and language ${slice.language} not supported for test task configuring") + } + val extension = + if (slice.language == KOTLIN) "extensions.configure(kotlinx.kover.api.KoverTaskExtension::class)" else "kover" + + println("tasks.$testTaskName {") + println(" $extension {") + if (state.excludes != null) { + val excludesString = state.excludes!!.joinToString(separator = ",") { "\"$it\"" } + println(" excludes.addAll($excludesString)") + } + if (state.includes != null) { + val includesString = state.includes!!.joinToString(separator = ",") { "\"$it\"" } + println(" includes.addAll($includesString)") + } + println(" }") + println("}") +} + +private const val DEPS_PLACEHOLDER = "/*DEPS*/" + +private const val KOTLIN_JVM_DEPS = """ +dependencies { + $DEPS_PLACEHOLDER + testImplementation(kotlin("test")) +} +""" + +private const val KOTLIN_KMP_DEPS = """ +kotlin { + jvm() { + withJava() + } + dependencies { + commonTestImplementation(kotlin("test")) + } + sourceSets { + val jvmMain by getting { + dependencies { + $DEPS_PLACEHOLDER + } + } + } +} +""" + +private const val GROOVY_JVM_DEPS = """ +dependencies { + $DEPS_PLACEHOLDER + testImplementation 'org.jetbrains.kotlin:kotlin-test' +} +""" + +private const val GROOVY_KMP_DEPS = """ +kotlin { + jvm() { + withJava() + } + dependencies { + commonTestImplementation 'org.jetbrains.kotlin:kotlin-test' + } + sourceSets { + jvmMain { + dependencies { + $DEPS_PLACEHOLDER + } + } + } +} +""" + +private fun ProjectBuilderState.copySources(projectDir: File, slice: ProjectSlice) { + fun File.copyInto(targetFile: File) { + listFiles()?.forEach { src -> + val subTarget = File(targetFile, src.name) + if (src.isDirectory) { + subTarget.mkdirs() + src.copyInto(subTarget) + } else if (src.exists() && src.length() > 0) { + src.copyTo(subTarget) + } + } + } + + sourceTemplates.forEach { template -> + File(SAMPLES_SOURCES_PATH, "$template/main").copyInto(File(projectDir, slice.mainPath)) + File(SAMPLES_SOURCES_PATH, "$template/test").copyInto(File(projectDir, slice.testPath)) + } +} + +private val ProjectSlice.mainPath: String + get() { + return when (type) { + ProjectType.KOTLIN_JVM -> "src/main" + ProjectType.KOTLIN_MULTIPLATFORM -> "src/jvmMain" + ProjectType.ANDROID -> "src/jvmMain" + } + } + +private val ProjectSlice.testPath: String + get() { + return when (type) { + ProjectType.KOTLIN_JVM -> "src/test" + ProjectType.KOTLIN_MULTIPLATFORM -> "src/jvmTest" + ProjectType.ANDROID -> "src/jvmTest" + } + } + + +// +//private fun CommonBuilderState.buildRootExtension(slice: ProjectSlice): String { +// if (slice.engine == null && koverConfig.isDefault) { +// return "" +// } +// +// val builder = StringBuilder() +// builder.appendLine() +// builder.appendLine("kover {") +// +// if (koverConfig.disabled != null) { +// val property = if (slice.language == KOTLIN) "isDisabled" else "disabled" +// builder.appendLine("$property = ${koverConfig.disabled}") +// } +// +// if (slice.engine == CoverageEngineVendor.INTELLIJ) { +// builder.appendLine(" coverageEngine.set(kotlinx.kover.api.CoverageEngine.INTELLIJ)") +// if (koverConfig.intellijVersion != null) { +// builder.appendLine(""" intellijEngineVersion.set("${koverConfig.intellijVersion}")""") +// } +// } +// if (slice.engine == CoverageEngineVendor.JACOCO) { +// builder.appendLine(" coverageEngine.set(kotlinx.kover.api.CoverageEngine.JACOCO)") +// if (koverConfig.jacocoVersion != null) { +// builder.appendLine(""" jacocoEngineVersion.set("${koverConfig.jacocoVersion}")""") +// } +// } +// +// if (koverConfig.disabledProjects.isNotEmpty()) { +// val prefix = if (slice.language == KOTLIN) "setOf(" else "[" +// val postfix = if (slice.language == KOTLIN) ")" else "]" +// val value = koverConfig.disabledProjects.joinToString(prefix = prefix, postfix = postfix) { "\"$it\"" } +// builder.appendLine(" disabledProjects = $value") +// } +// +// if (koverConfig.runAllTestsForProjectTask != null) { +// builder.appendLine(" runAllTestsForProjectTask = ${koverConfig.runAllTestsForProjectTask}") +// } +// +// builder.appendLine("}") +// +// return builder.toString() +//} + +// +//@Suppress("UNUSED_PARAMETER") +//private fun ProjectBuilderState.buildVerifications(slice: ProjectSlice): String { +// if (rules.isEmpty()) { +// return "" +// } +// +// val builder = StringBuilder() +// builder.appendLine() +// builder.appendLine("tasks.koverVerify {") +// +// for (rule in rules) { +// builder.appendLine(" rule {") +// rule.name?.also { builder.appendLine(""" name = "$it"""") } +// for (bound in rule.bounds) { +// builder.appendLine(" bound {") +// bound.minValue?.let { builder.appendLine(" minValue = $it") } +// bound.maxValue?.let { builder.appendLine(" maxValue = $it") } +// if (bound.valueType != VerificationValueType.COVERED_PERCENTAGE) { +// builder.appendLine(" valueType = kotlinx.kover.api.VerificationValueType.${bound.valueType}") +// } +// builder.appendLine(" }") +// } +// builder.appendLine(" }") +// } +// builder.appendLine("}") +// +// return builder.toString() +//} + diff --git a/src/functionalTest/templates/samples/different-plugins/build.gradle.kts b/src/functionalTest/templates/samples/different-plugins/build.gradle.kts index 095535e8..4207547b 100644 --- a/src/functionalTest/templates/samples/different-plugins/build.gradle.kts +++ b/src/functionalTest/templates/samples/different-plugins/build.gradle.kts @@ -5,3 +5,5 @@ plugins { repositories { mavenCentral() } + +koverMerged.enable() diff --git a/src/functionalTest/templates/samples/different-plugins/subproject-multiplatform/build.gradle.kts b/src/functionalTest/templates/samples/different-plugins/subproject-multiplatform/build.gradle.kts index 15fecee9..2dc779a1 100644 --- a/src/functionalTest/templates/samples/different-plugins/subproject-multiplatform/build.gradle.kts +++ b/src/functionalTest/templates/samples/different-plugins/subproject-multiplatform/build.gradle.kts @@ -1,5 +1,6 @@ plugins { kotlin("multiplatform") + id("org.jetbrains.kotlinx.kover") } repositories { diff --git a/src/functionalTest/templates/scripts/buildscripts/groovy/kjvm/root b/src/functionalTest/templates/scripts/buildscripts/groovy/kjvm/root deleted file mode 100644 index 8a66aba6..00000000 --- a/src/functionalTest/templates/scripts/buildscripts/groovy/kjvm/root +++ /dev/null @@ -1,13 +0,0 @@ -plugins { - id 'org.jetbrains.kotlin.jvm' version '1.6.10' - id 'org.jetbrains.kotlinx.kover' version '//PLUGIN_VERSION' -} - -repositories { - mavenCentral()//REPOSITORIES -} - -dependencies {//DEPENDENCIES - testImplementation 'org.jetbrains.kotlin:kotlin-test' -} -//KOVER//SCRIPTS//TEST_TASK//VERIFICATIONS diff --git a/src/functionalTest/templates/scripts/buildscripts/groovy/kjvm/subproject b/src/functionalTest/templates/scripts/buildscripts/groovy/kjvm/subproject deleted file mode 100644 index 9cc59b26..00000000 --- a/src/functionalTest/templates/scripts/buildscripts/groovy/kjvm/subproject +++ /dev/null @@ -1,12 +0,0 @@ -plugins { - id 'org.jetbrains.kotlin.jvm' -} - -repositories { - mavenCentral()//REPOSITORIES -} - -dependencies {//DEPENDENCIES - testImplementation 'org.jetbrains.kotlin:kotlin-test' -} -//KOVER//SCRIPTS//TEST_TASK//VERIFICATIONS diff --git a/src/functionalTest/templates/scripts/buildscripts/groovy/kjvm/testTask b/src/functionalTest/templates/scripts/buildscripts/groovy/kjvm/testTask deleted file mode 100644 index a9d184d8..00000000 --- a/src/functionalTest/templates/scripts/buildscripts/groovy/kjvm/testTask +++ /dev/null @@ -1,5 +0,0 @@ -tasks.test { - kover { - //KOVER_TEST_CONFIG - } -} diff --git a/src/functionalTest/templates/scripts/buildscripts/groovy/kmp/root b/src/functionalTest/templates/scripts/buildscripts/groovy/kmp/root deleted file mode 100644 index 7c1727f6..00000000 --- a/src/functionalTest/templates/scripts/buildscripts/groovy/kmp/root +++ /dev/null @@ -1,26 +0,0 @@ -plugins { - id 'org.jetbrains.kotlin.multiplatform' version '1.6.10' - id 'org.jetbrains.kotlinx.kover' version '//PLUGIN_VERSION' -} - -repositories { - mavenCentral()//REPOSITORIES -} - -kotlin { - jvm() { - withJava() - } - - dependencies { - commonTestImplementation 'org.jetbrains.kotlin:kotlin-test' - } - - sourceSets { - jvmMain { - dependencies {//DEPENDENCIES - } - } - } -} -//KOVER//SCRIPTS//TEST_TASK//VERIFICATIONS diff --git a/src/functionalTest/templates/scripts/buildscripts/groovy/kmp/subproject b/src/functionalTest/templates/scripts/buildscripts/groovy/kmp/subproject deleted file mode 100644 index 3f710e4d..00000000 --- a/src/functionalTest/templates/scripts/buildscripts/groovy/kmp/subproject +++ /dev/null @@ -1,25 +0,0 @@ -plugins { - id 'org.jetbrains.kotlin.multiplatform' -} - -repositories { - mavenCentral()//REPOSITORIES -} - -kotlin { - jvm() { - withJava() - } - - dependencies { - commonTestImplementation 'org.jetbrains.kotlin:kotlin-test' - } - - sourceSets { - jvmMain { - dependencies {//DEPENDENCIES - } - } - } -} -//KOVER//SCRIPTS//TEST_TASK//VERIFICATIONS diff --git a/src/functionalTest/templates/scripts/buildscripts/groovy/kmp/testTask b/src/functionalTest/templates/scripts/buildscripts/groovy/kmp/testTask deleted file mode 100644 index 2ccf87c9..00000000 --- a/src/functionalTest/templates/scripts/buildscripts/groovy/kmp/testTask +++ /dev/null @@ -1,5 +0,0 @@ -tasks.jvmTest { - kover { - //KOVER_TEST_CONFIG - } -} diff --git a/src/functionalTest/templates/scripts/buildscripts/kotlin/kjvm/root b/src/functionalTest/templates/scripts/buildscripts/kotlin/kjvm/root deleted file mode 100644 index 2217fed6..00000000 --- a/src/functionalTest/templates/scripts/buildscripts/kotlin/kjvm/root +++ /dev/null @@ -1,13 +0,0 @@ -plugins { - kotlin("jvm") version "1.6.10" - id("org.jetbrains.kotlinx.kover") version "//PLUGIN_VERSION" -} - -repositories { - mavenCentral()//REPOSITORIES -} - -dependencies {//DEPENDENCIES - testImplementation(kotlin("test")) -} -//KOVER//SCRIPTS//TEST_TASK//VERIFICATIONS diff --git a/src/functionalTest/templates/scripts/buildscripts/kotlin/kjvm/subproject b/src/functionalTest/templates/scripts/buildscripts/kotlin/kjvm/subproject deleted file mode 100644 index 3fa1382e..00000000 --- a/src/functionalTest/templates/scripts/buildscripts/kotlin/kjvm/subproject +++ /dev/null @@ -1,12 +0,0 @@ -plugins { - kotlin("jvm") -} - -repositories { - mavenCentral()//REPOSITORIES -} - -dependencies {//DEPENDENCIES - testImplementation(kotlin("test")) -} -//KOVER//SCRIPTS//TEST_TASK//VERIFICATIONS diff --git a/src/functionalTest/templates/scripts/buildscripts/kotlin/kjvm/testTask b/src/functionalTest/templates/scripts/buildscripts/kotlin/kjvm/testTask deleted file mode 100644 index 3a225fde..00000000 --- a/src/functionalTest/templates/scripts/buildscripts/kotlin/kjvm/testTask +++ /dev/null @@ -1,5 +0,0 @@ -tasks.test { - extensions.configure(kotlinx.kover.api.KoverTaskExtension::class) { - //KOVER_TEST_CONFIG - } -} diff --git a/src/functionalTest/templates/scripts/buildscripts/kotlin/kmp/root b/src/functionalTest/templates/scripts/buildscripts/kotlin/kmp/root deleted file mode 100644 index 410231c5..00000000 --- a/src/functionalTest/templates/scripts/buildscripts/kotlin/kmp/root +++ /dev/null @@ -1,26 +0,0 @@ -plugins { - kotlin("multiplatform") version "1.6.10" - id("org.jetbrains.kotlinx.kover") version "//PLUGIN_VERSION" -} - -repositories { - mavenCentral()//REPOSITORIES -} - -kotlin { - jvm() { - withJava() - } - - dependencies { - commonTestImplementation(kotlin("test")) - } - - sourceSets { - val jvmMain by getting { - dependencies {//DEPENDENCIES - } - } - } -} -//KOVER//SCRIPTS//TEST_TASK//VERIFICATIONS diff --git a/src/functionalTest/templates/scripts/buildscripts/kotlin/kmp/subproject b/src/functionalTest/templates/scripts/buildscripts/kotlin/kmp/subproject deleted file mode 100644 index d826c33d..00000000 --- a/src/functionalTest/templates/scripts/buildscripts/kotlin/kmp/subproject +++ /dev/null @@ -1,25 +0,0 @@ -plugins { - kotlin("multiplatform") -} - -repositories { - mavenCentral()//REPOSITORIES -} - -kotlin { - jvm() { - withJava() - } - - dependencies { - commonTestImplementation(kotlin("test")) - } - - sourceSets { - val jvmMain by getting { - dependencies {//DEPENDENCIES - } - } - } -} -//KOVER//SCRIPTS//TEST_TASK//VERIFICATIONS diff --git a/src/functionalTest/templates/scripts/buildscripts/kotlin/kmp/testTask b/src/functionalTest/templates/scripts/buildscripts/kotlin/kmp/testTask deleted file mode 100644 index 0c1f5175..00000000 --- a/src/functionalTest/templates/scripts/buildscripts/kotlin/kmp/testTask +++ /dev/null @@ -1,5 +0,0 @@ -tasks.named("jvmTest").configure { - extensions.configure(kotlinx.kover.api.KoverTaskExtension::class) { - //KOVER_TEST_CONFIG - } -} diff --git a/src/functionalTest/templates/scripts/settings/settings.gradle b/src/functionalTest/templates/scripts/settings/settings.gradle deleted file mode 100644 index 368b66a0..00000000 --- a/src/functionalTest/templates/scripts/settings/settings.gradle +++ /dev/null @@ -1,9 +0,0 @@ -pluginManagement { - repositories { - maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } - mavenCentral() - maven { url 'https://plugins.gradle.org/m2/' } - } -} -rootProject.name = 'plugin-test-project-groovy' -//SUBPROJECTS//EXTRA_SETTINGS diff --git a/src/functionalTest/templates/scripts/settings/settings.gradle.kts b/src/functionalTest/templates/scripts/settings/settings.gradle.kts deleted file mode 100644 index 60f592ab..00000000 --- a/src/functionalTest/templates/scripts/settings/settings.gradle.kts +++ /dev/null @@ -1,9 +0,0 @@ -pluginManagement { - repositories { - maven { url=uri("https://oss.sonatype.org/content/repositories/snapshots") } - mavenCentral() - maven { url=uri("https://plugins.gradle.org/m2/") } - } -} -rootProject.name = "plugin-test-project-kotlin" -//SUBPROJECTS//EXTRA_SETTINGS diff --git a/src/main/kotlin/kotlinx/kover/KoverPlugin.kt b/src/main/kotlin/kotlinx/kover/KoverPlugin.kt index 013bf80d..9aadc639 100644 --- a/src/main/kotlin/kotlinx/kover/KoverPlugin.kt +++ b/src/main/kotlin/kotlinx/kover/KoverPlugin.kt @@ -1,398 +1,15 @@ /* - * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ package kotlinx.kover -import kotlinx.kover.adapters.* -import kotlinx.kover.api.* -import kotlinx.kover.api.KoverPaths.MERGED_HTML_REPORT_DEFAULT_PATH -import kotlinx.kover.api.KoverNames.CHECK_TASK_NAME -import kotlinx.kover.api.KoverNames.COLLECT_REPORTS_TASK_NAME -import kotlinx.kover.api.KoverNames.MERGED_HTML_REPORT_TASK_NAME -import kotlinx.kover.api.KoverNames.HTML_REPORT_TASK_NAME -import kotlinx.kover.api.KoverNames.REPORT_TASK_NAME -import kotlinx.kover.api.KoverNames.VERIFY_TASK_NAME -import kotlinx.kover.api.KoverNames.MERGED_REPORT_TASK_NAME -import kotlinx.kover.api.KoverNames.XML_REPORT_TASK_NAME -import kotlinx.kover.api.KoverNames.ROOT_EXTENSION_NAME -import kotlinx.kover.api.KoverNames.TASK_EXTENSION_NAME -import kotlinx.kover.api.KoverNames.VERIFICATION_GROUP -import kotlinx.kover.api.KoverNames.MERGED_VERIFY_TASK_NAME -import kotlinx.kover.api.KoverNames.MERGED_XML_REPORT_TASK_NAME -import kotlinx.kover.api.KoverPaths.ALL_PROJECTS_REPORTS_DEFAULT_PATH -import kotlinx.kover.api.KoverPaths.PROJECT_HTML_REPORT_DEFAULT_PATH -import kotlinx.kover.api.KoverPaths.MERGED_XML_REPORT_DEFAULT_PATH -import kotlinx.kover.api.KoverPaths.PROJECT_XML_REPORT_DEFAULT_PATH -import kotlinx.kover.engines.commons.* -import kotlinx.kover.engines.commons.CoverageAgent -import kotlinx.kover.engines.intellij.* -import kotlinx.kover.tasks.* +import kotlinx.kover.appliers.* import org.gradle.api.* -import org.gradle.api.provider.* -import org.gradle.api.tasks.* -import org.gradle.api.tasks.testing.* -import org.gradle.process.* -import java.io.File -import kotlin.reflect.* class KoverPlugin : Plugin { - private val defaultJacocoVersion = "0.8.7" - override fun apply(target: Project) { - val koverExtension = target.createKoverExtension() - val agents = AgentsFactory.createAgents(target, koverExtension) - - val providers = target.createProviders(agents) - - target.allprojects { - it.applyToProject(providers, agents) - } - target.createCollectingTask() - - target.createMergedTasks(providers) - } - - private fun Project.applyToProject(providers: BuildProviders, agents: Map) { - val projectProviders = - providers.projects[path] - ?: throw GradleException("Kover: Providers for project '$name' ('$path') was not found") - - val xmlReportTask = createKoverProjectTask( - XML_REPORT_TASK_NAME, - KoverXmlReportTask::class, - providers, - projectProviders - ) { - it.xmlReportFile.set(layout.buildDirectory.file(PROJECT_XML_REPORT_DEFAULT_PATH)) - it.description = "Generates code coverage XML report for all enabled test tasks in one project." - } - - val htmlReportTask = createKoverProjectTask( - HTML_REPORT_TASK_NAME, - KoverHtmlReportTask::class, - providers, - projectProviders - ) { - it.htmlReportDir.set(it.project.layout.buildDirectory.dir(PROJECT_HTML_REPORT_DEFAULT_PATH)) - it.description = "Generates code coverage HTML report for all enabled test tasks in one project." - } - - val verifyTask = createKoverProjectTask( - VERIFY_TASK_NAME, - KoverVerificationTask::class, - providers, - projectProviders - ) { - it.onlyIf { t -> (t as KoverVerificationTask).rules.isNotEmpty() } - it.description = "Verifies code coverage metrics of one project based on specified rules." - } - - tasks.create(REPORT_TASK_NAME) { - it.group = VERIFICATION_GROUP - it.dependsOn(xmlReportTask) - it.dependsOn(htmlReportTask) - it.description = "Generates code coverage HTML and XML reports for all enabled test tasks in one project." - } - - tasks.configureEach { - if (it.name == CHECK_TASK_NAME) { - it.dependsOn(verifyTask) - } - } - - tasks.withType(Test::class.java).configureEach { t -> - t.configTestTask(providers, agents) - } - } - - private fun Project.createMergedTasks(providers: BuildProviders) { - val xmlReportTask = createKoverMergedTask( - MERGED_XML_REPORT_TASK_NAME, - KoverMergedXmlReportTask::class, - providers - ) { - it.xmlReportFile.set(layout.buildDirectory.file(MERGED_XML_REPORT_DEFAULT_PATH)) - it.description = "Generates code coverage XML report for all enabled test tasks in all projects." - } - - val htmlReportTask = createKoverMergedTask( - MERGED_HTML_REPORT_TASK_NAME, - KoverMergedHtmlReportTask::class, - providers - ) { - it.htmlReportDir.set(layout.buildDirectory.dir(MERGED_HTML_REPORT_DEFAULT_PATH)) - it.description = "Generates code coverage HTML report for all enabled test tasks in all projects." - } - - val reportTask = tasks.create(MERGED_REPORT_TASK_NAME) { - it.group = VERIFICATION_GROUP - it.dependsOn(xmlReportTask) - it.dependsOn(htmlReportTask) - it.description = "Generates code coverage HTML and XML reports for all enabled test tasks in all projects." - } - - val verifyTask = createKoverMergedTask( - MERGED_VERIFY_TASK_NAME, - KoverMergedVerificationTask::class, - providers - ) { - it.onlyIf { t -> (t as KoverMergedVerificationTask).rules.isNotEmpty() } - it.description = "Verifies code coverage metrics of all projects based on specified rules." - } - - tasks.configureEach { - if (it.name == CHECK_TASK_NAME) { - it.dependsOn(provider { - val koverExtension = extensions.getByType(KoverExtension::class.java) - if (koverExtension.generateReportOnCheck) { - listOf(reportTask, verifyTask) - } else { - listOf(verifyTask) - } - }) - } - } - } - - - private fun Project.createKoverMergedTask( - taskName: String, - type: KClass, - providers: BuildProviders, - block: (T) -> Unit - ): T { - val task = tasks.create(taskName, type.java) - - task.group = VERIFICATION_GROUP - - providers.projects.forEach { (projectPath, m) -> - task.binaryReportFiles.put(projectPath, NestedFiles(task.project.objects, m.reports)) - task.srcDirs.put(projectPath, NestedFiles(task.project.objects, m.sources)) - task.outputDirs.put(projectPath, NestedFiles(task.project.objects, m.output)) - } - - task.coverageEngine.set(providers.engine) - task.classpath.set(providers.classpath) - task.dependsOn(providers.merged.tests) - - val disabledProvider = providers.merged.disabled - task.onlyIf { !disabledProvider.get() } - - block(task) - return task - } - - private fun Project.createCollectingTask() { - tasks.create(COLLECT_REPORTS_TASK_NAME, KoverCollectingTask::class.java) { task -> - task.group = VERIFICATION_GROUP - task.description = "Collects all projects reports into one directory." - task.outputDir.set(project.layout.buildDirectory.dir(ALL_PROJECTS_REPORTS_DEFAULT_PATH)) - // disable UP-TO-DATE check for task: it will be executed every time - task.outputs.upToDateWhen { false } - - allprojects { proj -> - val xmlReportTask = - proj.tasks.withType(KoverXmlReportTask::class.java).getByName(XML_REPORT_TASK_NAME) - val htmlReportTask = - proj.tasks.withType(KoverHtmlReportTask::class.java).getByName(HTML_REPORT_TASK_NAME) - - task.mustRunAfter(xmlReportTask) - task.mustRunAfter(htmlReportTask) - - task.xmlFiles[proj.path] = xmlReportTask.xmlReportFile - task.htmlDirs[proj.path] = htmlReportTask.htmlReportDir - } - } - } - - - private fun Project.createKoverProjectTask( - taskName: String, - type: KClass, - providers: BuildProviders, - projectProviders: ProjectProviders, - block: (T) -> Unit - ): T { - tasks.findByName(taskName)?.let { - throw GradleException("Kover task '$taskName' already exist. Plugin should not be applied in child project if it has already been applied in one of the parent projects.") - } - - val task = tasks.create(taskName, type.java) - task.group = VERIFICATION_GROUP - - task.coverageEngine.set(providers.engine) - task.classpath.set(providers.classpath) - task.srcDirs.set(projectProviders.sources) - task.outputDirs.set(projectProviders.output) - - // it is necessary to read all binary reports because project's classes can be invoked in another project - task.binaryReportFiles.set(projectProviders.reports) - task.dependsOn(projectProviders.tests) - - val disabledProvider = projectProviders.disabled - task.onlyIf { !disabledProvider.get() } - task.onlyIf { !(it as KoverProjectTask).binaryReportFiles.get().isEmpty } - - block(task) - - return task - } - - private fun Project.createKoverExtension(): KoverExtension { - val extension = extensions.create(ROOT_EXTENSION_NAME, KoverExtension::class.java, objects) - extension.isDisabled = false - extension.coverageEngine.set(CoverageEngine.INTELLIJ) - extension.intellijEngineVersion.set(defaultIntellijVersion.toString()) - extension.jacocoEngineVersion.set(defaultJacocoVersion) - - afterEvaluate(CollectDisabledProjectsPathsAction(extension)) - - return extension - } - - private fun Test.configTestTask( - providers: BuildProviders, - agents: Map - ) { - val taskExtension = extensions.create(TASK_EXTENSION_NAME, KoverTaskExtension::class.java, project.objects) - - taskExtension.isDisabled = false - taskExtension.binaryReportFile.set(project.provider { - val koverExtension = providers.koverExtension.get() - val suffix = if (koverExtension.coverageEngine.get() == CoverageEngine.INTELLIJ) ".ic" else ".exec" - project.layout.buildDirectory.get().file("kover/$name$suffix").asFile - }) - - val pluginContainer = project.plugins - val excludeAndroidPackages = - project.provider { pluginContainer.androidPluginIsApplied && !providers.koverExtension.get().instrumentAndroidPackage } - - jvmArgumentProviders.add( - CoverageArgumentProvider( - this, - agents, - taskExtension, - providers.koverExtension, - excludeAndroidPackages - ) - ) - - val sourceErrorProvider = project.provider { - File(taskExtension.binaryReportFile.get().parentFile, "coverage-error.log") - } - val targetErrorProvider = project.layout.buildDirectory.file("kover/errors/$name.log").map { it.asFile } - - doFirst(BinaryReportCleanupAction(project.path, providers.koverExtension, taskExtension)) - doLast(MoveIntellijErrorLogAction(sourceErrorProvider, targetErrorProvider)) - } -} - -/* - To support parallel tests, both Coverage Engines work in append to data file mode. - For this reason, before starting the tests, it is necessary to clear the file from the results of previous runs. -*/ -private class BinaryReportCleanupAction( - private val projectPath: String, - private val koverExtensionProvider: Provider, - private val taskExtension: KoverTaskExtension -) : Action { - override fun execute(task: Task) { - val koverExtension = koverExtensionProvider.get() - val file = taskExtension.binaryReportFile.get() - - // always delete previous data file - file.delete() - - if (!taskExtension.isDisabled - && !koverExtension.isDisabled - && !koverExtension.disabledProjectsPaths.contains(projectPath) - && koverExtension.coverageEngine.get() == CoverageEngine.INTELLIJ - ) { - // IntelliJ engine expected empty file for parallel test execution. - // Since it is impossible to know in advance whether the tests will be run in parallel, we always create an empty file. - file.createNewFile() - } - } -} - -private class CollectDisabledProjectsPathsAction( - private val koverExtension: KoverExtension, -) : Action { - override fun execute(project: Project) { - val allProjects = project.allprojects - val paths = allProjects.associate { it.name to mutableListOf() } - allProjects.forEach { paths.getValue(it.name) += it.path } - - val result: MutableSet = mutableSetOf() - - koverExtension.disabledProjects.map { - if (it.startsWith(':')) { - result += it - } else { - val projectPaths = paths[it] ?: return@map - if (projectPaths.size > 1) { - throw GradleException("Kover configuring error: ambiguous name of the excluded project '$it': suitable projects with paths $projectPaths. Consider using fully-qualified name starting with ':'") - } - result += projectPaths - } - } - - koverExtension.disabledProjectsPaths = result - } -} - -private class MoveIntellijErrorLogAction( - private val sourceFile: Provider, - private val targetFile: Provider -) : Action { - override fun execute(task: Task) { - val origin = sourceFile.get() - if (origin.exists() && origin.isFile) { - origin.copyTo(targetFile.get(), true) - origin.delete() - } - } -} - -private class CoverageArgumentProvider( - private val task: Task, - private val agents: Map, - @get:Nested val taskExtension: KoverTaskExtension, - @get:Nested val koverExtension: Provider, - @get:Input val excludeAndroidPackage: Provider -) : CommandLineArgumentProvider, Named { - - private val projectPath: String = task.project.path - - @Internal - override fun getName(): String { - return "koverArgumentsProvider" - } - - override fun asArguments(): MutableIterable { - val koverExtensionValue = koverExtension.get() - - if (taskExtension.isDisabled - || koverExtensionValue.isDisabled - || koverExtensionValue.disabledProjectsPaths.contains(projectPath) - ) { - return mutableListOf() - } - - if (excludeAndroidPackage.get()) { - /* - The instrumentation of android classes often causes errors when using third-party - frameworks (see https://github.com/Kotlin/kotlinx-kover/issues/89). - - Because android classes are not part of the project, in any case they do not get into the report, - and they can be excluded from instrumentation. - - FIXME Remove this code if the IntelliJ Agent stops changing project classes during instrumentation - */ - taskExtension.excludes = taskExtension.excludes + "android.*" + "com.android.*" - } - - return agents.getFor(koverExtensionValue.coverageEngine.get()).buildCommandLineArgs(task, taskExtension) + target.applyToProject() + target.applyMerged() } } diff --git a/src/main/kotlin/kotlinx/kover/Providers.kt b/src/main/kotlin/kotlinx/kover/Providers.kt deleted file mode 100644 index 6b33a3b7..00000000 --- a/src/main/kotlin/kotlinx/kover/Providers.kt +++ /dev/null @@ -1,135 +0,0 @@ -package kotlinx.kover - -import kotlinx.kover.adapters.* -import kotlinx.kover.api.* -import kotlinx.kover.engines.commons.* -import kotlinx.kover.engines.commons.CoverageAgent -import org.gradle.api.* -import org.gradle.api.file.* -import org.gradle.api.provider.* -import org.gradle.api.tasks.testing.* -import java.io.* - - -internal fun Project.createProviders(agents: Map): BuildProviders { - val projects: MutableMap = mutableMapOf() - - allprojects { - projects[it.path] = ProjectProviders( - it.provider { it.files(if (runAllTests()) allBinaryReports() else it.binaryReports(this)) }, - it.provider { if (runAllTests()) allTestTasks() else it.testTasks(this) }, - it.provider { it.collectDirs(this).first }, - it.provider { it.collectDirs(this).second }, - it.provider { it.isDisabled(this) } - ) - } - - val engineProvider = provider { extensions.getByType(KoverExtension::class.java).coverageEngine.get() } - - val classpathProvider: Provider = provider { - val koverExtension = extensions.getByType(KoverExtension::class.java) - agents.getFor(koverExtension.coverageEngine.get()).classpath - } - - val extensionProvider = provider { extensions.getByType(KoverExtension::class.java) } - - - val allReportsProvider: Provider = provider { files(allBinaryReports()) } - val allTestsProvider = provider { allTestTasks() } - val koverDisabledProvider = provider { extensions.getByType(KoverExtension::class.java).isDisabled } - - - // all sources and all outputs providers are unused, so NOW it can return empty file collection - val emptyProvider: Provider = provider { files() } - val mergedProviders = - ProjectProviders( - allReportsProvider, - allTestsProvider, - emptyProvider, - emptyProvider, - koverDisabledProvider) - - return BuildProviders(projects, mergedProviders, engineProvider, classpathProvider, extensionProvider) -} - - -internal fun Project.allTestTasks(): List { - return allprojects.flatMap { it.testTasks(this) } -} - -internal fun Project.allBinaryReports(): List { - return allprojects.flatMap { it.binaryReports(this) } -} - - -internal fun Project.testTasks(rootProject: Project): List { - if (isDisabled(rootProject)) { - return emptyList() - } - - return tasks.withType(Test::class.java) - .filterNot { t -> t.extensions.getByType(KoverTaskExtension::class.java).isDisabled } -} - -internal fun Project.binaryReports(root: Project): List { - if (isDisabled(root)) { - return emptyList() - } - - return tasks.withType(Test::class.java).asSequence() - .map { t -> t.extensions.getByType(KoverTaskExtension::class.java) } - // process binary report only from tasks with enabled cover - .filterNot { e -> e.isDisabled } - .map { e -> e.binaryReportFile.get() } - // process binary report only from tasks with sources - .filter { f -> f.exists() } - .toList() -} - -private fun Project.collectDirs(root: Project): Pair { - if (isDisabled(root)) { - return files() to files() - } - - val srcDirs = HashMap() - val outDirs = HashMap() - - createAdapters().forEach { - val dirs = it.findDirs(this) - srcDirs += dirs.sources.asSequence().map { f -> f.canonicalPath to f } - outDirs += dirs.output.asSequence().map { f -> f.canonicalPath to f } - } - - val src = srcDirs.asSequence().map { it.value }.filter { it.exists() && it.isDirectory }.toList() - val out = outDirs.asSequence().map { it.value }.filter { it.exists() && it.isDirectory }.toList() - - return files(src) to files(out) -} - -private fun Project.isDisabled(root: Project): Boolean { - val koverExtension = root.extensions.getByType(KoverExtension::class.java) - return koverExtension.isDisabled || koverExtension.disabledProjectsPaths.contains(path) -} - -private fun Project.runAllTests(): Boolean { - return extensions.getByType(KoverExtension::class.java).runAllTestsForProjectTask -} - - -internal class BuildProviders( - val projects: Map, - val merged: ProjectProviders, - - val engine: Provider, - val classpath: Provider, - val koverExtension: Provider -) - -internal class ProjectProviders( - val reports: Provider, - val tests: Provider>, - val sources: Provider, - val output: Provider, - val disabled: Provider -) - diff --git a/src/main/kotlin/kotlinx/kover/adapters/AndroidPluginAdapter.kt b/src/main/kotlin/kotlinx/kover/adapters/AndroidPluginAdapter.kt deleted file mode 100644 index d9228841..00000000 --- a/src/main/kotlin/kotlinx/kover/adapters/AndroidPluginAdapter.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.kover.adapters - -import com.android.build.gradle.* -import kotlinx.kover.adapters.api.* -import org.gradle.api.* - -class AndroidPluginAdapter : CompilationPluginAdapter { - - override fun findDirs(project: Project): PluginDirs { - return safe(project) { - this.plugins.findPlugin("android") ?: return@safe PluginDirs(emptyList(), emptyList()) - - val extension = project.extensions.findByType(BaseExtension::class.java) ?: return@safe PluginDirs( - emptyList(), - emptyList() - ) - - val sourceDirs = extension.sourceSets.asSequence() - .filter { !it.name.startsWith("test") && !it.name.startsWith("androidTest") } - .map { it.java }.toList() - .flatMap { it.srcDirs } - - - - return@safe PluginDirs(sourceDirs, emptyList()) - } - } - -} diff --git a/src/main/kotlin/kotlinx/kover/adapters/KotlinAndroidPluginAdapter.kt b/src/main/kotlin/kotlinx/kover/adapters/KotlinAndroidPluginAdapter.kt deleted file mode 100644 index 56aa6648..00000000 --- a/src/main/kotlin/kotlinx/kover/adapters/KotlinAndroidPluginAdapter.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.kover.adapters - -import kotlinx.kover.adapters.api.* -import org.gradle.api.* -import org.jetbrains.kotlin.gradle.dsl.* - -class KotlinAndroidPluginAdapter : CompilationPluginAdapter { - - override fun findDirs(project: Project): PluginDirs { - return safe(project) { - this.plugins.findPlugin("kotlin-android") ?: return@safe PluginDirs(emptyList(), emptyList()) - - val extension = project.extensions.findByType(KotlinAndroidProjectExtension::class.java) ?: return@safe PluginDirs( - emptyList(), - emptyList() - ) - - - val sourceDirs = extension.target.compilations - .filter { !it.name.endsWith("Test") } - .flatMap { it.allKotlinSourceSets } - .map { it.kotlin } - .flatMap { it.srcDirs } - - val outputDirs = extension.target.compilations - .filter { !it.name.endsWith("Test") } - .flatMap { it.output.classesDirs } - - return@safe PluginDirs(sourceDirs, outputDirs) - } - } - -} diff --git a/src/main/kotlin/kotlinx/kover/adapters/OldJavaPluginAdapter.kt b/src/main/kotlin/kotlinx/kover/adapters/OldJavaPluginAdapter.kt deleted file mode 100644 index 9640083c..00000000 --- a/src/main/kotlin/kotlinx/kover/adapters/OldJavaPluginAdapter.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.kover.adapters - -import kotlinx.kover.adapters.api.* -import org.gradle.api.* -import org.gradle.api.tasks.* - -class OldJavaPluginAdapter : CompilationPluginAdapter { - - override fun findDirs(project: Project): PluginDirs { - return safe(project) { - this.plugins.findPlugin("java") ?: return@safe PluginDirs(emptyList(), emptyList()) - - val sourceSetContainer = project.extensions.findByType( - SourceSetContainer::class.java - ) ?: return@safe PluginDirs(emptyList(), emptyList()) - - val sourceSets = sourceSetContainer.filter { it.name != SourceSet.TEST_SOURCE_SET_NAME } - - val sourceDirs = sourceSets.flatMap { it.allSource.srcDirs } - val outputDirs = sourceSets.flatMap { it.output.classesDirs } - - return@safe PluginDirs(sourceDirs, outputDirs) - } - } - -} diff --git a/src/main/kotlin/kotlinx/kover/adapters/PluginAdaptersFactory.kt b/src/main/kotlin/kotlinx/kover/adapters/PluginAdaptersFactory.kt deleted file mode 100644 index ec4d2ca6..00000000 --- a/src/main/kotlin/kotlinx/kover/adapters/PluginAdaptersFactory.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.kover.adapters - -import kotlinx.kover.adapters.api.* -import org.gradle.api.plugins.PluginContainer - -internal fun createAdapters(): List { - return listOf( - OldJavaPluginAdapter(), - KotlinMultiplatformPluginAdapter(), - AndroidPluginAdapter(), - KotlinAndroidPluginAdapter() - ) -} - -val PluginContainer.androidPluginIsApplied: Boolean - get() { - return findPlugin("android") != null || findPlugin("kotlin-android") != null - } diff --git a/src/main/kotlin/kotlinx/kover/adapters/api/CompilationPluginAdapter.kt b/src/main/kotlin/kotlinx/kover/adapters/api/CompilationPluginAdapter.kt deleted file mode 100644 index 9555412e..00000000 --- a/src/main/kotlin/kotlinx/kover/adapters/api/CompilationPluginAdapter.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.kover.adapters.api - -import org.gradle.api.* -import java.io.* - -interface CompilationPluginAdapter { - fun findDirs(project: Project): PluginDirs -} - - -data class PluginDirs(val sources: List, val output: List) - -internal inline fun safe(project: Project, block: Project.() -> PluginDirs): PluginDirs { - return try { - project.block() - } catch (e: Throwable) { - when (e) { - is NoSuchMethodError, is NoSuchFieldError, is ClassNotFoundException, is NoClassDefFoundError -> { - project.logger.info("Problem occurred in Kover source set adapter", e) - PluginDirs(emptyList(), emptyList()) - } - else -> throw e - } - } -} diff --git a/src/main/kotlin/kotlinx/kover/api/CoverageEngines.kt b/src/main/kotlin/kotlinx/kover/api/CoverageEngines.kt new file mode 100644 index 00000000..da9560bc --- /dev/null +++ b/src/main/kotlin/kotlinx/kover/api/CoverageEngines.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +@file:Suppress("RedundantVisibilityModifier") + +package kotlinx.kover.api + +import kotlinx.kover.api.KoverVersions.DEFAULT_INTELLIJ_VERSION +import kotlinx.kover.api.KoverVersions.DEFAULT_JACOCO_VERSION +import org.gradle.api.tasks.* + +public interface CoverageEngineVariant { + @get:Input + public val vendor: CoverageEngineVendor + @get:Input + public val version: String +} + +public enum class CoverageEngineVendor { + INTELLIJ, + JACOCO +} + +public class IntellijEngine(override val version: String): CoverageEngineVariant { + override val vendor: CoverageEngineVendor = CoverageEngineVendor.INTELLIJ + override fun toString(): String = "IntelliJ Coverage Engine $version" +} +public object DefaultIntellijEngine: CoverageEngineVariant { + override val vendor: CoverageEngineVendor = CoverageEngineVendor.INTELLIJ + override val version: String = DEFAULT_INTELLIJ_VERSION +} + +public class JacocoEngine(override val version: String): CoverageEngineVariant { + override val vendor: CoverageEngineVendor = CoverageEngineVendor.JACOCO + override fun toString(): String = "JaCoCo Coverage Engine $version" +} +public object DefaultJacocoEngine: CoverageEngineVariant { + override val vendor: CoverageEngineVendor = CoverageEngineVendor.JACOCO + override val version: String = DEFAULT_JACOCO_VERSION +} diff --git a/src/main/kotlin/kotlinx/kover/api/KoverConfig.kt b/src/main/kotlin/kotlinx/kover/api/KoverConfig.kt new file mode 100644 index 00000000..1336268c --- /dev/null +++ b/src/main/kotlin/kotlinx/kover/api/KoverConfig.kt @@ -0,0 +1,322 @@ +/* + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +@file:Suppress("RedundantVisibilityModifier") + +package kotlinx.kover.api + +import org.gradle.api.* +import org.gradle.api.file.* +import org.gradle.api.model.* +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.* +import javax.annotation.* +import javax.inject.* + +public open class KoverProjectConfig @Inject constructor(objects: ObjectFactory) { + public val isDisabled: Property = objects.property(Boolean::class.java) + + public val engine: Property = objects.property(CoverageEngineVariant::class.java) + + internal val filters: KoverProjectFilters = objects.newInstance(KoverProjectFilters::class.java, objects) + + internal val instrumentation: KoverProjectInstrumentation = + objects.newInstance(KoverProjectInstrumentation::class.java) + + internal val xmlReport: KoverProjectXmlConfig = objects.newInstance(KoverProjectXmlConfig::class.java, objects) + + internal val htmlReport: KoverProjectHtmlConfig = objects.newInstance(KoverProjectHtmlConfig::class.java, objects) + + internal val verify: KoverVerifyConfig = objects.newInstance(KoverVerifyConfig::class.java, objects) + + public fun filters(config: Action) { + config.execute(filters) + } + + public fun instrumentation(config: Action) { + config.execute(instrumentation) + } + + public fun xmlReport(config: Action) { + config.execute(xmlReport) + } + + public fun htmlReport(config: Action) { + config.execute(htmlReport) + } + + public fun verify(config: Action) { + config.execute(verify) + } +} + +public open class KoverProjectFilters @Inject constructor(private val objects: ObjectFactory) { + internal val classes: Property = objects.property(KoverClassFilters::class.java) + //it's ok to create an instance by a constructor because this object won't be used in Gradle + .value(KoverClassFilters()) + + internal val sourceSets: Property = objects.property(KoverSourceSetFilters::class.java) + //it's ok to create an instance by a constructor because this object won't be used in Gradle + .value(KoverSourceSetFilters()) + + public fun classes(config: Action) { + val classFilters = objects.newInstance(KoverClassFilters::class.java) + config.execute(classFilters) + classes.set(classFilters) + } + + public fun sourcesets(config: Action) { + val sourceSetFilters = objects.newInstance(KoverSourceSetFilters::class.java) + config.execute(sourceSetFilters) + sourceSets.set(sourceSetFilters) + } +} + +public open class KoverProjectInstrumentation { + public val excludeTasks: MutableSet = mutableSetOf() +} + +public open class KoverProjectXmlConfig @Inject constructor(objects: ObjectFactory) { + public val onCheck: Property = objects.property(Boolean::class.java) + public val reportFile: RegularFileProperty = objects.fileProperty() + internal val taskFilters: KoverProjectFilters = objects.newInstance(KoverProjectFilters::class.java, objects) + + public fun overrideFilters(config: Action) { + config.execute(taskFilters) + } +} + +public open class KoverProjectHtmlConfig @Inject constructor(private val objects: ObjectFactory) { + public val onCheck: Property = objects.property(Boolean::class.java) + + public val reportDir: DirectoryProperty = objects.directoryProperty() + internal val taskFilters: KoverProjectFilters = objects.newInstance(KoverProjectFilters::class.java, objects) + + public fun overrideFilters(config: Action) { + config.execute(taskFilters) + } +} + + +public open class KoverMergedConfig @Inject constructor(objects: ObjectFactory) { + internal var isEnabled: Property = objects.property(Boolean::class.java) + internal val filters: KoverMergedFilters = objects.newInstance(KoverMergedFilters::class.java, objects) + internal val xmlReport: KoverMergedXmlConfig = objects.newInstance(KoverMergedXmlConfig::class.java, objects) + internal val htmlReport: KoverMergedHtmlConfig = objects.newInstance(KoverMergedHtmlConfig::class.java, objects) + internal val verify: KoverVerifyConfig = objects.newInstance(KoverVerifyConfig::class.java, objects) + + public fun enable() { + isEnabled.set(true) + } + + public fun filters(config: Action) { + config.execute(filters) + } + + public fun xmlReport(config: Action) { + config.execute(xmlReport) + } + + public fun htmlReport(config: Action) { + config.execute(htmlReport) + } + + public fun verify(config: Action) { + config.execute(verify) + } +} + +public open class KoverMergedFilters @Inject constructor(private val objects: ObjectFactory) { + internal val classes: Property = + objects.property(KoverClassFilters::class.java).value(KoverClassFilters()) + + internal val projects: Property = + objects.property(KoverProjectsFilters::class.java).value(KoverProjectsFilters()) + + public fun classes(config: Action) { + val classFilters = objects.newInstance(KoverClassFilters::class.java) + config.execute(classFilters) + classes.set(classFilters) + } + + public fun projects(config: Action) { + val projectsFilters = objects.newInstance(KoverProjectsFilters::class.java) + config.execute(projectsFilters) + projects.set(projectsFilters) + } +} + + +public open class KoverMergedXmlConfig @Inject constructor(private val objects: ObjectFactory) { + public val onCheck: Property = objects.property(Boolean::class.java) + + /** + * Specifies file path of generated XML report file with coverage data. + */ + public val reportFile: RegularFileProperty = objects.fileProperty() + + internal val classes: Property = objects.property(KoverClassFilters::class.java) + + public fun overrideClassFilters(config: Action) { + val classFilters = objects.newInstance(KoverClassFilters::class.java) + config.execute(classFilters) + classes.set(classFilters) + } +} + +public open class KoverMergedHtmlConfig @Inject constructor(private val objects: ObjectFactory) { + + public val onCheck: Property = objects.property(Boolean::class.java) + + /** + * Specifies directory path of generated HTML report. + */ + public val reportDir: DirectoryProperty = objects.directoryProperty() + + internal val classes: Property = objects.property(KoverClassFilters::class.java) + + public fun overrideClassFilters(config: Action) { + val classFilters = objects.newInstance(KoverClassFilters::class.java) + config.execute(classFilters) + classes.set(classFilters) + } +} + +public open class KoverProjectsFilters { + @get:Input + public val includes: MutableList = mutableListOf() +} + + +public open class KoverVerifyConfig @Inject constructor(private val objects: ObjectFactory) { + public val onCheck: Property = objects.property(Boolean::class.java).value(true) + + internal val rules: ListProperty = objects.listProperty(VerificationRule::class.java) + + public fun rule(configureRule: Action) { + rules.add(objects.newInstance(VerificationRule::class.java, objects).also { configureRule.execute(it) }) + } +} + +public open class KoverSourceSetFilters { + @get:Input + public val excludes: MutableSet = mutableSetOf() + + @get:Input + public var excludeTests: Boolean = true +} + +public open class KoverClassFilters { + @get:Input + public val includes: MutableList = mutableListOf() + + @get:Input + public val excludes: MutableList = mutableListOf() +} + +public open class VerificationRule @Inject constructor(private val objects: ObjectFactory) { + @get:Input + public var isEnabled: Boolean = true + + @get:Input + @get:Nullable + @get:Optional + public var name: String? = null + + @get:Input + public var target: VerificationTarget = VerificationTarget.ALL + + /** + * Absent default value to indicate that class filters are not overridden for the rule. + */ + @get:Nested + @get:Optional + internal val filters: Property = objects.property(KoverClassFilters::class.java) + + @get:Nested + internal val bounds: ListProperty = objects.listProperty(VerificationBound::class.java) + + public fun overrideClassFilters(config: Action) { + if (!filters.isPresent) { + filters.set(objects.newInstance(KoverClassFilters::class.java)) + } + config.execute(filters.get()) + } + + public fun bound(configureBound: Action) { + bounds.add(objects.newInstance(VerificationBound::class.java).also { configureBound.execute(it) }) + } +} + +public open class VerificationBound { + /** + * Minimal value to compare with counter value. + */ + @get:Input + @get:Nullable + @get:Optional + public var minValue: Int? = null + + /** + * Maximal value to compare with counter value. + */ + @get:Input + @get:Nullable + @get:Optional + public var maxValue: Int? = null + + /** + * TODO + */ + @get:Input + public var counter: CounterType = CounterType.LINE + + /** + * Type of lines counter value to compare with minimal and maximal values if them defined. + * Default is [VerificationValueType.COVERED_PERCENTAGE] + */ + @get:Input + public var valueType: VerificationValueType = VerificationValueType.COVERED_PERCENTAGE +} + +/** + * TODO + */ +public enum class VerificationTarget { + /** + * TODO + */ + ALL, + + /** + * TODO + */ + CLASS, + + /** + * TODO + */ + PACKAGE +} + +/** + * TODO + */ +public enum class CounterType { + LINE, + INSTRUCTION, + BRANCH +} + + +/** + * Type of lines counter value to compare with minimal and maximal values if them defined. + */ +public enum class VerificationValueType { + COVERED_COUNT, + MISSED_COUNT, + COVERED_PERCENTAGE, + MISSED_PERCENTAGE +} diff --git a/src/main/kotlin/kotlinx/kover/api/KoverConstants.kt b/src/main/kotlin/kotlinx/kover/api/KoverConstants.kt index 8353963d..7d3e03f1 100644 --- a/src/main/kotlin/kotlinx/kover/api/KoverConstants.kt +++ b/src/main/kotlin/kotlinx/kover/api/KoverConstants.kt @@ -1,10 +1,17 @@ +/* + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +@file:Suppress("RedundantVisibilityModifier") + package kotlinx.kover.api public object KoverNames { public const val CHECK_TASK_NAME = "check" public const val VERIFICATION_GROUP = "verification" - public const val ROOT_EXTENSION_NAME = "kover" + public const val PROJECT_EXTENSION_NAME = "kover" + public const val MERGED_EXTENSION_NAME = "koverMerged" public const val TASK_EXTENSION_NAME = "kover" public const val MERGED_XML_REPORT_TASK_NAME = "koverMergedXmlReport" @@ -15,17 +22,24 @@ public object KoverNames { public const val XML_REPORT_TASK_NAME = "koverXmlReport" public const val HTML_REPORT_TASK_NAME = "koverHtmlReport" public const val REPORT_TASK_NAME = "koverReport" - public const val COLLECT_REPORTS_TASK_NAME = "koverCollectReports" public const val VERIFY_TASK_NAME = "koverVerify" + + public const val CONFIGURATION_NAME = "KoverEngineConfig" } public object KoverPaths { - public const val MERGED_HTML_REPORT_DEFAULT_PATH = "reports/kover/html" - public const val MERGED_XML_REPORT_DEFAULT_PATH = "reports/kover/report.xml" + public const val MERGED_HTML_REPORT_DEFAULT_PATH = "reports/kover/merged/html" + public const val MERGED_XML_REPORT_DEFAULT_PATH = "reports/kover/merged/xml/report.xml" + public const val MERGED_VERIFICATION_REPORT_DEFAULT_PATH = "reports/kover/merged/verification/errors.txt" - public const val PROJECT_HTML_REPORT_DEFAULT_PATH = "reports/kover/project-html" - public const val PROJECT_XML_REPORT_DEFAULT_PATH = "reports/kover/project-xml/report.xml" + public const val PROJECT_HTML_REPORT_DEFAULT_PATH = "reports/kover/html" + public const val PROJECT_XML_REPORT_DEFAULT_PATH = "reports/kover/xml/report.xml" + public const val PROJECT_VERIFICATION_REPORT_DEFAULT_PATH = "reports/kover/verification/errors.txt" +} - public const val ALL_PROJECTS_REPORTS_DEFAULT_PATH = "reports/kover/projects" +public object KoverVersions { + internal const val MINIMAL_INTELLIJ_VERSION = "1.0.668" + internal const val DEFAULT_INTELLIJ_VERSION = "1.0.668" + internal const val DEFAULT_JACOCO_VERSION = "0.8.8" } diff --git a/src/main/kotlin/kotlinx/kover/api/KoverExtension.kt b/src/main/kotlin/kotlinx/kover/api/KoverExtension.kt deleted file mode 100644 index d7fabfcb..00000000 --- a/src/main/kotlin/kotlinx/kover/api/KoverExtension.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.kover.api - -import org.gradle.api.model.* -import org.gradle.api.provider.* -import org.gradle.api.tasks.* - -open class KoverExtension(objects: ObjectFactory) { - - /** - * Specifies whether instrumentation is disabled for all test tasks of all projects. - */ - @get:Input - public var isDisabled: Boolean = false - - /** - * Specifies the coverage engine to be used to collect execution data. - */ - @get:Input - public val coverageEngine: Property = objects.property(CoverageEngine::class.java) - - /** - * Specifies the version of Intellij-coverage dependency. - */ - @get:Input - public val intellijEngineVersion: Property = objects.property(String::class.java) - - /** - * Specifies the version of JaCoCo dependency. - */ - @get:Input - public val jacocoEngineVersion: Property = objects.property(String::class.java) - - /** - * Specifies whether the reports will be generated within 'check' task execution. - */ - @get:Input - public var generateReportOnCheck: Boolean = true - - /** - * Specifies the projects to be disabled from instrumentation and reportings. - */ - @get:Input - public var disabledProjects: Set = emptySet() - - /** - * Specifies whether the classes from 'android' and 'com.android' packages should be included if Android plugin is applied. - */ - @get:Input - public var instrumentAndroidPackage: Boolean = false - - /** - * Specifies whether to perform all test tasks from all projects for Kover single-project tasks. - * If the value is `false`, then executed only test tasks of the project for which its Kover task is called. - */ - @get:Input - public var runAllTestsForProjectTask: Boolean = false - - /** - * Fully-qualified path of disabled projects. - * It is filled in only automatically based on custom values in `disabledProjects'. - */ - @get:Internal - internal var disabledProjectsPaths: Set = emptySet() -} - -public enum class CoverageEngine { - INTELLIJ, - JACOCO -} diff --git a/src/main/kotlin/kotlinx/kover/api/KoverTaskExtension.kt b/src/main/kotlin/kotlinx/kover/api/KoverTaskExtension.kt index cf3caa4b..b8b2cd72 100644 --- a/src/main/kotlin/kotlinx/kover/api/KoverTaskExtension.kt +++ b/src/main/kotlin/kotlinx/kover/api/KoverTaskExtension.kt @@ -1,32 +1,34 @@ /* - * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ @file:Suppress("RedundantVisibilityModifier") package kotlinx.kover.api +import org.gradle.api.file.RegularFileProperty import org.gradle.api.model.* -import org.gradle.api.provider.* +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property import org.gradle.api.tasks.* -import java.io.* /** * Extension for Kover plugin that additionally configures test tasks and * runs them with coverage agent to generate coverage execution data. */ -open class KoverTaskExtension(objects: ObjectFactory) { +public open class KoverTaskExtension(objects: ObjectFactory) { /** * Specifies whether instrumentation is disabled for an extended test task. */ @get:Input - public var isDisabled: Boolean = false + @get:JvmName("getIsDisabled") + public val isDisabled: Property = objects.property(Boolean::class.java) /** * Specifies file path of generated binary file with coverage data. */ @get:OutputFile - public val binaryReportFile: Property = objects.property(File::class.java) + public val reportFile: RegularFileProperty = objects.fileProperty() /** * Specifies class instrumentation inclusion rules. @@ -37,7 +39,7 @@ open class KoverTaskExtension(objects: ObjectFactory) { * It's possible to use `*` and `?` wildcards. */ @get:Input - public var includes: List = emptyList() + public val includes: ListProperty = objects.listProperty(String::class.java) /** * Specifies class instrumentation exclusion rules. @@ -48,5 +50,16 @@ open class KoverTaskExtension(objects: ObjectFactory) { * It's possible to use `*` and `?` wildcards. */ @get:Input - public var excludes: List = emptyList() + public val excludes: ListProperty = objects.listProperty(String::class.java) + + + // DEPRECATIONS + // TODO delete in 0.9 version + @get:Internal + @Deprecated( + message = "Property was renamed in Kover API version 2", + replaceWith = ReplaceWith("reportFile"), + level = DeprecationLevel.ERROR + ) + public val binaryReportFile: RegularFileProperty? = null } diff --git a/src/main/kotlin/kotlinx/kover/api/Rules.kt b/src/main/kotlin/kotlinx/kover/api/Rules.kt deleted file mode 100644 index 18ff50da..00000000 --- a/src/main/kotlin/kotlinx/kover/api/Rules.kt +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.kover.api - -import org.gradle.api.* -import org.gradle.api.tasks.* -import javax.annotation.* - -/** - * Simple verification rule for code coverage. - * Works only with lines counter. - */ -public interface VerificationRule { - /** - * Unique ID of the rule. - */ - @get:Internal - public val id: Int - - /** - * Custom name of the rule. - */ - @get:Input - @get:Nullable - @get:Optional - public var name: String? - - /** - * Added constraints on the values of code coverage metrics. - */ - @get:Input - public val bounds: List - - /** - * Add a constraint on the value of the code coverage metric. - */ - public fun bound(configureBound: Action) -} - -public interface VerificationBound { - /** - * ID of the bound unique in the rule. - */ - @get:Internal - public val id: Int - - /** - * Minimal value to compare with counter value. - */ - @get:Input - @get:Nullable - @get:Optional - public var minValue: Int? - - /** - * Maximal value to compare with counter value. - */ - @get:Input - @get:Nullable - @get:Optional - public var maxValue: Int? - - /** - * Type of lines counter value to compare with minimal and maximal values if them defined. - * Default is [VerificationValueType.COVERED_LINES_PERCENTAGE] - */ - @get:Input - public var valueType: VerificationValueType -} - -/** - * Type of lines counter value to compare with minimal and maximal values if them defined. - */ -public enum class VerificationValueType { - COVERED_LINES_COUNT, - MISSED_LINES_COUNT, - COVERED_LINES_PERCENTAGE -} diff --git a/src/main/kotlin/kotlinx/kover/appliers/KoverMergedApplier.kt b/src/main/kotlin/kotlinx/kover/appliers/KoverMergedApplier.kt new file mode 100644 index 00000000..f4ac654d --- /dev/null +++ b/src/main/kotlin/kotlinx/kover/appliers/KoverMergedApplier.kt @@ -0,0 +1,253 @@ +/* + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.kover.appliers + +import kotlinx.kover.api.* +import kotlinx.kover.api.KoverNames.CHECK_TASK_NAME +import kotlinx.kover.api.KoverNames.CONFIGURATION_NAME +import kotlinx.kover.api.KoverNames.MERGED_HTML_REPORT_TASK_NAME +import kotlinx.kover.api.KoverNames.MERGED_REPORT_TASK_NAME +import kotlinx.kover.api.KoverNames.MERGED_VERIFY_TASK_NAME +import kotlinx.kover.api.KoverNames.MERGED_XML_REPORT_TASK_NAME +import kotlinx.kover.api.KoverNames.VERIFICATION_GROUP +import kotlinx.kover.api.KoverPaths.MERGED_HTML_REPORT_DEFAULT_PATH +import kotlinx.kover.api.KoverPaths.MERGED_VERIFICATION_REPORT_DEFAULT_PATH +import kotlinx.kover.api.KoverPaths.MERGED_XML_REPORT_DEFAULT_PATH +import kotlinx.kover.tasks.* +import org.gradle.api.* +import org.gradle.api.file.* +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.testing.* +import org.gradle.configurationcache.extensions.* + +internal fun Project.applyMerged() { + val extension = createMergedExtension() + afterEvaluate(ProcessMergeExtensionAction(extension)) +} + +private fun Project.createMergedExtension(): KoverMergedConfig { + val extension = extensions.create(KoverNames.MERGED_EXTENSION_NAME, KoverMergedConfig::class.java, objects) + extension.isEnabled.set(false) + extension.xmlReport.onCheck.set(false) + extension.xmlReport.reportFile.set(layout.buildDirectory.file(MERGED_XML_REPORT_DEFAULT_PATH)) + extension.xmlReport.classes.set(extension.filters.classes) + extension.htmlReport.onCheck.set(false) + extension.htmlReport.reportDir.set(layout.buildDirectory.dir(MERGED_HTML_REPORT_DEFAULT_PATH)) + extension.htmlReport.classes.set(extension.filters.classes) + extension.verify.onCheck.set(false) + return extension +} + +private class ProcessMergeExtensionAction(private val extension: KoverMergedConfig) : Action { + override fun execute(container: Project) { + // don't create tasks if merge wasn't enabled + if (!extension.isEnabled.get()) { + return + } + + val extensionByProject = container.projectsExtensionsProvider(extension, container.allprojects) + val engineProvider = container.engineProvider(extensionByProject) + val testsProvider = container.instrumentedTasksProvider(extensionByProject) + + val xmlTask = container.createMergedTask( + MERGED_XML_REPORT_TASK_NAME, + extension.xmlReport.classes, + extensionByProject, + engineProvider, + testsProvider, + { e -> e.xmlReport.taskFilters.sourceSets.get() } + ) { + it.reportFile.set(extension.xmlReport.reportFile) + it.description = "Generates code coverage XML report for all enabled test tasks in specified projects." + } + + val htmlTask = container.createMergedTask( + MERGED_HTML_REPORT_TASK_NAME, + extension.htmlReport.classes, + extensionByProject, + engineProvider, + testsProvider, + { e -> e.htmlReport.taskFilters.sourceSets.get() } + ) { + it.reportDir.set(extension.htmlReport.reportDir) + it.description = "Generates code coverage HTML report for all enabled test tasks in specified projects." + } + + val verifyTask = container.createMergedTask( + MERGED_VERIFY_TASK_NAME, + extension.filters.classes, + extensionByProject, + engineProvider, + testsProvider, + { e -> e.filters.sourceSets.get() } + ) { + it.rules.set(extension.verify.rules) + it.resultFile.set(container.layout.buildDirectory.file(MERGED_VERIFICATION_REPORT_DEFAULT_PATH)) + it.description = "Verifies code coverage metrics of specified projects based on specified rules." + } + // TODO `onlyIf` block moved out from config lambda because of bug in Kotlin compiler - it implicitly adds closure on `Project` inside onlyIf's lambda + verifyTask.onlyIf { extension.verify.hasActiveRules() } + + + container.tasks.create(MERGED_REPORT_TASK_NAME) { + it.group = VERIFICATION_GROUP + it.dependsOn(xmlTask) + it.dependsOn(htmlTask) + it.description = "Generates code coverage HTML and XML reports for all enabled test tasks in one project." + } + + container.tasks.configureEach { + if (it.name == CHECK_TASK_NAME) { + it.dependsOn(container.provider { + val tasks = mutableListOf() + if (extension.xmlReport.onCheck.get()) { + tasks += xmlTask + } + if (extension.htmlReport.onCheck.get()) { + tasks += htmlTask + } + if (extension.verify.onCheck.get() && extension.verify.hasActiveRules()) { + // don't add dependency if there is no active verification rules https://github.com/Kotlin/kotlinx-kover/issues/168 + tasks += verifyTask + } + tasks + }) + } + } + } +} + +private inline fun Project.createMergedTask( + taskName: String, + classFilters: Provider, + extensionByProject: Provider>, + engineProvider: Provider, + testsProvider: Provider>, + crossinline filterExtractor: (KoverProjectConfig) -> KoverSourceSetFilters, + crossinline block: (T) -> Unit +): T { + val task = tasks.create(taskName, T::class.java) { + it.files.set(mergedFilesProvider(extensionByProject, filterExtractor)) + it.engine.set(engineProvider) + it.dependsOn(testsProvider) + + it.classFilters.set(classFilters) + it.group = VERIFICATION_GROUP + block(it) + } + // TODO `onlyIf` block moved out from config lambda because of bug in Kotlin compiler - it implicitly adds closure on `Project` inside onlyIf's lambda + // execute task only if there is at least one binary report + task.onlyIf { t -> (t as KoverReportTask).files.get().any { f -> !f.value.binaryReportFiles.isEmpty } } + + return task +} + +private fun Project.projectsExtensionsProvider( + extension: KoverMergedConfig, + allProjects: Iterable +): Provider> { + return provider { + val projects = filterProjects(extension.filters.projects.get(), allProjects) + + val notAppliedProjects = + projects.filter { it.extensions.findByType(KoverProjectConfig::class.java) == null }.map { it.path } + if (notAppliedProjects.isNotEmpty()) { + throw GradleException("Can't create Kover merge tasks: Kover plugin not applied in projects $notAppliedProjects") + } + projects.associateWith { it.extensions.getByType(KoverProjectConfig::class.java) } + } +} + + +private inline fun Project.mergedFilesProvider( + extensionByProject: Provider>, + crossinline filterExtractor: (KoverProjectConfig) -> KoverSourceSetFilters +): Provider> { + return provider { + extensionByProject.get() + .map { (project, extension) -> project.path to project.projectFiles(filterExtractor(extension), extension) } + .associate { it } + } +} + + +private fun filterProjects(filters: KoverProjectsFilters, allProjects: Iterable): List { + if (filters.includes.isEmpty()) { + return allProjects.toList() + } + + val projectsByPath = allProjects.associateBy { p -> p.path } + val pathsByName = allProjects.associate { it.name to mutableListOf() } + allProjects.forEach { pathsByName.getValue(it.name) += it.path } + + return filters.includes.map { + if (it.startsWith(':')) { + projectsByPath[it] + ?: throw GradleException("Kover configuring error: not found project '$it' for merged tasks") + } else { + val paths = pathsByName[it] + ?: throw GradleException("Kover configuring error: not found project '$it' for merged tasks") + if (paths.size > 1) { + throw GradleException("Kover configuring error: ambiguous name of the project '$it' for merged tasks: suitable projects with paths $paths. Consider using fully-qualified name starting with ':'") + } + projectsByPath[paths[0]]!! + } + } +} + + +private fun Project.engineProvider(extensionByProject: Provider>): Provider { + val archiveOperations: ArchiveOperations = this.serviceOf() + // configuration already created in all projects at this moment because merge tasks creates in afterEvaluate step + val config = configurations.getByName(CONFIGURATION_NAME) + // the plugin is always applied to the containing project + val containerEngine = extensions.getByType(KoverProjectConfig::class.java).engine + + val containerPath = path + return provider { + val variants = extensionByProject.get().map { ComparableEngineVariant(it.value.engine.get()) }.toSet() + // check same engine variants are used + if (variants.size > 1) { + throw GradleException("Can't create Kover merge tasks: different coverage engines are used in projects") + } + val variant = variants.first() + if (variant != ComparableEngineVariant(containerEngine.get())) { + throw GradleException("Can't create Kover merge tasks: project engines are different from the engine from the containing project '$containerPath'") + } + engineByVariant(variant, config, archiveOperations) + } +} + + +private fun Project.instrumentedTasksProvider(extensionByProject: Provider>): Provider> { + return provider { extensionByProject.get().flatMap { it.key.instrumentedTasks(it.value) } } +} + + +/** + * Wrapper for [CoverageEngineVariant] to group applied engines. + */ +private class ComparableEngineVariant(origin: CoverageEngineVariant) : CoverageEngineVariant { + override val vendor: CoverageEngineVendor = origin.vendor + override val version: String = origin.version + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ComparableEngineVariant + + if (vendor != other.vendor) return false + if (version != other.version) return false + + return true + } + + override fun hashCode(): Int { + var result = vendor.hashCode() + result = 31 * result + version.hashCode() + return result + } +} diff --git a/src/main/kotlin/kotlinx/kover/appliers/KoverProjectApplier.kt b/src/main/kotlin/kotlinx/kover/appliers/KoverProjectApplier.kt new file mode 100644 index 00000000..1376daa7 --- /dev/null +++ b/src/main/kotlin/kotlinx/kover/appliers/KoverProjectApplier.kt @@ -0,0 +1,231 @@ +/* + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.kover.appliers + +import kotlinx.kover.api.* +import kotlinx.kover.api.KoverNames.CONFIGURATION_NAME +import kotlinx.kover.api.KoverNames.HTML_REPORT_TASK_NAME +import kotlinx.kover.api.KoverNames.VERIFY_TASK_NAME +import kotlinx.kover.api.KoverNames.XML_REPORT_TASK_NAME +import kotlinx.kover.engines.commons.* +import kotlinx.kover.lookup.* +import kotlinx.kover.tasks.* +import org.gradle.api.* +import org.gradle.api.artifacts.* +import org.gradle.api.file.ArchiveOperations +import org.gradle.api.provider.* +import org.gradle.api.tasks.testing.* +import org.gradle.configurationcache.extensions.* +import java.io.* + +internal fun Project.applyToProject() { + val extension = createProjectExtension() + val config = createEngineConfig(extension.engine) + + val engineProvider = engineProvider(config, extension.engine) + + tasks.withType(Test::class.java).configureEach { t -> + t.applyToTestTask(extension, engineProvider) + } + + val testsProvider = instrumentedTasksProvider(extension) + + val xmlTask = createTask( + XML_REPORT_TASK_NAME, + extension.xmlReport.taskFilters, + extension, + engineProvider, + testsProvider, + ) { + it.reportFile.set(extension.xmlReport.reportFile) + it.description = "Generates code coverage XML report for all enabled test tasks in one project." + } + + val htmlTask = createTask( + HTML_REPORT_TASK_NAME, + extension.htmlReport.taskFilters, + extension, + engineProvider, + testsProvider, + ) { + it.reportDir.set(extension.htmlReport.reportDir) + it.description = "Generates code coverage HTML report for all enabled test tasks in one project." + } + + val verifyTask = createTask( + VERIFY_TASK_NAME, + extension.filters, + extension, + engineProvider, + testsProvider, + ) { + it.rules.set(extension.verify.rules) + it.resultFile.set(layout.buildDirectory.file(KoverPaths.PROJECT_VERIFICATION_REPORT_DEFAULT_PATH)) + it.description = "Verifies code coverage metrics of one project based on specified rules." + } + // TODO `onlyIf` block moved out from config lambda because of bug in Kotlin compiler - it implicitly adds closure on `Project` inside onlyIf's lambda + verifyTask.onlyIf { extension.verify.hasActiveRules() } + + + tasks.create(KoverNames.REPORT_TASK_NAME) { + it.group = KoverNames.VERIFICATION_GROUP + it.dependsOn(xmlTask) + it.dependsOn(htmlTask) + it.description = "Generates code coverage HTML and XML reports for all enabled test tasks in one project." + } + + + tasks.configureEach { + if (it.name == KoverNames.CHECK_TASK_NAME) { + it.dependsOn(provider { + // don't add dependency if Kover is disabled + if (extension.isDisabled.get()) { + return@provider emptyList() + } + + val tasks = mutableListOf() + if (extension.xmlReport.onCheck.get()) { + tasks += xmlTask + } + if (extension.htmlReport.onCheck.get()) { + tasks += htmlTask + } + if (extension.verify.onCheck.get() && extension.verify.hasActiveRules()) { + // don't add dependency if there is no active verification rules https://github.com/Kotlin/kotlinx-kover/issues/168 + tasks += verifyTask + } + tasks + }) + } + } +} + +private inline fun Project.createTask( + taskName: String, + filters: KoverProjectFilters, + extension: KoverProjectConfig, + engineProvider: Provider, + testsProvider: Provider>, + crossinline block: (T) -> Unit +): T { + val task = tasks.create(taskName, T::class.java) { + it.files.put(path, projectFilesProvider(extension, filters.sourceSets)) + it.engine.set(engineProvider) + it.dependsOn(testsProvider) + it.classFilters.set(filters.classes) + it.group = KoverNames.VERIFICATION_GROUP + block(it) + } + + // TODO `onlyIf` block moved out from config lambda because of bug in Kotlin compiler - it implicitly adds closure on `Project` inside onlyIf's lambda + // execute task only if Kover not disabled for project + task.onlyIf { !extension.isDisabled.get() } + // execute task only if there is at least one binary report + task.onlyIf { t -> (t as KoverReportTask).files.get().any { f -> !f.value.binaryReportFiles.isEmpty } } + + return task +} + +private fun Project.createProjectExtension(): KoverProjectConfig { + val extension = extensions.create(KoverNames.PROJECT_EXTENSION_NAME, KoverProjectConfig::class.java, objects) + // default values + + extension.isDisabled.set(false) + extension.engine.set(DefaultIntellijEngine) + extension.xmlReport.reportFile.set(layout.buildDirectory.file(KoverPaths.PROJECT_XML_REPORT_DEFAULT_PATH)) + extension.xmlReport.onCheck.set(false) + extension.xmlReport.taskFilters.classes.set(extension.filters.classes) + extension.xmlReport.taskFilters.sourceSets.set(extension.filters.sourceSets) + extension.htmlReport.reportDir.set(layout.buildDirectory.dir(KoverPaths.PROJECT_HTML_REPORT_DEFAULT_PATH)) + extension.htmlReport.onCheck.set(false) + extension.htmlReport.taskFilters.classes.set(extension.filters.classes) + extension.htmlReport.taskFilters.sourceSets.set(extension.filters.sourceSets) + extension.verify.onCheck.set(true) + + return extension +} + +private fun Project.createEngineConfig(engineVariantProvider: Provider): Configuration { + val config = project.configurations.create(CONFIGURATION_NAME) + config.isVisible = false + config.isTransitive = true + config.description = "Kotlin Kover Plugin configuration for Coverage Engine" + + config.defaultDependencies { default -> + val variant = engineVariantProvider.get() + EngineManager.dependencies(variant).forEach { + default.add(dependencies.create(it)) + } + } + return config +} + +private fun Project.engineProvider( + config: Configuration, + engineVariantProvider: Provider +): Provider { + val archiveOperations: ArchiveOperations = project.serviceOf() + return project.provider { engineByVariant(engineVariantProvider.get(), config, archiveOperations) } +} + +internal fun engineByVariant( + variant: CoverageEngineVariant, + config: Configuration, + archiveOperations: ArchiveOperations +): EngineDetails { + val jarFile = EngineManager.findJarFile(variant, config, archiveOperations) + return EngineDetails(variant, jarFile, config) +} + +internal fun Project.projectFilesProvider( + extension: KoverProjectConfig, + sourceSetFiltersProvider: Provider +): Provider { + return provider { projectFiles(sourceSetFiltersProvider.get(), extension) } +} + +internal fun Project.projectFiles( + filters: KoverSourceSetFilters, + extension: KoverProjectConfig +): ProjectFiles { + val directories = DirsLookup.lookup(project, filters) + val reportFiles = files(binaryReports(extension)) + return ProjectFiles(reportFiles, directories.sources, directories.outputs) +} + +internal fun Project.instrumentedTasks(extension: KoverProjectConfig): List { + return tasks.withType(Test::class.java).asSequence() + // task can be disabled in the project extension + .filterNot { extension.instrumentation.excludeTasks.contains(it.name) } + .filterNot { t -> t.extensions.getByType(KoverTaskExtension::class.java).isDisabled.get() } + .toList() +} + +private fun Project.instrumentedTasksProvider(extension: KoverProjectConfig): Provider> { + return provider { instrumentedTasks(extension) } +} + + +private fun Project.binaryReports(extension: KoverProjectConfig): List { + if (extension.isDisabled.get()) { + return emptyList() + } + + return tasks.withType(Test::class.java).asSequence() + // task can be disabled in the project extension + .filterNot { extension.instrumentation.excludeTasks.contains(it.name) } + .map { t -> t.extensions.getByType(KoverTaskExtension::class.java) } + // process binary report only from tasks with enabled cover + .filterNot { e -> e.isDisabled.get() } + .map { e -> e.reportFile.get().asFile } + // process binary report only from tasks with sources + .filter { f -> f.exists() } + .toList() +} + + +internal fun KoverVerifyConfig.hasActiveRules(): Boolean { + return rules.get().any { rule -> rule.isEnabled } +} diff --git a/src/main/kotlin/kotlinx/kover/appliers/KoverTaskApplier.kt b/src/main/kotlin/kotlinx/kover/appliers/KoverTaskApplier.kt new file mode 100644 index 00000000..c2016635 --- /dev/null +++ b/src/main/kotlin/kotlinx/kover/appliers/KoverTaskApplier.kt @@ -0,0 +1,130 @@ +/* + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.kover.appliers + +import kotlinx.kover.api.* +import kotlinx.kover.engines.commons.* +import kotlinx.kover.tasks.* +import org.gradle.api.* +import org.gradle.api.file.RegularFile +import org.gradle.api.provider.* +import org.gradle.api.tasks.* +import org.gradle.api.tasks.testing.* +import org.gradle.configurationcache.extensions.* +import org.gradle.process.* +import java.io.* + + +internal fun Test.applyToTestTask( + projectExtension: KoverProjectConfig, + engineProvider: Provider +) { + val extension = createTaskExtension(projectExtension) + + val disabledProvider = project.provider { + (projectExtension.isDisabled.get() || extension.isDisabled.get() || + projectExtension.instrumentation.excludeTasks.contains(name)) + } + + jvmArgumentProviders.add(CoverageArgumentProvider(this, engineProvider, disabledProvider, extension)) + + val sourceErrorProvider = project.provider { + File(extension.reportFile.get().asFile.parentFile, "coverage-error.log") + } + val targetErrorProvider = project.layout.buildDirectory.file("kover/errors/$name.log").map { it.asFile } + + doFirst(BinaryReportCleanupAction(disabledProvider, extension.reportFile, engineProvider.map { e -> e.variant })) + doLast(MoveIntellijErrorLogAction(sourceErrorProvider, targetErrorProvider)) +} + +private fun Task.createTaskExtension(projectExtension: KoverProjectConfig): KoverTaskExtension { + val taskExtension = + extensions.create(KoverNames.TASK_EXTENSION_NAME, KoverTaskExtension::class.java, project.objects) + + taskExtension.isDisabled.set(false) + 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") + }) + + return taskExtension +} + + +private class CoverageArgumentProvider( + private val task: Task, + @get:Nested val engineProvider: Provider, + @get:Input val disabledProvider: Provider, + @get:Nested val taskExtension: KoverTaskExtension +) : CommandLineArgumentProvider, Named { + @Internal + override fun getName(): String { + return "koverArgumentsProvider" + } + + override fun asArguments(): MutableIterable { + if (disabledProvider.get()) { + return mutableListOf() + } + + val reportFile = taskExtension.reportFile.get().asFile + val classFilters = KoverClassFilters() + + classFilters.includes += taskExtension.includes.get() + classFilters.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). + + Because android classes are not part of the project, in any case they do not get into the report, + and they can be excluded from instrumentation. + + FIXME Remove this code if the IntelliJ Agent stops changing project classes during instrumentation + */ + classFilters.excludes += "android.*" + classFilters.excludes += "com.android.*" + + return EngineManager.buildAgentArgs(engineProvider.get(), task, reportFile, classFilters) + } +} + + +/* + To support parallel tests, both Coverage Engines work in append to data file mode. + For this reason, before starting the tests, it is necessary to clear the file from the results of previous runs. +*/ +private class BinaryReportCleanupAction( + private val disabledProvider: Provider, + private val reportFileProvider: Provider, + private val engineVariantProvider: Provider, +) : Action { + override fun execute(task: Task) { + if (disabledProvider.get()) { + return + } + val file = reportFileProvider.get().asFile + // always delete previous data file + file.delete() + if (engineVariantProvider.get().vendor == CoverageEngineVendor.INTELLIJ) { + // IntelliJ engine expected empty file for parallel test execution. + // Since it is impossible to know in advance whether the tests will be run in parallel, we always create an empty file. + file.createNewFile() + } + } +} + +private class MoveIntellijErrorLogAction( + private val sourceFile: Provider, + private val targetFile: Provider +) : Action { + override fun execute(task: Task) { + val origin = sourceFile.get() + if (origin.exists() && origin.isFile) { + origin.copyTo(targetFile.get(), true) + origin.delete() + } + } +} diff --git a/src/main/kotlin/kotlinx/kover/engines/commons/AgentsFactory.kt b/src/main/kotlin/kotlinx/kover/engines/commons/AgentsFactory.kt deleted file mode 100644 index 2bf4ccac..00000000 --- a/src/main/kotlin/kotlinx/kover/engines/commons/AgentsFactory.kt +++ /dev/null @@ -1,19 +0,0 @@ -package kotlinx.kover.engines.commons - -import kotlinx.kover.api.* -import kotlinx.kover.engines.intellij.* -import kotlinx.kover.engines.jacoco.* -import org.gradle.api.* - -internal object AgentsFactory { - fun createAgents(project: Project, koverExtension: KoverExtension): Map { - return mapOf( - CoverageEngine.INTELLIJ to project.createIntellijAgent(koverExtension), - CoverageEngine.JACOCO to project.createJacocoAgent(koverExtension), - ) - } -} - -internal fun Map.getFor(engine: CoverageEngine): CoverageAgent { - return this[engine] ?: throw GradleException("Coverage agent for Coverage Engine '$engine' not found") -} diff --git a/src/main/kotlin/kotlinx/kover/engines/commons/CoverageAgent.kt b/src/main/kotlin/kotlinx/kover/engines/commons/CoverageAgent.kt deleted file mode 100644 index 77197f25..00000000 --- a/src/main/kotlin/kotlinx/kover/engines/commons/CoverageAgent.kt +++ /dev/null @@ -1,11 +0,0 @@ -package kotlinx.kover.engines.commons - -import kotlinx.kover.api.* -import org.gradle.api.* -import org.gradle.api.file.* - -internal interface CoverageAgent { - val engine: CoverageEngine - val classpath: FileCollection - fun buildCommandLineArgs(task: Task, extension: KoverTaskExtension): MutableList -} diff --git a/src/main/kotlin/kotlinx/kover/engines/commons/EngineManager.kt b/src/main/kotlin/kotlinx/kover/engines/commons/EngineManager.kt new file mode 100644 index 00000000..bf70fd68 --- /dev/null +++ b/src/main/kotlin/kotlinx/kover/engines/commons/EngineManager.kt @@ -0,0 +1,82 @@ +/* + * 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 kotlinx.kover.api.* +import kotlinx.kover.engines.intellij.* +import kotlinx.kover.engines.jacoco.* +import kotlinx.kover.tasks.* +import org.gradle.api.* +import org.gradle.api.artifacts.Configuration +import org.gradle.api.file.* +import org.gradle.process.* +import java.io.* + + +@Suppress("UNUSED_PARAMETER") +internal object EngineManager { + fun buildAgentArgs( + details: EngineDetails, + task: Task, + reportFile: File, + classFilters: KoverClassFilters + ): MutableList { + return if (details.variant.vendor == CoverageEngineVendor.INTELLIJ) { + task.buildIntellijAgentJvmArgs(details.jarFile, reportFile, classFilters) + } else { + reportFile.parentFile.mkdirs() + task.buildJacocoAgentJvmArgs(details.jarFile, reportFile, classFilters) + } + } + + fun report( + details: EngineDetails, + task: Task, + exec: ExecOperations, + projectFiles: Map, + classFilters: KoverClassFilters, + xmlFile: File?, + htmlDir: File? + ) { + if (details.variant.vendor == CoverageEngineVendor.INTELLIJ) { + task.intellijReport(exec, projectFiles, classFilters, xmlFile, htmlDir, details.classpath) + } else { + task.jacocoReport(projectFiles, xmlFile, htmlDir, details.classpath) + } + } + + fun verify( + details: EngineDetails, + task: Task, + exec: ExecOperations, + projectFiles: Map, + classFilters: KoverClassFilters, + rules: List, + ): String? { + return if (details.variant.vendor == CoverageEngineVendor.INTELLIJ) { + task.intellijVerification(exec, projectFiles, classFilters, rules, details.classpath) + } else { + task.jacocoVerification(projectFiles, rules, details.classpath) + } + } + + + fun findJarFile(variant: CoverageEngineVariant, config: Configuration, archiveOperations: ArchiveOperations): File { + return if (variant.vendor == CoverageEngineVendor.INTELLIJ) { + config.fileCollection { it.name == "intellij-coverage-agent" }.singleFile + } else { + val fatJar = config.fileCollection { it.name == "org.jacoco.agent" }.singleFile + archiveOperations.zipTree(fatJar).filter { it.name == "jacocoagent.jar" }.singleFile + } + } + + fun dependencies(engineVariant: CoverageEngineVariant): List { + return if (engineVariant.vendor == CoverageEngineVendor.INTELLIJ) { + getIntellijDependencies(engineVariant.version) + } else { + getJacocoDependencies(engineVariant.version) + } + } +} diff --git a/src/main/kotlin/kotlinx/kover/engines/commons/Reports.kt b/src/main/kotlin/kotlinx/kover/engines/commons/Reports.kt index ee7d7749..d6566599 100644 --- a/src/main/kotlin/kotlinx/kover/engines/commons/Reports.kt +++ b/src/main/kotlin/kotlinx/kover/engines/commons/Reports.kt @@ -1,14 +1,28 @@ +/* + * 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 java.io.* +import kotlinx.kover.api.* +import java.math.BigDecimal + + +internal class ReportVerificationRule( + val id: Int, + val name: String?, + val target: VerificationTarget, + val filters: KoverClassFilters?, + val bounds: List +) -internal class Report( - val files: List, - val projects: List, - val includes: List = emptyList(), - val excludes: List = emptyList() +internal class ReportVerificationBound( + val id: Int, + val minValue: BigDecimal?, + val maxValue: BigDecimal?, + val counter: CounterType, + val valueType: VerificationValueType ) -internal class ProjectInfo(val sources: Iterable, val outputs: Iterable) private val regexMetacharactersSet = "<([{\\^-=$!|]})+.>".toSet() diff --git a/src/main/kotlin/kotlinx/kover/engines/intellij/IntellijAgent.kt b/src/main/kotlin/kotlinx/kover/engines/intellij/IntellijAgent.kt index 5ade3b98..9fdb6824 100644 --- a/src/main/kotlin/kotlinx/kover/engines/intellij/IntellijAgent.kt +++ b/src/main/kotlin/kotlinx/kover/engines/intellij/IntellijAgent.kt @@ -1,91 +1,52 @@ /* - * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ package kotlinx.kover.engines.intellij import kotlinx.kover.api.* import kotlinx.kover.engines.commons.* -import kotlinx.kover.engines.commons.CoverageAgent -import org.gradle.api.* -import org.gradle.api.artifacts.* -import org.gradle.api.file.* -import org.gradle.api.provider.Provider +import org.gradle.api.Task import java.io.* - -internal fun Project.createIntellijAgent(koverExtension: KoverExtension): CoverageAgent { - val intellijConfig = createIntellijConfig(koverExtension) - val jarProvider = provider { intellijConfig.fileCollection { it.name == "intellij-coverage-agent" }.singleFile } - return IntellijAgent(intellijConfig, jarProvider) +private const val trackingPerTest = false // a flag to enable tracking per test coverage +private const val calculateForUnloadedClasses = false // a flag to calculate coverage for unloaded classes +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, classFilters: KoverClassFilters): MutableList { + val argsFile = File(temporaryDir, "intellijagent.args") + argsFile.writeAgentArgs(reportFile, classFilters) + + return mutableListOf( + "-javaagent:${jarFile.canonicalPath}=${argsFile.canonicalPath}", + "-Didea.new.sampling.coverage=true", + "-Didea.new.tracing.coverage=true", + "-Didea.coverage.log.level=error", + "-Dcoverage.ignore.private.constructor.util.class=true" + ) } -private class IntellijAgent(override val classpath: FileCollection, private val jarProvider: Provider): CoverageAgent { - private val trackingPerTest = false // a flag to enable tracking per test coverage - private val calculateForUnloadedClasses = false // a flag to calculate coverage for unloaded classes - private val appendToDataFile = true // a flag to use data file as initial coverage - private val samplingMode = false //a flag to run coverage in sampling mode or in tracing mode otherwise - - override val engine: CoverageEngine = CoverageEngine.INTELLIJ - - override fun buildCommandLineArgs(task: Task, extension: KoverTaskExtension): MutableList { - val argsFile = File(task.temporaryDir, "intellijagent.args") - argsFile.writeArgsToFile(extension) - - return mutableListOf( - "-javaagent:${jarProvider.get().canonicalPath}=${argsFile.canonicalPath}", - "-Didea.new.sampling.coverage=true", - "-Didea.new.tracing.coverage=true", - "-Didea.coverage.log.level=error", - "-Dcoverage.ignore.private.constructor.util.class=true" - ) - } - - private fun File.writeArgsToFile(extension: KoverTaskExtension) { - val binary = extension.binaryReportFile.get() - binary.parentFile.mkdirs() - val binaryPath = binary.canonicalPath - - printWriter().use { pw -> - pw.appendLine(binaryPath) - pw.appendLine(trackingPerTest.toString()) - pw.appendLine(calculateForUnloadedClasses.toString()) - pw.appendLine(appendToDataFile.toString()) - pw.appendLine(samplingMode.toString()) - extension.includes.forEach { i -> - pw.appendLine(i.wildcardsToRegex()) - } - - if (extension.excludes.isNotEmpty()) { - pw.appendLine("-exclude") - } - - extension.excludes.forEach { e -> - pw.appendLine(e.wildcardsToRegex()) - } +private fun File.writeAgentArgs(reportFile: File, classFilters: KoverClassFilters) { + reportFile.parentFile.mkdirs() + val binaryPath = reportFile.canonicalPath + + printWriter().use { pw -> + pw.appendLine(binaryPath) + pw.appendLine(trackingPerTest.toString()) + pw.appendLine(calculateForUnloadedClasses.toString()) + pw.appendLine(appendToDataFile.toString()) + pw.appendLine(samplingMode.toString()) + classFilters.includes.forEach { i -> + pw.appendLine(i.wildcardsToRegex()) } - } -} -private fun Project.createIntellijConfig(koverExtension: KoverExtension): Configuration { - val config = project.configurations.create("IntellijKoverConfig") - config.isVisible = false - config.isTransitive = true - config.description = "Kotlin Kover Plugin configuration for IntelliJ agent and reporter" - - config.defaultDependencies { dependencies -> - val agentVersion = koverExtension.intellijEngineVersion.get() - IntellijEngineVersion.parseOrNull(agentVersion)?.let { - if (it < minimalIntellijVersion) throw GradleException("IntelliJ engine version $it is too low, minimal version is $minimalIntellijVersion") + if (classFilters.excludes.isNotEmpty()) { + pw.appendLine("-exclude") } - dependencies.add( - this.dependencies.create("org.jetbrains.intellij.deps:intellij-coverage-agent:$agentVersion") - ) - - dependencies.add( - this.dependencies.create("org.jetbrains.intellij.deps:intellij-coverage-reporter:$agentVersion") - ) + classFilters.excludes.forEach { e -> + pw.appendLine(e.wildcardsToRegex()) + } } - return config } diff --git a/src/main/kotlin/kotlinx/kover/engines/intellij/IntellijEngine.kt b/src/main/kotlin/kotlinx/kover/engines/intellij/IntellijEngine.kt new file mode 100644 index 00000000..e36ac608 --- /dev/null +++ b/src/main/kotlin/kotlinx/kover/engines/intellij/IntellijEngine.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ +package kotlinx.kover.engines.intellij + +import kotlinx.kover.api.KoverVersions.MINIMAL_INTELLIJ_VERSION +import org.gradle.api.* + +internal fun getIntellijDependencies(engineVersion: String): List { + if (engineVersion.isLowVersion(MINIMAL_INTELLIJ_VERSION)) { + throw GradleException("IntelliJ engine version $engineVersion is too low, minimal version is $MINIMAL_INTELLIJ_VERSION") + } + return listOf( + "org.jetbrains.intellij.deps:intellij-coverage-agent:$engineVersion", + "org.jetbrains.intellij.deps:intellij-coverage-reporter:$engineVersion" + ) +} + + +private fun String.isLowVersion(minimalVersion: String): Boolean { + val customParts = this.split(".") + if (customParts.size != 3) { + throw GradleException("Invalid custom IntelliJ Coverage Engine version '$this'. Expected 'x.x.xxx'") + } + + val minimalParts = minimalVersion.split(".") + if (minimalParts.size != 3) { + throw GradleException("Invalid minimal IntelliJ Coverage Engine version '$this'") + } + + for (i in 0..2) { + if (customParts[i] > minimalParts[i]) { + return false + } else if (customParts[i] < minimalParts[i]) { + return true + } + } + + return false +} diff --git a/src/main/kotlin/kotlinx/kover/engines/intellij/IntellijReports.kt b/src/main/kotlin/kotlinx/kover/engines/intellij/IntellijReports.kt index d5978aee..f064e639 100644 --- a/src/main/kotlin/kotlinx/kover/engines/intellij/IntellijReports.kt +++ b/src/main/kotlin/kotlinx/kover/engines/intellij/IntellijReports.kt @@ -4,9 +4,10 @@ package kotlinx.kover.engines.intellij +import kotlinx.kover.api.* import kotlinx.kover.engines.commons.* -import kotlinx.kover.engines.commons.Report import kotlinx.kover.json.* +import kotlinx.kover.tasks.* import org.gradle.api.* import org.gradle.api.file.* import org.gradle.process.ExecOperations @@ -14,7 +15,8 @@ import java.io.* internal fun Task.intellijReport( exec: ExecOperations, - report: Report, + projectFiles: Map, + filters: KoverClassFilters, xmlFile: File?, htmlDir: File?, classpath: FileCollection @@ -28,7 +30,7 @@ internal fun Task.intellijReport( } val argsFile = File(temporaryDir, "intellijreport.json") - argsFile.writeReportsJson(report, xmlFile, htmlDir) + argsFile.writeReportsJson(projectFiles, filters, xmlFile, htmlDir) exec.javaexec { e -> e.mainClass.set("com.intellij.rt.coverage.report.Main") @@ -77,19 +79,20 @@ JSON example: ``` */ private fun File.writeReportsJson( - report: Report, + projectFiles: Map, + classFilters: KoverClassFilters, xmlFile: File?, htmlDir: File? ) { writeJsonObject(mutableMapOf( - "reports" to report.files.map { mapOf("ic" to it) }, - "modules" to report.projects.map { mapOf("sources" to it.sources, "output" to it.outputs) }, + "reports" to projectFiles.flatMap { it.value.binaryReportFiles }.map { mapOf("ic" to it) }, + "modules" to projectFiles.map { mapOf("sources" to it.value.sources, "output" to it.value.outputs) }, ).also { - if (report.includes.isNotEmpty()) { - it["include"] = mapOf("classes" to report.includes.map { c -> c.wildcardsToRegex() }) + if (classFilters.includes.isNotEmpty()) { + it["include"] = mapOf("classes" to classFilters.includes.map { c -> c.wildcardsToRegex() }) } - if (report.excludes.isNotEmpty()) { - it["exclude"] = mapOf("classes" to report.excludes.map { c -> c.wildcardsToRegex() }) + if (classFilters.excludes.isNotEmpty()) { + it["exclude"] = mapOf("classes" to classFilters.excludes.map { c -> c.wildcardsToRegex() }) } xmlFile?.also { f -> it["xml"] = f diff --git a/src/main/kotlin/kotlinx/kover/engines/intellij/IntellijVerification.kt b/src/main/kotlin/kotlinx/kover/engines/intellij/IntellijVerification.kt index 5b90ddc6..38f6b1fe 100644 --- a/src/main/kotlin/kotlinx/kover/engines/intellij/IntellijVerification.kt +++ b/src/main/kotlin/kotlinx/kover/engines/intellij/IntellijVerification.kt @@ -5,10 +5,10 @@ package kotlinx.kover.engines.intellij import kotlinx.kover.api.* -import kotlinx.kover.api.VerificationValueType.COVERED_LINES_PERCENTAGE +import kotlinx.kover.api.VerificationValueType.* import kotlinx.kover.engines.commons.* -import kotlinx.kover.engines.commons.Report import kotlinx.kover.json.* +import kotlinx.kover.tasks.* import org.gradle.api.* import org.gradle.api.file.* import org.gradle.process.ExecOperations @@ -19,13 +19,14 @@ import java.math.RoundingMode @Suppress("UNUSED_PARAMETER") internal fun Task.intellijVerification( exec: ExecOperations, - report: Report, - rules: Iterable, + projectFiles: Map, + classFilters: KoverClassFilters, + rules: List, classpath: FileCollection -) { +): String? { val aggRequest = File(temporaryDir, "agg-request.json") - val aggFile = File(temporaryDir, "aggregated.ic") - aggRequest.writeAggVerifyJson(report, aggFile) + val groupedRules = groupRules(classFilters, rules) + aggRequest.writeAggJson(projectFiles, groupedRules) exec.javaexec { e -> e.mainClass.set("com.intellij.rt.coverage.aggregate.Main") e.classpath = classpath @@ -33,19 +34,46 @@ internal fun Task.intellijVerification( } val verifyRequest = File(temporaryDir, "verify-request.json") - val resultFile = File(temporaryDir, "verify-result.json") - verifyRequest.writeVerifyJson(aggFile, resultFile, rules) + val verifyResponseFile = File(temporaryDir, "verify-result.json") + verifyRequest.writeVerifyJson(groupedRules, verifyResponseFile) exec.javaexec { e -> e.mainClass.set("com.intellij.rt.coverage.verify.Main") e.classpath = classpath e.args = mutableListOf(verifyRequest.canonicalPath) } - val violations = resultFile.readJsonObject() - if (violations.isNotEmpty()) { + val violations = verifyResponseFile.readJsonObject() + return if (violations.isNotEmpty()) { val result = processViolationsModel(violations) raiseViolations(result, rules) + } else { + null + } +} + +private data class RulesGroup( + val aggFile: File, + val filters: KoverClassFilters, + val rules: List +) + +private fun Task.groupRules( + commonClassFilters: KoverClassFilters, + allRules: List +): List { + val result = mutableListOf() + val commonAggFile = File(temporaryDir, "aggregated-common.ic") + val commonRules = mutableListOf() + result += RulesGroup(commonAggFile, commonClassFilters, commonRules) + + allRules.forEach { + if (it.filters == null) { + commonRules += it + } else { + result += RulesGroup(File(temporaryDir, "aggregated-${result.size}.ic"), it.filters, listOf(it)) + } } + return result } /* @@ -68,24 +96,28 @@ internal fun Task.intellijVerification( } } */ -private fun File.writeAggVerifyJson( - report: Report, - aggReport: File +private fun File.writeAggJson( + projectFiles: Map, + groups: List ) { writeJsonObject(mapOf( - "reports" to report.files.map { mapOf("ic" to it) }, - "modules" to report.projects.map { mapOf("sources" to it.sources, "output" to it.outputs) }, - "result" to listOf(mapOf( - "aggregatedReportFile" to aggReport, - "filters" to mutableMapOf().also { - if (report.includes.isNotEmpty()) { - it["include"] = mapOf("classes" to report.includes.map { c -> c.wildcardsToRegex() }) - } - if (report.excludes.isNotEmpty()) { - it["exclude"] = mapOf("classes" to report.excludes.map { c -> c.wildcardsToRegex() }) + "reports" to projectFiles.flatMap { it.value.binaryReportFiles }.map { mapOf("ic" to it) }, + "modules" to projectFiles.map { mapOf("sources" to it.value.sources, "output" to it.value.outputs) }, + "result" to groups.map { group -> + mapOf( + "aggregatedReportFile" to group.aggFile, + "filters" to mutableMapOf().also { + if (group.filters.includes.isNotEmpty()) { + it["include"] = + mapOf("classes" to group.filters.includes.map { c -> c.wildcardsToRegex() }) + } + if (group.filters.excludes.isNotEmpty()) { + it["exclude"] = + mapOf("classes" to group.filters.excludes.map { c -> c.wildcardsToRegex() }) + } } - } - )) + ) + } )) } @@ -110,51 +142,70 @@ private fun File.writeAggVerifyJson( } */ private fun File.writeVerifyJson( - aggReport: File, + groups: List, result: File, - rules: Iterable ) { - writeJsonObject(mapOf( - "resultFile" to result, - "rules" to rules.map { rule -> - mapOf( + val rulesArray = mutableListOf>() + + groups.forEach { group -> + group.rules.forEach { rule -> + rulesArray += mapOf( "id" to rule.id, - "aggregatedReportFile" to aggReport, - "targetType" to "ALL", + "aggregatedReportFile" to group.aggFile, + "targetType" to rule.targetToReporter(), "bounds" to rule.bounds.map { b -> mutableMapOf( "id" to b.id, - "counter" to "LINE", - "valueType" to b.valueTypeConverted(), + "counter" to b.counterToReporter(), + "valueType" to b.valueTypeToReporter(), ).also { val minValue = b.minValue val maxValue = b.maxValue if (minValue != null) { - it["min"] = b.valueAligned(minValue) + it["min"] = b.valueToReporter(minValue) } if (maxValue != null) { - it["max"] = b.valueAligned(maxValue) + it["max"] = b.valueToReporter(maxValue) } } } ) } - )) + } + + writeJsonObject(mapOf("resultFile" to result, "rules" to rulesArray)) +} + +private fun ReportVerificationRule.targetToReporter(): String { + return when (target) { + VerificationTarget.ALL -> "ALL" + VerificationTarget.CLASS -> "CLASS" + VerificationTarget.PACKAGE -> "PACKAGE" + } +} + +private fun ReportVerificationBound.counterToReporter(): String { + return when (counter) { + CounterType.LINE -> "LINE" + CounterType.INSTRUCTION -> "INSTRUCTION" + CounterType.BRANCH -> "BRANCH" + } } -private fun VerificationBound.valueTypeConverted(): String { +private fun ReportVerificationBound.valueTypeToReporter(): String { return when (valueType) { - VerificationValueType.COVERED_LINES_COUNT -> "COVERED" - VerificationValueType.MISSED_LINES_COUNT -> "MISSED" - COVERED_LINES_PERCENTAGE -> "COVERED_RATE" + COVERED_COUNT -> "COVERED" + MISSED_COUNT -> "MISSED" + COVERED_PERCENTAGE -> "COVERED_RATE" + MISSED_PERCENTAGE -> "MISSED_RATE" } } -private fun VerificationBound.valueAligned(value: Int): BigDecimal { - return if (valueType == COVERED_LINES_PERCENTAGE) { - value.toBigDecimal().divide(ONE_HUNDRED, 6, RoundingMode.HALF_UP) +private fun ReportVerificationBound.valueToReporter(value: BigDecimal): BigDecimal { + return if (valueType == COVERED_PERCENTAGE || valueType == MISSED_PERCENTAGE) { + value.divide(ONE_HUNDRED, 6, RoundingMode.HALF_UP) } else { - value.toBigDecimal() + value } } @@ -180,31 +231,33 @@ private fun processViolationsModel(violations: Map): ViolationsResu rules[ruleId.toInt()] = RuleViolation(bounds) } } catch (e: Throwable) { - throw GradleException("Error occurred while parsing verifier result", e) + throw GradleException("Error occurred while parsing verifier result", e) } return ViolationsResult(rules) } -private fun raiseViolations(result: ViolationsResult, rules: Iterable) { +private fun raiseViolations(result: ViolationsResult, rules: Iterable): String { val messageBuilder = StringBuilder() val rulesMap = rules.associateBy { r -> r.id } result.ruleViolations.forEach { (ruleId, rv) -> - val rule = rulesMap[ruleId] ?: throw Exception("") + val rule = rulesMap[ruleId] + ?: throw Exception("Error occurred while parsing verification error: unmapped rule with ID $ruleId") val ruleName = if (rule.name != null) " '${rule.name}' " else " " val boundsMap = rule.bounds.associateBy { b -> b.id } val boundMessages = rv.boundViolations.mapNotNull { (boundId, v) -> - val bound = boundsMap[boundId] ?: throw Exception("") + val bound = boundsMap[boundId] + ?: throw Exception("Error occurred while parsing verification error: unmapped bound with ID $boundId") val minViolation = v.min["all"] val maxViolation = v.max["all"] if (minViolation != null) { - "${bound.readableValueType} is ${minViolation.toRateIfNeeded(bound)}, but expected minimum is ${bound.minValue}" + "${bound.readableValueType} is ${minViolation.fromRateIfNeeded(bound)}, but expected minimum is ${bound.minValue}" } else if (maxViolation != null) { - "${bound.readableValueType} is ${maxViolation.toRateIfNeeded(bound)}, but expected maximum is ${bound.maxValue}" + "${bound.readableValueType} is ${maxViolation.fromRateIfNeeded(bound)}, but expected maximum is ${bound.maxValue}" } else { null } @@ -219,22 +272,23 @@ private fun raiseViolations(result: ViolationsResult, rules: Iterable "covered lines count" - VerificationValueType.MISSED_LINES_COUNT -> "missed lines count" - COVERED_LINES_PERCENTAGE -> "covered lines percentage" + COVERED_COUNT -> "covered lines count" + MISSED_COUNT -> "missed lines count" + COVERED_PERCENTAGE -> "covered lines percentage" + MISSED_PERCENTAGE -> "missed lines percentage" } diff --git a/src/main/kotlin/kotlinx/kover/engines/intellij/Versions.kt b/src/main/kotlin/kotlinx/kover/engines/intellij/Versions.kt deleted file mode 100644 index 320ee50d..00000000 --- a/src/main/kotlin/kotlinx/kover/engines/intellij/Versions.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.kover.engines.intellij - -val minimalIntellijVersion = IntellijEngineVersion(1, 0, 668) -val defaultIntellijVersion = minimalIntellijVersion - -data class IntellijEngineVersion(val major: Int, val minor: Int, val build: Int): Comparable { - override fun compareTo(other: IntellijEngineVersion): Int { - var compared = major.compareTo(other.major) - if (compared != 0) { - return compared - } - compared = minor.compareTo(other.minor) - if (compared != 0) { - return compared - } - return build.compareTo(other.build) - } - - override fun toString(): String { - return "$major.$minor.$build" - } - - companion object { - fun parseOrNull(string: String): IntellijEngineVersion? { - val parts = string.split('.') - if (parts.size != 3) return null - val major = parts[0].toIntOrNull() ?: return null - val minor = parts[1].toIntOrNull() ?: return null - val build = parts[2].toIntOrNull() ?: return null - - return IntellijEngineVersion(major, minor, build) - } - } -} - - diff --git a/src/main/kotlin/kotlinx/kover/engines/jacoco/JacocoAgent.kt b/src/main/kotlin/kotlinx/kover/engines/jacoco/JacocoAgent.kt index c6d18b74..c2d40a3d 100644 --- a/src/main/kotlin/kotlinx/kover/engines/jacoco/JacocoAgent.kt +++ b/src/main/kotlin/kotlinx/kover/engines/jacoco/JacocoAgent.kt @@ -1,73 +1,24 @@ /* - * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ package kotlinx.kover.engines.jacoco import kotlinx.kover.api.* -import kotlinx.kover.engines.commons.CoverageAgent -import org.gradle.api.* -import org.gradle.api.artifacts.* -import org.gradle.api.file.* -import org.gradle.api.provider.* -import org.gradle.configurationcache.extensions.* +import org.gradle.api.Task import java.io.* -internal fun Project.createJacocoAgent(koverExtension: KoverExtension): CoverageAgent { - val jacocoConfig = createJacocoConfig(koverExtension) - val archiveOperations: ArchiveOperations = serviceOf() - val jarProvider = provider { - val fatJar = jacocoConfig.fileCollection { it.name == "org.jacoco.agent" }.singleFile - archiveOperations.zipTree(fatJar).filter { it.name == "jacocoagent.jar" }.singleFile - } - return JacocoAgent(jacocoConfig, jarProvider) -} - -private class JacocoAgent( - override val classpath: FileCollection, - private val jarProvider: Provider -) : CoverageAgent { - override val engine: CoverageEngine = CoverageEngine.JACOCO - - override fun buildCommandLineArgs(task: Task, extension: KoverTaskExtension): MutableList { - return mutableListOf("-javaagent:${jarProvider.get().canonicalPath}=${agentArgs(extension)}") - } - - private fun agentArgs(extension: KoverTaskExtension): String { - val binary = extension.binaryReportFile.get() - binary.parentFile.mkdirs() +internal fun Task.buildJacocoAgentJvmArgs(jarFile: File, reportFile: File, classFilters: KoverClassFilters): MutableList { + val agentArgs = listOfNotNull( + "destfile=${reportFile.canonicalPath},append=true,inclnolocationclasses=false,dumponexit=true,output=file,jmx=false", + classFilters.includes.joinToFilterString("includes"), + classFilters.excludes.joinToFilterString("excludes") + ).joinToString(",") - return listOfNotNull( - "destfile=${binary.canonicalPath}", - "append=true", - "inclnolocationclasses=false", - "dumponexit=true", - "output=file", - "jmx=false", - extension.includes.joinToFilterString("includes"), - extension.excludes.joinToFilterString("excludes") - ).joinToString(",") - } + return mutableListOf("-javaagent:${jarFile.canonicalPath}=$agentArgs") } private fun List.joinToFilterString(name: String): String? { if (isEmpty()) return null return name + "=" + joinToString(":") } - -private fun Project.createJacocoConfig(koverExtension: KoverExtension): Configuration { - val config = project.configurations.create("JacocoKoverConfig") - config.isVisible = false - config.isTransitive = true - config.description = "Kotlin Kover Plugin configuration for JaCoCo agent and reporter" - - config.defaultDependencies { dependencies -> - dependencies.add( - this.dependencies.create("org.jacoco:org.jacoco.agent:${koverExtension.jacocoEngineVersion.get()}") - ) - dependencies.add( - this.dependencies.create("org.jacoco:org.jacoco.ant:${koverExtension.jacocoEngineVersion.get()}") - ) - } - return config -} diff --git a/src/main/kotlin/kotlinx/kover/engines/jacoco/JacocoEngine.kt b/src/main/kotlin/kotlinx/kover/engines/jacoco/JacocoEngine.kt new file mode 100644 index 00000000..0942c846 --- /dev/null +++ b/src/main/kotlin/kotlinx/kover/engines/jacoco/JacocoEngine.kt @@ -0,0 +1,10 @@ +/* + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ +package kotlinx.kover.engines.jacoco + +internal fun getJacocoDependencies(engineVersion: String): List { + return listOf("org.jacoco:org.jacoco.agent:$engineVersion", "org.jacoco:org.jacoco.ant:$engineVersion") +} + + diff --git a/src/main/kotlin/kotlinx/kover/engines/jacoco/JacocoReports.kt b/src/main/kotlin/kotlinx/kover/engines/jacoco/JacocoReports.kt index 9cdb07ee..655f339a 100644 --- a/src/main/kotlin/kotlinx/kover/engines/jacoco/JacocoReports.kt +++ b/src/main/kotlin/kotlinx/kover/engines/jacoco/JacocoReports.kt @@ -1,14 +1,14 @@ /* - * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ package kotlinx.kover.engines.jacoco import groovy.lang.* import kotlinx.kover.api.* -import kotlinx.kover.engines.commons.* +import kotlinx.kover.engines.commons.ReportVerificationRule import kotlinx.kover.engines.commons.ONE_HUNDRED -import kotlinx.kover.engines.commons.Report +import kotlinx.kover.tasks.* import org.gradle.api.* import org.gradle.api.file.* import org.gradle.internal.reflect.* @@ -16,51 +16,13 @@ import java.io.* import java.math.* import java.util.* -private fun Task.callJacocoAntReportTask( - report: Report, - classpath: FileCollection, - block: GroovyObject.() -> Unit -) { - val builder = ant - builder.invokeMethod( - "taskdef", - mapOf( - "name" to "jacocoReport", - "classname" to "org.jacoco.ant.ReportTask", - "classpath" to classpath.asPath - ) - ) - - val sources: MutableList = mutableListOf() - val outputs: MutableList = mutableListOf() - report.projects.forEach { projectInfo -> - sources.addAll(projectInfo.sources) - outputs.addAll(projectInfo.outputs) - } - - builder.invokeWithBody("jacocoReport") { - invokeWithBody("executiondata") { - project.files(report.files).addToAntBuilder(this, "resources") - } - invokeWithBody("structure", mapOf("name" to project.path)) { - invokeWithBody("sourcefiles") { - project.files(sources).addToAntBuilder(this, "resources") - } - invokeWithBody("classfiles") { - project.files(outputs).addToAntBuilder(this, "resources") - } - } - block() - } -} - internal fun Task.jacocoReport( - report: Report, + projectFiles: Map, xmlFile: File?, htmlDir: File?, classpath: FileCollection ) { - callJacocoAntReportTask(report, classpath) { + callJacocoAntReportTask(projectFiles, classpath) { if (xmlFile != null) { xmlFile.parentFile.mkdirs() invokeMethod("xml", mapOf("destfile" to xmlFile)) @@ -74,30 +36,35 @@ internal fun Task.jacocoReport( internal fun Task.jacocoVerification( - report: Report, - rules: Iterable, + projectFiles: Map, + rules: List, classpath: FileCollection -) { - callJacocoAntReportTask(report, classpath) { +): String? { + callJacocoAntReportTask(projectFiles, classpath) { invokeWithBody("check", mapOf("failonviolation" to "false", "violationsproperty" to "jacocoErrors")) { rules.forEach { invokeWithBody("rule", mapOf("element" to "BUNDLE")) { it.bounds.forEach { b -> val limitArgs = mutableMapOf("counter" to "LINE") - var min: BigDecimal? = b.minValue?.toBigDecimal() - var max: BigDecimal? = b.maxValue?.toBigDecimal() + var min: BigDecimal? = b.minValue + var max: BigDecimal? = b.maxValue when (b.valueType) { - VerificationValueType.COVERED_LINES_COUNT -> { + VerificationValueType.COVERED_COUNT -> { limitArgs["value"] = "COVEREDCOUNT" } - VerificationValueType.MISSED_LINES_COUNT -> { + VerificationValueType.MISSED_COUNT -> { limitArgs["value"] = "MISSEDCOUNT" } - VerificationValueType.COVERED_LINES_PERCENTAGE -> { + VerificationValueType.COVERED_PERCENTAGE -> { limitArgs["value"] = "COVEREDRATIO" min = min?.divide(ONE_HUNDRED) max = max?.divide(ONE_HUNDRED) } + VerificationValueType.MISSED_PERCENTAGE -> { + limitArgs["value"] = "MISSEDRATIO" + min = min?.divide(ONE_HUNDRED) + max = max?.divide(ONE_HUNDRED) + } } if (min != null) { @@ -114,7 +81,47 @@ internal fun Task.jacocoVerification( } } - ant.violations?.let { throw GradleException(it) } + return ant.violations +} + +private fun Task.callJacocoAntReportTask( + projectFiles: Map, + classpath: FileCollection, + block: GroovyObject.() -> Unit +) { + val builder = ant + builder.invokeMethod( + "taskdef", + mapOf( + "name" to "jacocoReport", + "classname" to "org.jacoco.ant.ReportTask", + "classpath" to classpath.asPath + ) + ) + + val sources: MutableList = mutableListOf() + val outputs: MutableList = mutableListOf() + val binaries: MutableList = mutableListOf() + projectFiles.forEach { pf -> + binaries += pf.value.binaryReportFiles + sources += pf.value.sources + outputs += pf.value.outputs + } + + builder.invokeWithBody("jacocoReport") { + invokeWithBody("executiondata") { + project.files(binaries).addToAntBuilder(this, "resources") + } + invokeWithBody("structure", mapOf("name" to project.path)) { + invokeWithBody("sourcefiles") { + project.files(sources).addToAntBuilder(this, "resources") + } + invokeWithBody("classfiles") { + project.files(outputs).addToAntBuilder(this, "resources") + } + } + block() + } } private val GroovyObject.violations: String? diff --git a/src/main/kotlin/kotlinx/kover/lookup/DirsLookup.kt b/src/main/kotlin/kotlinx/kover/lookup/DirsLookup.kt new file mode 100644 index 00000000..16481d2b --- /dev/null +++ b/src/main/kotlin/kotlinx/kover/lookup/DirsLookup.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ +package kotlinx.kover.lookup + +import kotlinx.kover.api.* +import kotlinx.kover.lookup.adapters.* +import org.gradle.api.Project +import org.gradle.api.file.* +import java.io.File + + +open class ProjectDirectories(val sources: FileCollection, val outputs: FileCollection) + +internal object DirsLookup { + private val adapters: List = listOf( + OldJavaPluginAdapter(), + AndroidPluginAdapter(), + KotlinMultiplatformPluginAdapter(), + KotlinAndroidPluginAdapter() + ) + + @Suppress("UNUSED_PARAMETER") + fun lookup(project: Project, sourceSetFilters: KoverSourceSetFilters): ProjectDirectories { + val srcDirs = HashMap() + val outDirs = HashMap() + + adapters.forEach { + val (src, out) = it.lookupSafe(project, sourceSetFilters) + srcDirs += src.map { file -> file.canonicalPath to file } + outDirs += out.map { file -> file.canonicalPath to file } + } + + val src = srcDirs.values.filter { it.exists() && it.isDirectory } + val out = outDirs.values.filter { it.exists() && it.isDirectory } + + return ProjectDirectories(project.files(src), project.files(out)) + } + + +} + + +internal abstract class LookupAdapter { + protected abstract fun lookup(project: Project, sourceSetFilters: KoverSourceSetFilters): Dirs + + fun lookupSafe(project: Project, sourceSetFilters: KoverSourceSetFilters): Dirs { + return try { + lookup(project, sourceSetFilters) + } catch (e: Throwable) { + when (e) { + is NoSuchMethodError, is NoSuchFieldError, is ClassNotFoundException, is NoClassDefFoundError -> { + project.logger.info("Problem occurred in Kover source set adapter", e) + Dirs() + } + else -> throw e + } + } + } + + protected fun filterSourceSet(name: String, sourceSetFilters: KoverSourceSetFilters): Boolean { + if (sourceSetFilters.excludes.contains(name)) { + return false + } + if (sourceSetFilters.excludeTests && (name == "test" || name.endsWith("Test") || name.startsWith("test") || name.startsWith("androidTest"))) { + return false + } + return true + } + + data class Dirs(val sources: List = emptyList(), val outputs: List = emptyList()) +} diff --git a/src/main/kotlin/kotlinx/kover/lookup/adapters/AndroidPluginAdapter.kt b/src/main/kotlin/kotlinx/kover/lookup/adapters/AndroidPluginAdapter.kt new file mode 100644 index 00000000..01ef4d86 --- /dev/null +++ b/src/main/kotlin/kotlinx/kover/lookup/adapters/AndroidPluginAdapter.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.kover.lookup.adapters + +import com.android.build.gradle.* +import kotlinx.kover.api.* +import kotlinx.kover.lookup.* +import org.gradle.api.* + +internal class AndroidPluginAdapter : LookupAdapter() { + override fun lookup(project: Project, sourceSetFilters: KoverSourceSetFilters): Dirs { + project.plugins.findPlugin("android") ?: return Dirs() + + val extension = project.extensions.findByType(BaseExtension::class.java) ?: return Dirs() + + val sourceDirs = extension.sourceSets.asSequence() + .filter { filterSourceSet(it.name, sourceSetFilters) } + .map { it.java }.toList() + .flatMap { it.srcDirs } + + return Dirs(sourceDirs, emptyList()) + } +} diff --git a/src/main/kotlin/kotlinx/kover/lookup/adapters/KotlinAndroidPluginAdapter.kt b/src/main/kotlin/kotlinx/kover/lookup/adapters/KotlinAndroidPluginAdapter.kt new file mode 100644 index 00000000..c8302fff --- /dev/null +++ b/src/main/kotlin/kotlinx/kover/lookup/adapters/KotlinAndroidPluginAdapter.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.kover.lookup.adapters + +import kotlinx.kover.api.* +import kotlinx.kover.lookup.LookupAdapter +import org.gradle.api.* +import org.jetbrains.kotlin.gradle.dsl.* + +internal class KotlinAndroidPluginAdapter : LookupAdapter() { + + override fun lookup(project: Project, sourceSetFilters: KoverSourceSetFilters): Dirs { + project.plugins.findPlugin("kotlin-android") ?: return Dirs() + + val extension = project.extensions.findByType(KotlinAndroidProjectExtension::class.java) ?: return Dirs() + + val sourceDirs = extension.target.compilations + .filter { !it.name.endsWith("Test") } + .flatMap { it.allKotlinSourceSets } + .map { it.kotlin } + .flatMap { it.srcDirs } + + val outputDirs = extension.target.compilations + .filter { !it.name.endsWith("Test") } + .flatMap { it.output.classesDirs } + + return Dirs(sourceDirs, outputDirs) + + } + +} diff --git a/src/main/kotlin/kotlinx/kover/adapters/KotlinMultiplatformPluginAdapter.kt b/src/main/kotlin/kotlinx/kover/lookup/adapters/KotlinMultiplatformPluginAdapter.kt similarity index 50% rename from src/main/kotlin/kotlinx/kover/adapters/KotlinMultiplatformPluginAdapter.kt rename to src/main/kotlin/kotlinx/kover/lookup/adapters/KotlinMultiplatformPluginAdapter.kt index 4830b8b2..3533c28c 100644 --- a/src/main/kotlin/kotlinx/kover/adapters/KotlinMultiplatformPluginAdapter.kt +++ b/src/main/kotlin/kotlinx/kover/lookup/adapters/KotlinMultiplatformPluginAdapter.kt @@ -1,46 +1,43 @@ /* - * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -package kotlinx.kover.adapters +package kotlinx.kover.lookup.adapters import groovy.lang.* -import kotlinx.kover.adapters.api.* +import kotlinx.kover.api.* +import kotlinx.kover.lookup.LookupAdapter import org.gradle.api.* import org.gradle.api.file.* import org.gradle.internal.metaobject.* import org.jetbrains.kotlin.gradle.dsl.* import org.jetbrains.kotlin.gradle.plugin.* -class KotlinMultiplatformPluginAdapter : CompilationPluginAdapter { +internal class KotlinMultiplatformPluginAdapter : LookupAdapter() { - override fun findDirs(project: Project): PluginDirs { - return safe(project) { - this.plugins.findPlugin("kotlin-multiplatform") ?: return@safe PluginDirs(emptyList(), emptyList()) + override fun lookup(project: Project, sourceSetFilters: KoverSourceSetFilters): Dirs { + project.plugins.findPlugin("kotlin-multiplatform") ?: return Dirs() - val extension = try { - project.extensions.findByType( - KotlinMultiplatformExtension::class.java - ) ?: return@safe PluginDirs(emptyList(), emptyList()) - } catch (e: ClassNotFoundException) { - return findByReflection(project) - } catch (e: NoClassDefFoundError) { - return findByReflection(project) - } + val extension = try { + project.extensions.findByType(KotlinMultiplatformExtension::class.java) ?: return Dirs() + } catch (e: ClassNotFoundException) { + return findByReflection(project) + } catch (e: NoClassDefFoundError) { + return findByReflection(project) + } - val targets = - extension.targets.filter { it.platformType == KotlinPlatformType.jvm || it.platformType == KotlinPlatformType.androidJvm } + val targets = + extension.targets.filter { it.platformType == KotlinPlatformType.jvm || it.platformType == KotlinPlatformType.androidJvm } - val compilations = targets.flatMap { it.compilations.filter { c -> c.name != "test" } } - val sourceDirs = - compilations.asSequence().flatMap { it.allKotlinSourceSets }.map { it.kotlin }.flatMap { it.srcDirs } - .toList() + val compilations = targets.flatMap { it.compilations.filter { c -> c.name != "test" } } + val sourceDirs = + compilations.asSequence().flatMap { it.allKotlinSourceSets }.map { it.kotlin }.flatMap { it.srcDirs } + .toList() - val outputDirs = - compilations.asSequence().flatMap { it.output.classesDirs }.toList() + val outputDirs = + compilations.asSequence().flatMap { it.output.classesDirs }.toList() - return@safe PluginDirs(sourceDirs, outputDirs) - } + return Dirs(sourceDirs, outputDirs) } /* @@ -49,12 +46,9 @@ class KotlinMultiplatformPluginAdapter : CompilationPluginAdapter { * Therefore, the only way to work with such an object is to use reflection. */ @Suppress("UNCHECKED_CAST") - fun findByReflection(project: Project): PluginDirs { + fun findByReflection(project: Project): Dirs { val extension = - project.extensions.findByName("kotlin")?.let { BeanDynamicObject(it) } ?: return PluginDirs( - emptyList(), - emptyList() - ) + project.extensions.findByName("kotlin")?.let { BeanDynamicObject(it) } ?: return Dirs() val targets = (extension.getProperty("targets") as NamedDomainObjectCollection).filter { val platformTypeName = (it.getProperty("platformType") as Named).name @@ -75,7 +69,7 @@ class KotlinMultiplatformPluginAdapter : CompilationPluginAdapter { compilations.asSequence().map { BeanDynamicObject(it.getProperty("output")) } .flatMap { it.getProperty("classesDirs") as FileCollection }.toList() - return PluginDirs(sourceDirs, outputDirs) + return Dirs(sourceDirs, outputDirs) } } diff --git a/src/main/kotlin/kotlinx/kover/lookup/adapters/OldJavaPluginAdapter.kt b/src/main/kotlin/kotlinx/kover/lookup/adapters/OldJavaPluginAdapter.kt new file mode 100644 index 00000000..c9f44d1f --- /dev/null +++ b/src/main/kotlin/kotlinx/kover/lookup/adapters/OldJavaPluginAdapter.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.kover.lookup.adapters + +import kotlinx.kover.api.* +import kotlinx.kover.lookup.LookupAdapter +import org.gradle.api.* +import org.gradle.api.tasks.* + +internal class OldJavaPluginAdapter : LookupAdapter() { + + override fun lookup(project: Project, sourceSetFilters: KoverSourceSetFilters): Dirs { + project.plugins.findPlugin("java") ?: return Dirs() + + val sourceSetContainer = project.extensions.findByType( + SourceSetContainer::class.java + ) ?: return Dirs() + + val sourceSets = sourceSetContainer.filter { filterSourceSet(it.name, sourceSetFilters) } + + val sourceDirs = sourceSets.flatMap { it.allSource.srcDirs } + val outputDirs = sourceSets.flatMap { it.output.classesDirs } + + return Dirs(sourceDirs, outputDirs) + } + +} diff --git a/src/main/kotlin/kotlinx/kover/tasks/KoverCollectingTask.kt b/src/main/kotlin/kotlinx/kover/tasks/KoverCollectingTask.kt deleted file mode 100644 index b8cd2fab..00000000 --- a/src/main/kotlin/kotlinx/kover/tasks/KoverCollectingTask.kt +++ /dev/null @@ -1,49 +0,0 @@ -package kotlinx.kover.tasks - -import org.gradle.api.* -import org.gradle.api.file.* -import org.gradle.api.tasks.* - -open class KoverCollectingTask : DefaultTask() { - /** - * Specifies directory path for collecting of all XML and HTML reports from all projects. - */ - @get:OutputDirectory - val outputDir: DirectoryProperty = project.objects.directoryProperty() - - @get:Internal - internal var xmlFiles: MutableMap = mutableMapOf() - - @get:Internal - internal var htmlDirs: MutableMap = mutableMapOf() - - - @TaskAction - fun collect() { - project.copy { - it.into(outputDir) - xmlFiles.forEach { (p, f) -> - it.from(f) { c -> - c.rename { "${p.pathAsFilename()}.xml" } - } - } - } - - - htmlDirs.forEach { (p, d) -> - val name = p.pathAsFilename() - - // delete directory for HTML reports so that the old reports do not overlap with the new ones - project.delete(outputDir.dir("html/$name")) - - project.copy { - it.from(d) - it.into(outputDir.dir("html/$name")) - } - } - } - - private fun String.pathAsFilename(): String { - return if (this == ":") "_root_" else replace(':', '_') - } -} diff --git a/src/main/kotlin/kotlinx/kover/tasks/KoverHtmlReport.kt b/src/main/kotlin/kotlinx/kover/tasks/KoverHtmlReport.kt deleted file mode 100644 index f1055831..00000000 --- a/src/main/kotlin/kotlinx/kover/tasks/KoverHtmlReport.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.kover.tasks - -import kotlinx.kover.api.* -import kotlinx.kover.engines.intellij.* -import kotlinx.kover.engines.jacoco.* -import org.gradle.api.file.* -import org.gradle.api.tasks.* - -@CacheableTask -open class KoverMergedHtmlReportTask : KoverMergedTask() { - /** - * Specifies directory path of generated HTML report. - */ - @get:OutputDirectory - val htmlReportDir: DirectoryProperty = project.objects.directoryProperty() - - @TaskAction - fun generate() { - val htmlDirFile = htmlReportDir.get().asFile - - if (coverageEngine.get() == CoverageEngine.INTELLIJ) { - intellijReport( - exec, - report(), - null, - htmlDirFile, - classpath.get() - ) - } else { - jacocoReport( - report(), - null, - htmlDirFile, - classpath.get(), - ) - } - logger.lifecycle("Kover: merged HTML report file://${htmlDirFile.canonicalPath}/index.html") - } -} - -@CacheableTask -open class KoverHtmlReportTask : KoverProjectTask() { - private val projectPath = project.path - - /** - * Specifies directory path of generated HTML report. - */ - @get:OutputDirectory - val htmlReportDir: DirectoryProperty = project.objects.directoryProperty() - - @TaskAction - fun generate() { - val htmlDirFile = htmlReportDir.get().asFile - - if (coverageEngine.get() == CoverageEngine.INTELLIJ) { - intellijReport( - exec, - report(), - null, - htmlDirFile, - classpath.get() - ) - } else { - jacocoReport( - report(), - null, - htmlDirFile, - classpath.get(), - ) - } - logger.lifecycle("Kover: HTML report for '$projectPath' file://${htmlDirFile.canonicalPath}/index.html") - } -} diff --git a/src/main/kotlin/kotlinx/kover/tasks/KoverHtmlTask.kt b/src/main/kotlin/kotlinx/kover/tasks/KoverHtmlTask.kt new file mode 100644 index 00000000..66e29507 --- /dev/null +++ b/src/main/kotlin/kotlinx/kover/tasks/KoverHtmlTask.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.kover.tasks + +import kotlinx.kover.engines.commons.* +import org.gradle.api.file.* +import org.gradle.api.tasks.* + +@CacheableTask +internal open class KoverHtmlTask : KoverReportTask() { + @get:OutputDirectory + internal val reportDir: DirectoryProperty = project.objects.directoryProperty() + + @TaskAction + fun generate() { + val reportDirFile = reportDir.get().asFile + + val projectFiles = files.get() + EngineManager.report( + engine.get(), + this, + exec, + projectFiles, + classFilters.get(), + null, + reportDirFile + ) + + if (projectFiles.keys.size > 1) { + logger.lifecycle("Kover: HTML merged report for '$projectPath' file://${reportDirFile.canonicalPath}/index.html \n merged projects ${projectFiles.keys}") + } else { + logger.lifecycle("Kover: HTML report for '$projectPath' file://${reportDirFile.canonicalPath}/index.html") + } + } +} diff --git a/src/main/kotlin/kotlinx/kover/tasks/KoverMergedTask.kt b/src/main/kotlin/kotlinx/kover/tasks/KoverMergedTask.kt deleted file mode 100644 index 29753a0a..00000000 --- a/src/main/kotlin/kotlinx/kover/tasks/KoverMergedTask.kt +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.kover.tasks - -import kotlinx.kover.api.* -import kotlinx.kover.engines.commons.* -import kotlinx.kover.engines.commons.Report -import org.gradle.api.* -import org.gradle.api.file.* -import org.gradle.api.model.* -import org.gradle.api.provider.* -import org.gradle.api.tasks.* -import org.gradle.configurationcache.extensions.* -import org.gradle.process.* -import java.io.* - -@CacheableTask -open class KoverMergedTask : DefaultTask() { - @get:Nested - val binaryReportFiles: MapProperty = - project.objects.mapProperty(String::class.java, NestedFiles::class.java) - - @get:Nested - val srcDirs: MapProperty = - project.objects.mapProperty(String::class.java, NestedFiles::class.java) - - @get:Nested - val outputDirs: MapProperty = - project.objects.mapProperty(String::class.java, NestedFiles::class.java) - - @get:Input - internal val coverageEngine: Property = project.objects.property(CoverageEngine::class.java) - - @get:Classpath - internal val classpath: Property = project.objects.property(FileCollection::class.java) - - /** - * Specifies class inclusion rules into report. - * Only the specified classes may be present in the report. - * Exclusion rules have priority over inclusion ones. - * - * Inclusion rules are represented as a set of fully-qualified names of the classes being instrumented. - * It's possible to use `*` and `?` wildcards. - * - * **Works only with IntelliJ Coverage Engine.** - */ - @get:Input - public var includes: List = emptyList() - - /** - * Specifies class exclusion rules into report. - * The specified classes will definitely be missing from report. - * Exclusion rules have priority over inclusion ones. - * - * Exclusion rules are represented as a set of fully-qualified names of the classes being instrumented. - * It's possible to use `*` and `?` wildcards. - * - * **Works only with IntelliJ Coverage Engine.** - */ - @get:Input - public var excludes: List = emptyList() - - // exec operations to launch Java applications - @get:Internal - protected val exec: ExecOperations = project.serviceOf() - - internal fun report(): Report { - val binariesMap = binaryReportFiles.get() - val sourcesMap = srcDirs.get() - val outputsMap = outputDirs.get() - - val projectsPaths = sourcesMap.keys - - val reportFiles: MutableList = mutableListOf() - val projects: MutableList = mutableListOf() - - projectsPaths.map { projectPath -> - reportFiles += binariesMap.getValue(projectPath).files.get() - projects += ProjectInfo( - sources = sourcesMap.getValue(projectPath).files.get(), - outputs = outputsMap.getValue(projectPath).files.get() - ) - } - - return Report(reportFiles, projects, includes, excludes) - } -} - - -class NestedFiles(objects: ObjectFactory, files: Provider) { - @get:InputFiles - @get:PathSensitive(PathSensitivity.RELATIVE) - val files: Property = objects.property(FileCollection::class.java).also { it.set(files) } -} diff --git a/src/main/kotlin/kotlinx/kover/tasks/KoverProjectTask.kt b/src/main/kotlin/kotlinx/kover/tasks/KoverProjectTask.kt deleted file mode 100644 index a9b39d65..00000000 --- a/src/main/kotlin/kotlinx/kover/tasks/KoverProjectTask.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.kover.tasks - -import kotlinx.kover.api.* -import kotlinx.kover.engines.commons.ProjectInfo -import kotlinx.kover.engines.commons.Report -import org.gradle.api.* -import org.gradle.api.file.* -import org.gradle.api.provider.* -import org.gradle.api.tasks.* -import org.gradle.configurationcache.extensions.* -import org.gradle.process.* - -abstract class KoverProjectTask : DefaultTask() { - @get:InputFiles - @get:PathSensitive(PathSensitivity.RELATIVE) - val binaryReportFiles: Property = project.objects.property(FileCollection::class.java) - - @get:InputFiles - @get:PathSensitive(PathSensitivity.RELATIVE) - val srcDirs: Property = project.objects.property(FileCollection::class.java) - - @get:Classpath - val outputDirs: Property = project.objects.property(FileCollection::class.java) - - @get:Input - internal val coverageEngine: Property = project.objects.property(CoverageEngine::class.java) - - @get:Classpath - internal val classpath: Property = project.objects.property(FileCollection::class.java) - - /** - * Specifies class inclusion rules into report. - * Only the specified classes may be present in the report. - * Exclusion rules have priority over inclusion ones. - * - * Inclusion rules are represented as a set of fully-qualified names of the classes being instrumented. - * It's possible to use `*` and `?` wildcards. - * - * **Works only with IntelliJ Coverage Engine.** - */ - @get:Input - public var includes: List = emptyList() - - /** - * Specifies class exclusion rules into report. - * The specified classes will definitely be missing from report. - * Exclusion rules have priority over inclusion ones. - * - * Exclusion rules are represented as a set of fully-qualified names of the classes being instrumented. - * It's possible to use `*` and `?` wildcards. - * - * **Works only with IntelliJ Coverage Engine.** - */ - @get:Input - public var excludes: List = emptyList() - - // exec operations to launch Java applications - @get:Internal - protected val exec: ExecOperations = project.serviceOf() - - internal fun report(): Report { - return Report( - binaryReportFiles.get().toList(), - listOf(ProjectInfo(srcDirs.get(), outputDirs.get())), - includes, - excludes - ) - } -} diff --git a/src/main/kotlin/kotlinx/kover/tasks/KoverReportTask.kt b/src/main/kotlin/kotlinx/kover/tasks/KoverReportTask.kt new file mode 100644 index 00000000..bde78c82 --- /dev/null +++ b/src/main/kotlin/kotlinx/kover/tasks/KoverReportTask.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.kover.tasks + +import kotlinx.kover.api.* +import org.gradle.api.* +import org.gradle.api.file.* +import org.gradle.api.provider.* +import org.gradle.api.tasks.* +import org.gradle.configurationcache.extensions.* +import org.gradle.process.* +import java.io.* + +internal abstract class KoverReportTask : DefaultTask() { + @get:Nested + internal val files: MapProperty = + project.objects.mapProperty(String::class.java, ProjectFiles::class.java) + + @get:Nested + internal val classFilters: Property = project.objects.property(KoverClassFilters::class.java) + + @get:Nested + internal val engine: Property = project.objects.property(EngineDetails::class.java) + + // exec operations to launch Java applications + @get:Internal + protected val exec: ExecOperations = project.serviceOf() + + @get:Internal + protected val projectPath: String = project.path + +} + +open class ProjectFiles( + @get:InputFiles + @get:PathSensitive(PathSensitivity.RELATIVE) + val binaryReportFiles: FileCollection, + + @get:InputFiles + @get:PathSensitive(PathSensitivity.RELATIVE) + val sources: FileCollection, + + @get:Classpath + val outputs: FileCollection +) + +internal class EngineDetails( + @get:Nested val variant: CoverageEngineVariant, + @get:Internal val jarFile: File, + @get:Internal val classpath: FileCollection +) + + + + + + + + + + + diff --git a/src/main/kotlin/kotlinx/kover/tasks/KoverVerification.kt b/src/main/kotlin/kotlinx/kover/tasks/KoverVerification.kt deleted file mode 100644 index 60681a87..00000000 --- a/src/main/kotlin/kotlinx/kover/tasks/KoverVerification.kt +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.kover.tasks - -import kotlinx.kover.api.* -import kotlinx.kover.engines.commons.* -import kotlinx.kover.engines.intellij.* -import kotlinx.kover.engines.jacoco.* -import org.gradle.api.* -import org.gradle.api.file.* -import org.gradle.api.model.* -import org.gradle.api.tasks.* -import org.gradle.process.* -import javax.inject.* - -open class KoverMergedVerificationTask : KoverMergedTask() { - private val rulesInternal: MutableList = mutableListOf() - - /** - * Added verification rules for test task. - */ - @get:Nested - public val rules: List - get() = rulesInternal - - /** - * Add new coverage verification rule to check after test task execution. - */ - public fun rule(configureRule: Action) { - rulesInternal += project.objects.newInstance( - VerificationRuleImpl::class.java, - rulesInternal.size, - project.objects - ) - .also { configureRule.execute(it) } - } - - @TaskAction - fun verify() { - verify(exec, report(), coverageEngine.get(), rulesInternal, classpath.get()) - } - -} - -open class KoverVerificationTask : KoverProjectTask() { - private val rulesInternal: MutableList = mutableListOf() - - /** - * Added verification rules for test task. - */ - @get:Nested - public val rules: List - get() = rulesInternal - - /** - * Add new coverage verification rule to check after test task execution. - */ - public fun rule(configureRule: Action) { - rulesInternal += project.objects.newInstance( - VerificationRuleImpl::class.java, - rulesInternal.size, - project.objects - ) - .also { configureRule.execute(it) } - } - - @TaskAction - fun verify() { - verify(exec, report(), coverageEngine.get(), rulesInternal, classpath.get()) - } - -} - -private fun Task.verify( - exec: ExecOperations, - report: Report, - engine: CoverageEngine, - rules: List, - classpath: FileCollection -) { - if (engine == CoverageEngine.INTELLIJ) { - intellijVerification(exec, report, rules, classpath) - } else { - jacocoVerification(report, rules, classpath) - } -} - -private open class VerificationRuleImpl @Inject constructor(override val id: Int, private val objects: ObjectFactory) : - VerificationRule { - override var name: String? = null - override val bounds: MutableList = mutableListOf() - override fun bound(configureBound: Action) { - bounds += objects.newInstance(VerificationBoundImpl::class.java, bounds.size) - .also { configureBound.execute(it) } - } -} - -private open class VerificationBoundImpl @Inject constructor(override val id: Int) : VerificationBound { - override var minValue: Int? = null - override var maxValue: Int? = null - override var valueType: VerificationValueType = VerificationValueType.COVERED_LINES_PERCENTAGE -} - - - diff --git a/src/main/kotlin/kotlinx/kover/tasks/KoverVerificationTask.kt b/src/main/kotlin/kotlinx/kover/tasks/KoverVerificationTask.kt new file mode 100644 index 00000000..feb2adab --- /dev/null +++ b/src/main/kotlin/kotlinx/kover/tasks/KoverVerificationTask.kt @@ -0,0 +1,58 @@ +package kotlinx.kover.tasks + +import kotlinx.kover.api.* +import kotlinx.kover.engines.commons.* +import org.gradle.api.* +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.ListProperty +import org.gradle.api.tasks.* + +@CacheableTask +internal open class KoverVerificationTask : KoverReportTask() { + @get:Nested + internal val rules: ListProperty = project.objects.listProperty(VerificationRule::class.java) + + @get:OutputFile + internal val resultFile: RegularFileProperty = project.objects.fileProperty() + + @TaskAction + fun verify() { + val reportRules = convertRules() + val errors = EngineManager.verify( + engine.get(), + this, + exec, + files.get(), + classFilters.get(), + reportRules, + ) + resultFile.get().asFile.writeText(errors ?: "") + + if (errors != null) { + throw GradleException(errors) + } + } + + private fun convertRules(): List { + val result = mutableListOf() + + rules.get().forEach { rule -> + if (!rule.isEnabled) { + return@forEach + } + + val ruleBounds = mutableListOf() + rule.bounds.get().forEach { bound -> + val min = bound.minValue?.toBigDecimal() + val max = bound.maxValue?.toBigDecimal() + ruleBounds += ReportVerificationBound(ruleBounds.size, min, max, bound.counter, bound.valueType) + } + + result += ReportVerificationRule(result.size, rule.name, rule.target, rule.filters.orNull, ruleBounds) + } + + return result + } + +} + diff --git a/src/main/kotlin/kotlinx/kover/tasks/KoverXmlReport.kt b/src/main/kotlin/kotlinx/kover/tasks/KoverXmlReport.kt deleted file mode 100644 index 5e582a9e..00000000 --- a/src/main/kotlin/kotlinx/kover/tasks/KoverXmlReport.kt +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.kover.tasks - -import kotlinx.kover.api.* -import kotlinx.kover.engines.intellij.* -import kotlinx.kover.engines.jacoco.* -import org.gradle.api.file.* -import org.gradle.api.tasks.* - -@CacheableTask -open class KoverMergedXmlReportTask : KoverMergedTask() { - - /** - * Specifies file path of generated XML report file with coverage data. - */ - @get:OutputFile - val xmlReportFile: RegularFileProperty = project.objects.fileProperty() - - @TaskAction - fun generate() { - if (coverageEngine.get() == CoverageEngine.INTELLIJ) { - intellijReport( - exec, - report(), - xmlReportFile.get().asFile, - null, - classpath.get() - ) - } else { - jacocoReport( - report(), - xmlReportFile.get().asFile, - null, - classpath.get() - ) - } - } -} - -@CacheableTask -open class KoverXmlReportTask : KoverProjectTask() { - - /** - * Specifies file path of generated XML report file with coverage data. - */ - @get:OutputFile - val xmlReportFile: RegularFileProperty = project.objects.fileProperty() - - @TaskAction - fun generate() { - if (coverageEngine.get() == CoverageEngine.INTELLIJ) { - intellijReport( - exec, - report(), - xmlReportFile.get().asFile, - null, - classpath.get() - ) - } else { - jacocoReport( - report(), - xmlReportFile.get().asFile, - null, - classpath.get() - ) - } - } -} diff --git a/src/main/kotlin/kotlinx/kover/tasks/KoverXmlTask.kt b/src/main/kotlin/kotlinx/kover/tasks/KoverXmlTask.kt new file mode 100644 index 00000000..50bdb590 --- /dev/null +++ b/src/main/kotlin/kotlinx/kover/tasks/KoverXmlTask.kt @@ -0,0 +1,27 @@ +package kotlinx.kover.tasks + +import kotlinx.kover.engines.commons.* +import org.gradle.api.file.* +import org.gradle.api.tasks.* + +@CacheableTask +internal open class KoverXmlTask : KoverReportTask() { + @get:OutputFile + internal val reportFile: RegularFileProperty = project.objects.fileProperty() + + @TaskAction + fun generate() { + val projectFiles = files.get() + + EngineManager.report( + engine.get(), + this, + exec, + projectFiles, + classFilters.get(), + reportFile.get().asFile, + null + ) + } + +} From ac081bfcf3bccbe2a9fe070fc7193a10aba4d80c Mon Sep 17 00:00:00 2001 From: "Sergey.Shanshin" Date: Thu, 16 Jun 2022 18:09:53 +0300 Subject: [PATCH 02/10] Added KDocs --- .../kover/test/functional/core/Builder.kt | 30 +- .../kover/test/functional/core/Types.kt | 8 +- .../functional/core/writer/CommonWriter.kt | 16 +- .../core/writer/KoverExtensionWriter.kt | 2 +- .../core/writer/MergedExtensionWriter.kt | 2 +- .../kotlinx/kover/api/CoverageEngines.kt | 14 + .../kotlin/kotlinx/kover/api/KoverConfig.kt | 282 +++++++++++++----- .../kotlinx/kover/api/KoverTaskExtension.kt | 3 +- .../kover/appliers/KoverMergedApplier.kt | 22 +- .../kover/appliers/KoverProjectApplier.kt | 14 +- .../kover/appliers/KoverTaskApplier.kt | 13 +- .../kover/engines/commons/EngineManager.kt | 14 +- .../kotlinx/kover/engines/commons/Reports.kt | 2 +- .../kover/engines/intellij/IntellijAgent.kt | 12 +- .../kover/engines/intellij/IntellijReports.kt | 12 +- .../engines/intellij/IntellijVerification.kt | 10 +- .../kover/engines/jacoco/JacocoAgent.kt | 6 +- .../kotlin/kotlinx/kover/lookup/DirsLookup.kt | 8 +- .../lookup/adapters/AndroidPluginAdapter.kt | 2 +- .../adapters/KotlinAndroidPluginAdapter.kt | 2 +- .../KotlinMultiplatformPluginAdapter.kt | 2 +- .../lookup/adapters/OldJavaPluginAdapter.kt | 2 +- .../kotlinx/kover/tasks/KoverHtmlTask.kt | 2 +- .../kotlinx/kover/tasks/KoverReportTask.kt | 2 +- .../kover/tasks/KoverVerificationTask.kt | 4 +- .../kotlinx/kover/tasks/KoverXmlTask.kt | 2 +- 26 files changed, 321 insertions(+), 167 deletions(-) diff --git a/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/Builder.kt b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/Builder.kt index 52388956..921a153b 100644 --- a/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/Builder.kt +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/Builder.kt @@ -116,15 +116,15 @@ internal class TestKoverProjectConfigState : TestKoverProjectConfig { } internal class TestKoverProjectFiltersState : TestKoverProjectFilters { - var classes: KoverClassFilters? = null - var sourcesets: KoverSourceSetFilters? = null + var classes: KoverClassFilter? = null + var sourcesets: KoverSourceSetFilter? = null - override fun classes(config: KoverClassFilters.() -> Unit) { - classes = KoverClassFilters().also(config) + override fun classes(config: KoverClassFilter.() -> Unit) { + classes = KoverClassFilter().also(config) } - override fun sourcesets(config: KoverSourceSetFilters.() -> Unit) { - sourcesets = KoverSourceSetFilters().also(config) + override fun sourcesets(config: KoverSourceSetFilter.() -> Unit) { + sourcesets = KoverSourceSetFilter().also(config) } } @@ -168,11 +168,11 @@ internal class TestVerificationRule { var name: String? = null var target: VerificationTarget? = null - var overrideClassFilters: KoverClassFilters? = null + var overrideClassFilter: KoverClassFilter? = null val bounds: MutableList = mutableListOf() - fun overrideClassFilters(config: Action) { - overrideClassFilters = KoverClassFilters().also { config.execute(it) } + fun overrideClassFilter(config: Action) { + overrideClassFilter = KoverClassFilter().also { config.execute(it) } } fun bound(configureBound: VerificationBoundState.() -> Unit) { @@ -221,14 +221,14 @@ internal class TestKoverMergedConfigState : TestKoverMergedConfig { } internal class TestKoverMergedFiltersState : TestKoverMergedFilters { - var classes: KoverClassFilters? = null - var projects: KoverProjectsFilters? = null - override fun classes(config: KoverClassFilters.() -> Unit) { - classes = KoverClassFilters().also(config) + var classes: KoverClassFilter? = null + var projects: KoverProjectsFilter? = null + override fun classes(config: KoverClassFilter.() -> Unit) { + classes = KoverClassFilter().also(config) } - override fun projects(config: KoverProjectsFilters.() -> Unit) { - projects = KoverProjectsFilters().also(config) + override fun projects(config: KoverProjectsFilter.() -> Unit) { + projects = KoverProjectsFilter().also(config) } } 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 b41be670..e93651fb 100644 --- a/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/Types.kt +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/Types.kt @@ -89,9 +89,9 @@ internal interface TestKoverProjectHtmlConfig { } internal interface TestKoverProjectFilters { - fun classes(config: KoverClassFilters.() -> Unit) + fun classes(config: KoverClassFilter.() -> Unit) - fun sourcesets(config: KoverSourceSetFilters.() -> Unit) + fun sourcesets(config: KoverSourceSetFilter.() -> Unit) } internal interface TestKoverMergedConfig { @@ -101,9 +101,9 @@ internal interface TestKoverMergedConfig { } public interface TestKoverMergedFilters { - public fun classes(config: KoverClassFilters.() -> Unit) + public fun classes(config: KoverClassFilter.() -> Unit) - public fun projects(config: KoverProjectsFilters.() -> Unit) + public fun projects(config: KoverProjectsFilter.() -> Unit) } diff --git a/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/CommonWriter.kt b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/CommonWriter.kt index c63f4738..0a946715 100644 --- a/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/CommonWriter.kt +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/CommonWriter.kt @@ -8,12 +8,12 @@ import kotlinx.kover.api.* import kotlinx.kover.test.functional.core.* import java.io.PrintWriter -internal fun PrintWriter.printClassFilters(classFilters: KoverClassFilters, slice: ProjectSlice, indents: Int) { - if (classFilters.excludes.isNotEmpty()) { - indented(indents, "excludes".addAllList(classFilters.excludes, slice.language)) +internal fun PrintWriter.printClassFilter(classFilter: KoverClassFilter, slice: ProjectSlice, indents: Int) { + if (classFilter.excludes.isNotEmpty()) { + indented(indents, "excludes".addAllList(classFilter.excludes, slice.language)) } - if (classFilters.includes.isNotEmpty()) { - indented(indents, "includes".addAllList(classFilters.includes, slice.language)) + if (classFilter.includes.isNotEmpty()) { + indented(indents, "includes".addAllList(classFilter.includes, slice.language)) } } @@ -37,9 +37,9 @@ internal fun PrintWriter.printVerify(state: TestKoverVerifyConfigState, slice: P if (rule.target != null) { indented(indents + 2, "target = ${rule.target?.enum(slice.language)}") } - if (rule.overrideClassFilters != null) { - indented(indents + 2, "overrideClassFilters {") - printClassFilters(rule.overrideClassFilters!!, slice, indents + 3) + if (rule.overrideClassFilter != null) { + indented(indents + 2, "overrideClassFilter {") + printClassFilter(rule.overrideClassFilter!!, slice, indents + 3) indented(indents + 2, "}") } rule.bounds.forEach { bound -> 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 48ee9a30..596e8e97 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 @@ -61,7 +61,7 @@ private fun PrintWriter.printFilters(state: TestKoverProjectFiltersState, slice: indented(indents, "filters {") if (classes != null && (classes.excludes.isNotEmpty() || classes.includes.isNotEmpty())) { indented(indents + 1, "classes {") - printClassFilters(classes, slice, indents + 2) + printClassFilter(classes, slice, indents + 2) indented(indents + 1, "}") } diff --git a/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/MergedExtensionWriter.kt b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/MergedExtensionWriter.kt index e5768c83..f16193dc 100644 --- a/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/MergedExtensionWriter.kt +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/MergedExtensionWriter.kt @@ -31,7 +31,7 @@ private fun PrintWriter.printFilters(state: TestKoverMergedFiltersState, slice: indented(indents, "filters {") if (classes != null && (classes.excludes.isNotEmpty() || classes.includes.isNotEmpty())) { indented(indents + 1, "classes {") - printClassFilters(classes, slice, indents + 2) + printClassFilter(classes, slice, indents + 2) indented(indents + 1, "}") } diff --git a/src/main/kotlin/kotlinx/kover/api/CoverageEngines.kt b/src/main/kotlin/kotlinx/kover/api/CoverageEngines.kt index da9560bc..202148fb 100644 --- a/src/main/kotlin/kotlinx/kover/api/CoverageEngines.kt +++ b/src/main/kotlin/kotlinx/kover/api/CoverageEngines.kt @@ -22,19 +22,33 @@ public enum class CoverageEngineVendor { JACOCO } +/** + * Coverage Engine by IntelliJ. + */ public class IntellijEngine(override val version: String): CoverageEngineVariant { override val vendor: CoverageEngineVendor = CoverageEngineVendor.INTELLIJ override fun toString(): String = "IntelliJ Coverage Engine $version" } + +/** + * IntelliJ Coverage Engine with default version [DEFAULT_INTELLIJ_VERSION]. + */ public object DefaultIntellijEngine: CoverageEngineVariant { override val vendor: CoverageEngineVendor = CoverageEngineVendor.INTELLIJ override val version: String = DEFAULT_INTELLIJ_VERSION } +/** + * Coverage Engine by JaCoCo. + */ public class JacocoEngine(override val version: String): CoverageEngineVariant { override val vendor: CoverageEngineVendor = CoverageEngineVendor.JACOCO override fun toString(): String = "JaCoCo Coverage Engine $version" } + +/** + * JaCoCo Coverage Engine with default version [DEFAULT_JACOCO_VERSION]. + */ public object DefaultJacocoEngine: CoverageEngineVariant { override val vendor: CoverageEngineVendor = CoverageEngineVendor.JACOCO override val version: String = DEFAULT_JACOCO_VERSION diff --git a/src/main/kotlin/kotlinx/kover/api/KoverConfig.kt b/src/main/kotlin/kotlinx/kover/api/KoverConfig.kt index 1336268c..93f4e322 100644 --- a/src/main/kotlin/kotlinx/kover/api/KoverConfig.kt +++ b/src/main/kotlin/kotlinx/kover/api/KoverConfig.kt @@ -16,10 +16,6 @@ import javax.annotation.* import javax.inject.* public open class KoverProjectConfig @Inject constructor(objects: ObjectFactory) { - public val isDisabled: Property = objects.property(Boolean::class.java) - - public val engine: Property = objects.property(CoverageEngineVariant::class.java) - internal val filters: KoverProjectFilters = objects.newInstance(KoverProjectFilters::class.java, objects) internal val instrumentation: KoverProjectInstrumentation = @@ -31,69 +27,122 @@ public open class KoverProjectConfig @Inject constructor(objects: ObjectFactory) internal val verify: KoverVerifyConfig = objects.newInstance(KoverVerifyConfig::class.java, objects) + /** + * Specifies whether instrumentation is disabled for all test tasks of current project. + */ + public val isDisabled: Property = objects.property(Boolean::class.java) + + /** + * Specifies the coverage engine variant to be used to collect execution data. + */ + public val engine: Property = objects.property(CoverageEngineVariant::class.java) + + /** + * Configures filters for all Kover tasks of current project. + */ public fun filters(config: Action) { config.execute(filters) } + /** + * Configures instrumentation of the test tasks of current project. + */ public fun instrumentation(config: Action) { config.execute(instrumentation) } + /** + * Configures the task of generating an XML report. + */ public fun xmlReport(config: Action) { config.execute(xmlReport) } + /** + * Configures the task of generating an HTML report. + */ public fun htmlReport(config: Action) { config.execute(htmlReport) } + /** + * Configures the verification task. + */ public fun verify(config: Action) { config.execute(verify) } } public open class KoverProjectFilters @Inject constructor(private val objects: ObjectFactory) { - internal val classes: Property = objects.property(KoverClassFilters::class.java) - //it's ok to create an instance by a constructor because this object won't be used in Gradle - .value(KoverClassFilters()) - - internal val sourceSets: Property = objects.property(KoverSourceSetFilters::class.java) - //it's ok to create an instance by a constructor because this object won't be used in Gradle - .value(KoverSourceSetFilters()) - - public fun classes(config: Action) { - val classFilters = objects.newInstance(KoverClassFilters::class.java) - config.execute(classFilters) - classes.set(classFilters) + internal val classes: Property = objects.property(KoverClassFilter::class.java) + + internal val sourceSets: Property = objects.property(KoverSourceSetFilter::class.java) + + /** + * Configures class filter. + */ + public fun classes(config: Action) { + val classFilter = objects.newInstance(KoverClassFilter::class.java) + config.execute(classFilter) + classes.set(classFilter) } - public fun sourcesets(config: Action) { - val sourceSetFilters = objects.newInstance(KoverSourceSetFilters::class.java) + /** + * Configures source set filter. + */ + public fun sourcesets(config: Action) { + val sourceSetFilters = objects.newInstance(KoverSourceSetFilter::class.java) config.execute(sourceSetFilters) sourceSets.set(sourceSetFilters) } } public open class KoverProjectInstrumentation { + /** + * Specifies the names of test tasks for which instrumentation will be disabled. + */ public val excludeTasks: MutableSet = mutableSetOf() } public open class KoverProjectXmlConfig @Inject constructor(objects: ObjectFactory) { + internal val filters: KoverProjectFilters = objects.newInstance(KoverProjectFilters::class.java, objects) + + /** + * Specifies whether the XML report generation task should be executed before the `check` task. + */ public val onCheck: Property = objects.property(Boolean::class.java) + + /** + * Specifies file path of generated XML report file with coverage data. + */ public val reportFile: RegularFileProperty = objects.fileProperty() - internal val taskFilters: KoverProjectFilters = objects.newInstance(KoverProjectFilters::class.java, objects) + /** + * Override filters for the XML report generation task. + * Only the explicitly specified filters will be overrided, the rest will be inherited from the common filters (see [KoverProjectConfig.filters]). + */ public fun overrideFilters(config: Action) { - config.execute(taskFilters) + config.execute(filters) } } public open class KoverProjectHtmlConfig @Inject constructor(private val objects: ObjectFactory) { + internal val taskFilters: KoverProjectFilters = objects.newInstance(KoverProjectFilters::class.java, objects) + + /** + * Specifies whether the HTML report generation task should be executed before the `check` task. + */ public val onCheck: Property = objects.property(Boolean::class.java) + /** + * Specifies directory path of generated HTML report. + */ public val reportDir: DirectoryProperty = objects.directoryProperty() - internal val taskFilters: KoverProjectFilters = objects.newInstance(KoverProjectFilters::class.java, objects) + /** + * Override filters for the HTML report generation task. + * Only the explicitly specified filters will be overrided, the rest will be inherited from the common filters (see [KoverProjectConfig.filters]). + */ public fun overrideFilters(config: Action) { config.execute(taskFilters) } @@ -107,42 +156,61 @@ public open class KoverMergedConfig @Inject constructor(objects: ObjectFactory) internal val htmlReport: KoverMergedHtmlConfig = objects.newInstance(KoverMergedHtmlConfig::class.java, objects) internal val verify: KoverVerifyConfig = objects.newInstance(KoverVerifyConfig::class.java, objects) + /** + * Create Kover tasks for generating merged reports. + */ public fun enable() { isEnabled.set(true) } + /** + * Configures filters for all Kover merged tasks in current project. + */ public fun filters(config: Action) { config.execute(filters) } + /** + * Configures the task of generating a merged XML report. + */ public fun xmlReport(config: Action) { config.execute(xmlReport) } + /** + * Configures the task of generating a merged HTML report. + */ public fun htmlReport(config: Action) { config.execute(htmlReport) } + /** + * Configures the merged verification task. + */ public fun verify(config: Action) { config.execute(verify) } } public open class KoverMergedFilters @Inject constructor(private val objects: ObjectFactory) { - internal val classes: Property = - objects.property(KoverClassFilters::class.java).value(KoverClassFilters()) + internal val classes: Property = objects.property(KoverClassFilter::class.java) - internal val projects: Property = - objects.property(KoverProjectsFilters::class.java).value(KoverProjectsFilters()) + internal val projects: Property = objects.property(KoverProjectsFilter::class.java) - public fun classes(config: Action) { - val classFilters = objects.newInstance(KoverClassFilters::class.java) - config.execute(classFilters) - classes.set(classFilters) + /** + * Configures class filter. + */ + public fun classes(config: Action) { + val classFilter = objects.newInstance(KoverClassFilter::class.java) + config.execute(classFilter) + classes.set(classFilter) } - public fun projects(config: Action) { - val projectsFilters = objects.newInstance(KoverProjectsFilters::class.java) + /** + * Configures projects filter. + */ + public fun projects(config: Action) { + val projectsFilters = objects.newInstance(KoverProjectsFilter::class.java) config.execute(projectsFilters) projects.set(projectsFilters) } @@ -150,6 +218,11 @@ public open class KoverMergedFilters @Inject constructor(private val objects: Ob public open class KoverMergedXmlConfig @Inject constructor(private val objects: ObjectFactory) { + internal val classFilter: Property = objects.property(KoverClassFilter::class.java) + + /** + * Specifies whether the merged XML report generation task should be executed before the `check` task. + */ public val onCheck: Property = objects.property(Boolean::class.java) /** @@ -157,17 +230,22 @@ public open class KoverMergedXmlConfig @Inject constructor(private val objects: */ public val reportFile: RegularFileProperty = objects.fileProperty() - internal val classes: Property = objects.property(KoverClassFilters::class.java) - - public fun overrideClassFilters(config: Action) { - val classFilters = objects.newInstance(KoverClassFilters::class.java) - config.execute(classFilters) - classes.set(classFilters) + /** + * Override class filter for the merged XML report generation task. + */ + public fun overrideClassFilter(config: Action) { + val newClassFilter = objects.newInstance(KoverClassFilter::class.java) + config.execute(newClassFilter) + classFilter.set(newClassFilter) } } public open class KoverMergedHtmlConfig @Inject constructor(private val objects: ObjectFactory) { + internal val classFilter: Property = objects.property(KoverClassFilter::class.java) + /** + * Specifies whether the merged HTML report generation task should be executed before the `check` task. + */ public val onCheck: Property = objects.property(Boolean::class.java) /** @@ -175,76 +253,123 @@ public open class KoverMergedHtmlConfig @Inject constructor(private val objects: */ public val reportDir: DirectoryProperty = objects.directoryProperty() - internal val classes: Property = objects.property(KoverClassFilters::class.java) - - public fun overrideClassFilters(config: Action) { - val classFilters = objects.newInstance(KoverClassFilters::class.java) - config.execute(classFilters) - classes.set(classFilters) + /** + * Override class filter for the merged HTML report generation task. + */ + public fun overrideClassFilter(config: Action) { + val newClassFilter = objects.newInstance(KoverClassFilter::class.java) + config.execute(newClassFilter) + classFilter.set(newClassFilter) } } -public open class KoverProjectsFilters { +public open class KoverProjectsFilter { + /** + * Specifies the projects involved in the merged tasks. Both the project name (if it is unique) and the project path can be used. + * + * If empty, the current project and all subprojects are used. + */ @get:Input public val includes: MutableList = mutableListOf() } public open class KoverVerifyConfig @Inject constructor(private val objects: ObjectFactory) { - public val onCheck: Property = objects.property(Boolean::class.java).value(true) - internal val rules: ListProperty = objects.listProperty(VerificationRule::class.java) + /** + * Specifies whether the verification task should be executed before the `check` task. + */ + public val onCheck: Property = objects.property(Boolean::class.java).value(true) + + /** + * Add new coverage verification rule to check after test task execution. + */ public fun rule(configureRule: Action) { rules.add(objects.newInstance(VerificationRule::class.java, objects).also { configureRule.execute(it) }) } } -public open class KoverSourceSetFilters { +public open class KoverClassFilter { + /** + * Specifies class inclusion rules into report. + * Only the specified classes may be present in the report. + * Exclusion rules have priority over inclusion ones. + * + * Inclusion rules are represented as a set of fully-qualified names of the classes being instrumented. + * It's possible to use `*` and `?` wildcards. + */ @get:Input - public val excludes: MutableSet = mutableSetOf() + public val includes: MutableList = mutableListOf() + /** + * Specifies class exclusion rules into report. + * The specified classes will definitely be missing from report. + * Exclusion rules have priority over inclusion ones. + * + * Exclusion rules are represented as a set of fully-qualified names of the classes being instrumented. + * It's possible to use `*` and `?` wildcards. + */ @get:Input - public var excludeTests: Boolean = true + public val excludes: MutableList = mutableListOf() } -public open class KoverClassFilters { +public open class KoverSourceSetFilter { + /** + * TBD + */ @get:Input - public val includes: MutableList = mutableListOf() + public val excludes: MutableSet = mutableSetOf() + /** + * TBD + */ @get:Input - public val excludes: MutableList = mutableListOf() + public var excludeTests: Boolean = true } + public open class VerificationRule @Inject constructor(private val objects: ObjectFactory) { + @get:Nested + @get:Optional + internal val classFilter: Property = objects.property(KoverClassFilter::class.java) + + @get:Nested + internal val bounds: ListProperty = objects.listProperty(VerificationBound::class.java) + + /** + * Specifies that the rule will be checked during verification. + */ @get:Input public var isEnabled: Boolean = true + /** + * Specifies custom name of the rule. + */ @get:Input @get:Nullable @get:Optional public var name: String? = null + /** + * Specifies by which entity the code for separate coverage evaluation will be grouped. + */ @get:Input public var target: VerificationTarget = VerificationTarget.ALL /** - * Absent default value to indicate that class filters are not overridden for the rule. + * Override class filter for the rule. */ - @get:Nested - @get:Optional - internal val filters: Property = objects.property(KoverClassFilters::class.java) - - @get:Nested - internal val bounds: ListProperty = objects.listProperty(VerificationBound::class.java) - - public fun overrideClassFilters(config: Action) { - if (!filters.isPresent) { - filters.set(objects.newInstance(KoverClassFilters::class.java)) + public fun overrideClassFilter(config: Action) { + if (!classFilter.isPresent) { + classFilter.set(objects.newInstance(KoverClassFilter::class.java)) } - config.execute(filters.get()) + config.execute(classFilter.get()) } + /** + * Add a constraint on the value of the code coverage metric. + */ public fun bound(configureBound: Action) { bounds.add(objects.newInstance(VerificationBound::class.java).also { configureBound.execute(it) }) } @@ -252,7 +377,7 @@ public open class VerificationRule @Inject constructor(private val objects: Obje public open class VerificationBound { /** - * Minimal value to compare with counter value. + * Specifies minimal value to compare with counter value. */ @get:Input @get:Nullable @@ -260,7 +385,7 @@ public open class VerificationBound { public var minValue: Int? = null /** - * Maximal value to compare with counter value. + * Specifies maximal value to compare with counter value. */ @get:Input @get:Nullable @@ -268,13 +393,13 @@ public open class VerificationBound { public var maxValue: Int? = null /** - * TODO + * Specifies which metric will be evaluation code coverage. */ @get:Input public var counter: CounterType = CounterType.LINE /** - * Type of lines counter value to compare with minimal and maximal values if them defined. + * Specifies type of lines counter value to compare with minimal and maximal values if them defined. * Default is [VerificationValueType.COVERED_PERCENTAGE] */ @get:Input @@ -282,31 +407,42 @@ public open class VerificationBound { } /** - * TODO + * Entity type for grouping code to coverage evaluation. */ public enum class VerificationTarget { /** - * TODO + * Count the coverage for all code. */ ALL, /** - * TODO + * Count the coverage for each class separately. */ CLASS, /** - * TODO + * Count the coverage for each package that has classes separately. */ PACKAGE } /** - * TODO + * Type of the metric to evaluate code coverage. */ public enum class CounterType { + /** + * Evaluate coverage for lines. + */ LINE, + + /** + * Evaluate coverage for JVM bytecode instructions. + */ INSTRUCTION, + + /** + * Evaluate coverage for code branches. + */ BRANCH } diff --git a/src/main/kotlin/kotlinx/kover/api/KoverTaskExtension.kt b/src/main/kotlin/kotlinx/kover/api/KoverTaskExtension.kt index b8b2cd72..049b391a 100644 --- a/src/main/kotlin/kotlinx/kover/api/KoverTaskExtension.kt +++ b/src/main/kotlin/kotlinx/kover/api/KoverTaskExtension.kt @@ -11,6 +11,7 @@ import org.gradle.api.model.* import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property import org.gradle.api.tasks.* +import java.io.* /** * Extension for Kover plugin that additionally configures test tasks and @@ -61,5 +62,5 @@ public open class KoverTaskExtension(objects: ObjectFactory) { replaceWith = ReplaceWith("reportFile"), level = DeprecationLevel.ERROR ) - public val binaryReportFile: RegularFileProperty? = null + public val binaryReportFile: Property = objects.property(File::class.java) } diff --git a/src/main/kotlin/kotlinx/kover/appliers/KoverMergedApplier.kt b/src/main/kotlin/kotlinx/kover/appliers/KoverMergedApplier.kt index f4ac654d..78fa29e9 100644 --- a/src/main/kotlin/kotlinx/kover/appliers/KoverMergedApplier.kt +++ b/src/main/kotlin/kotlinx/kover/appliers/KoverMergedApplier.kt @@ -30,12 +30,14 @@ internal fun Project.applyMerged() { private fun Project.createMergedExtension(): KoverMergedConfig { val extension = extensions.create(KoverNames.MERGED_EXTENSION_NAME, KoverMergedConfig::class.java, objects) extension.isEnabled.set(false) + extension.filters.classes.set(KoverClassFilter()) + extension.filters.projects.set(KoverProjectsFilter()) extension.xmlReport.onCheck.set(false) extension.xmlReport.reportFile.set(layout.buildDirectory.file(MERGED_XML_REPORT_DEFAULT_PATH)) - extension.xmlReport.classes.set(extension.filters.classes) + extension.xmlReport.classFilter.set(extension.filters.classes) extension.htmlReport.onCheck.set(false) extension.htmlReport.reportDir.set(layout.buildDirectory.dir(MERGED_HTML_REPORT_DEFAULT_PATH)) - extension.htmlReport.classes.set(extension.filters.classes) + extension.htmlReport.classFilter.set(extension.filters.classes) extension.verify.onCheck.set(false) return extension } @@ -53,11 +55,11 @@ private class ProcessMergeExtensionAction(private val extension: KoverMergedConf val xmlTask = container.createMergedTask( MERGED_XML_REPORT_TASK_NAME, - extension.xmlReport.classes, + extension.xmlReport.classFilter, extensionByProject, engineProvider, testsProvider, - { e -> e.xmlReport.taskFilters.sourceSets.get() } + { e -> e.xmlReport.filters.sourceSets.get() } ) { it.reportFile.set(extension.xmlReport.reportFile) it.description = "Generates code coverage XML report for all enabled test tasks in specified projects." @@ -65,7 +67,7 @@ private class ProcessMergeExtensionAction(private val extension: KoverMergedConf val htmlTask = container.createMergedTask( MERGED_HTML_REPORT_TASK_NAME, - extension.htmlReport.classes, + extension.htmlReport.classFilter, extensionByProject, engineProvider, testsProvider, @@ -121,11 +123,11 @@ private class ProcessMergeExtensionAction(private val extension: KoverMergedConf private inline fun Project.createMergedTask( taskName: String, - classFilters: Provider, + classFilter: Provider, extensionByProject: Provider>, engineProvider: Provider, testsProvider: Provider>, - crossinline filterExtractor: (KoverProjectConfig) -> KoverSourceSetFilters, + crossinline filterExtractor: (KoverProjectConfig) -> KoverSourceSetFilter, crossinline block: (T) -> Unit ): T { val task = tasks.create(taskName, T::class.java) { @@ -133,7 +135,7 @@ private inline fun Project.createMergedTask( it.engine.set(engineProvider) it.dependsOn(testsProvider) - it.classFilters.set(classFilters) + it.classFilter.set(classFilter) it.group = VERIFICATION_GROUP block(it) } @@ -163,7 +165,7 @@ private fun Project.projectsExtensionsProvider( private inline fun Project.mergedFilesProvider( extensionByProject: Provider>, - crossinline filterExtractor: (KoverProjectConfig) -> KoverSourceSetFilters + crossinline filterExtractor: (KoverProjectConfig) -> KoverSourceSetFilter ): Provider> { return provider { extensionByProject.get() @@ -173,7 +175,7 @@ private inline fun Project.mergedFilesProvider( } -private fun filterProjects(filters: KoverProjectsFilters, allProjects: Iterable): List { +private fun filterProjects(filters: KoverProjectsFilter, allProjects: Iterable): List { if (filters.includes.isEmpty()) { return allProjects.toList() } diff --git a/src/main/kotlin/kotlinx/kover/appliers/KoverProjectApplier.kt b/src/main/kotlin/kotlinx/kover/appliers/KoverProjectApplier.kt index 1376daa7..48a7a34b 100644 --- a/src/main/kotlin/kotlinx/kover/appliers/KoverProjectApplier.kt +++ b/src/main/kotlin/kotlinx/kover/appliers/KoverProjectApplier.kt @@ -34,7 +34,7 @@ internal fun Project.applyToProject() { val xmlTask = createTask( XML_REPORT_TASK_NAME, - extension.xmlReport.taskFilters, + extension.xmlReport.filters, extension, engineProvider, testsProvider, @@ -114,7 +114,7 @@ private inline fun Project.createTask( it.files.put(path, projectFilesProvider(extension, filters.sourceSets)) it.engine.set(engineProvider) it.dependsOn(testsProvider) - it.classFilters.set(filters.classes) + it.classFilter.set(filters.classes) it.group = KoverNames.VERIFICATION_GROUP block(it) } @@ -134,10 +134,12 @@ private fun Project.createProjectExtension(): KoverProjectConfig { extension.isDisabled.set(false) extension.engine.set(DefaultIntellijEngine) + extension.filters.classes.set(KoverClassFilter()) + extension.filters.sourceSets.set(KoverSourceSetFilter()) extension.xmlReport.reportFile.set(layout.buildDirectory.file(KoverPaths.PROJECT_XML_REPORT_DEFAULT_PATH)) extension.xmlReport.onCheck.set(false) - extension.xmlReport.taskFilters.classes.set(extension.filters.classes) - extension.xmlReport.taskFilters.sourceSets.set(extension.filters.sourceSets) + extension.xmlReport.filters.classes.set(extension.filters.classes) + extension.xmlReport.filters.sourceSets.set(extension.filters.sourceSets) extension.htmlReport.reportDir.set(layout.buildDirectory.dir(KoverPaths.PROJECT_HTML_REPORT_DEFAULT_PATH)) extension.htmlReport.onCheck.set(false) extension.htmlReport.taskFilters.classes.set(extension.filters.classes) @@ -181,13 +183,13 @@ internal fun engineByVariant( internal fun Project.projectFilesProvider( extension: KoverProjectConfig, - sourceSetFiltersProvider: Provider + sourceSetFiltersProvider: Provider ): Provider { return provider { projectFiles(sourceSetFiltersProvider.get(), extension) } } internal fun Project.projectFiles( - filters: KoverSourceSetFilters, + filters: KoverSourceSetFilter, extension: KoverProjectConfig ): ProjectFiles { val directories = DirsLookup.lookup(project, filters) diff --git a/src/main/kotlin/kotlinx/kover/appliers/KoverTaskApplier.kt b/src/main/kotlin/kotlinx/kover/appliers/KoverTaskApplier.kt index c2016635..49b82d08 100644 --- a/src/main/kotlin/kotlinx/kover/appliers/KoverTaskApplier.kt +++ b/src/main/kotlin/kotlinx/kover/appliers/KoverTaskApplier.kt @@ -12,7 +12,6 @@ import org.gradle.api.file.RegularFile import org.gradle.api.provider.* import org.gradle.api.tasks.* import org.gradle.api.tasks.testing.* -import org.gradle.configurationcache.extensions.* import org.gradle.process.* import java.io.* @@ -71,10 +70,10 @@ private class CoverageArgumentProvider( } val reportFile = taskExtension.reportFile.get().asFile - val classFilters = KoverClassFilters() + val classFilter = KoverClassFilter() - classFilters.includes += taskExtension.includes.get() - classFilters.excludes += taskExtension.excludes.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). @@ -84,10 +83,10 @@ private class CoverageArgumentProvider( FIXME Remove this code if the IntelliJ Agent stops changing project classes during instrumentation */ - classFilters.excludes += "android.*" - classFilters.excludes += "com.android.*" + classFilter.excludes += "android.*" + classFilter.excludes += "com.android.*" - return EngineManager.buildAgentArgs(engineProvider.get(), task, reportFile, classFilters) + return EngineManager.buildAgentArgs(engineProvider.get(), task, reportFile, classFilter) } } diff --git a/src/main/kotlin/kotlinx/kover/engines/commons/EngineManager.kt b/src/main/kotlin/kotlinx/kover/engines/commons/EngineManager.kt index bf70fd68..6dea8b3f 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, - classFilters: KoverClassFilters + classFilter: KoverClassFilter ): MutableList { return if (details.variant.vendor == CoverageEngineVendor.INTELLIJ) { - task.buildIntellijAgentJvmArgs(details.jarFile, reportFile, classFilters) + task.buildIntellijAgentJvmArgs(details.jarFile, reportFile, classFilter) } else { reportFile.parentFile.mkdirs() - task.buildJacocoAgentJvmArgs(details.jarFile, reportFile, classFilters) + task.buildJacocoAgentJvmArgs(details.jarFile, reportFile, classFilter) } } @@ -36,12 +36,12 @@ internal object EngineManager { task: Task, exec: ExecOperations, projectFiles: Map, - classFilters: KoverClassFilters, + classFilter: KoverClassFilter, xmlFile: File?, htmlDir: File? ) { if (details.variant.vendor == CoverageEngineVendor.INTELLIJ) { - task.intellijReport(exec, projectFiles, classFilters, xmlFile, htmlDir, details.classpath) + task.intellijReport(exec, projectFiles, classFilter, xmlFile, htmlDir, details.classpath) } else { task.jacocoReport(projectFiles, xmlFile, htmlDir, details.classpath) } @@ -52,11 +52,11 @@ internal object EngineManager { task: Task, exec: ExecOperations, projectFiles: Map, - classFilters: KoverClassFilters, + classFilter: KoverClassFilter, rules: List, ): String? { return if (details.variant.vendor == CoverageEngineVendor.INTELLIJ) { - task.intellijVerification(exec, projectFiles, classFilters, rules, details.classpath) + task.intellijVerification(exec, projectFiles, classFilter, rules, details.classpath) } else { task.jacocoVerification(projectFiles, rules, details.classpath) } diff --git a/src/main/kotlin/kotlinx/kover/engines/commons/Reports.kt b/src/main/kotlin/kotlinx/kover/engines/commons/Reports.kt index d6566599..e9c7238a 100644 --- a/src/main/kotlin/kotlinx/kover/engines/commons/Reports.kt +++ b/src/main/kotlin/kotlinx/kover/engines/commons/Reports.kt @@ -12,7 +12,7 @@ internal class ReportVerificationRule( val id: Int, val name: String?, val target: VerificationTarget, - val filters: KoverClassFilters?, + val filters: KoverClassFilter?, val bounds: List ) diff --git a/src/main/kotlin/kotlinx/kover/engines/intellij/IntellijAgent.kt b/src/main/kotlin/kotlinx/kover/engines/intellij/IntellijAgent.kt index 9fdb6824..93716c33 100644 --- a/src/main/kotlin/kotlinx/kover/engines/intellij/IntellijAgent.kt +++ b/src/main/kotlin/kotlinx/kover/engines/intellij/IntellijAgent.kt @@ -14,9 +14,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, classFilters: KoverClassFilters): MutableList { +internal fun Task.buildIntellijAgentJvmArgs(jarFile: File, reportFile: File, classFilter: KoverClassFilter): MutableList { val argsFile = File(temporaryDir, "intellijagent.args") - argsFile.writeAgentArgs(reportFile, classFilters) + argsFile.writeAgentArgs(reportFile, classFilter) return mutableListOf( "-javaagent:${jarFile.canonicalPath}=${argsFile.canonicalPath}", @@ -27,7 +27,7 @@ internal fun Task.buildIntellijAgentJvmArgs(jarFile: File, reportFile: File, cla ) } -private fun File.writeAgentArgs(reportFile: File, classFilters: KoverClassFilters) { +private fun File.writeAgentArgs(reportFile: File, classFilter: KoverClassFilter) { reportFile.parentFile.mkdirs() val binaryPath = reportFile.canonicalPath @@ -37,15 +37,15 @@ private fun File.writeAgentArgs(reportFile: File, classFilters: KoverClassFilter pw.appendLine(calculateForUnloadedClasses.toString()) pw.appendLine(appendToDataFile.toString()) pw.appendLine(samplingMode.toString()) - classFilters.includes.forEach { i -> + classFilter.includes.forEach { i -> pw.appendLine(i.wildcardsToRegex()) } - if (classFilters.excludes.isNotEmpty()) { + if (classFilter.excludes.isNotEmpty()) { pw.appendLine("-exclude") } - classFilters.excludes.forEach { e -> + classFilter.excludes.forEach { e -> pw.appendLine(e.wildcardsToRegex()) } } diff --git a/src/main/kotlin/kotlinx/kover/engines/intellij/IntellijReports.kt b/src/main/kotlin/kotlinx/kover/engines/intellij/IntellijReports.kt index f064e639..d4c330ff 100644 --- a/src/main/kotlin/kotlinx/kover/engines/intellij/IntellijReports.kt +++ b/src/main/kotlin/kotlinx/kover/engines/intellij/IntellijReports.kt @@ -16,7 +16,7 @@ import java.io.* internal fun Task.intellijReport( exec: ExecOperations, projectFiles: Map, - filters: KoverClassFilters, + filters: KoverClassFilter, xmlFile: File?, htmlDir: File?, classpath: FileCollection @@ -80,7 +80,7 @@ JSON example: */ private fun File.writeReportsJson( projectFiles: Map, - classFilters: KoverClassFilters, + classFilter: KoverClassFilter, xmlFile: File?, htmlDir: File? ) { @@ -88,11 +88,11 @@ private fun File.writeReportsJson( "reports" to projectFiles.flatMap { it.value.binaryReportFiles }.map { mapOf("ic" to it) }, "modules" to projectFiles.map { mapOf("sources" to it.value.sources, "output" to it.value.outputs) }, ).also { - if (classFilters.includes.isNotEmpty()) { - it["include"] = mapOf("classes" to classFilters.includes.map { c -> c.wildcardsToRegex() }) + if (classFilter.includes.isNotEmpty()) { + it["include"] = mapOf("classes" to classFilter.includes.map { c -> c.wildcardsToRegex() }) } - if (classFilters.excludes.isNotEmpty()) { - it["exclude"] = mapOf("classes" to classFilters.excludes.map { c -> c.wildcardsToRegex() }) + if (classFilter.excludes.isNotEmpty()) { + it["exclude"] = mapOf("classes" to classFilter.excludes.map { c -> c.wildcardsToRegex() }) } xmlFile?.also { f -> it["xml"] = f diff --git a/src/main/kotlin/kotlinx/kover/engines/intellij/IntellijVerification.kt b/src/main/kotlin/kotlinx/kover/engines/intellij/IntellijVerification.kt index 38f6b1fe..63e58aa0 100644 --- a/src/main/kotlin/kotlinx/kover/engines/intellij/IntellijVerification.kt +++ b/src/main/kotlin/kotlinx/kover/engines/intellij/IntellijVerification.kt @@ -20,12 +20,12 @@ import java.math.RoundingMode internal fun Task.intellijVerification( exec: ExecOperations, projectFiles: Map, - classFilters: KoverClassFilters, + classFilter: KoverClassFilter, rules: List, classpath: FileCollection ): String? { val aggRequest = File(temporaryDir, "agg-request.json") - val groupedRules = groupRules(classFilters, rules) + val groupedRules = groupRules(classFilter, rules) aggRequest.writeAggJson(projectFiles, groupedRules) exec.javaexec { e -> e.mainClass.set("com.intellij.rt.coverage.aggregate.Main") @@ -53,18 +53,18 @@ internal fun Task.intellijVerification( private data class RulesGroup( val aggFile: File, - val filters: KoverClassFilters, + val filters: KoverClassFilter, val rules: List ) private fun Task.groupRules( - commonClassFilters: KoverClassFilters, + commonClassFilter: KoverClassFilter, allRules: List ): List { val result = mutableListOf() val commonAggFile = File(temporaryDir, "aggregated-common.ic") val commonRules = mutableListOf() - result += RulesGroup(commonAggFile, commonClassFilters, commonRules) + result += RulesGroup(commonAggFile, commonClassFilter, commonRules) allRules.forEach { if (it.filters == null) { diff --git a/src/main/kotlin/kotlinx/kover/engines/jacoco/JacocoAgent.kt b/src/main/kotlin/kotlinx/kover/engines/jacoco/JacocoAgent.kt index c2d40a3d..1bc31b02 100644 --- a/src/main/kotlin/kotlinx/kover/engines/jacoco/JacocoAgent.kt +++ b/src/main/kotlin/kotlinx/kover/engines/jacoco/JacocoAgent.kt @@ -8,11 +8,11 @@ import kotlinx.kover.api.* import org.gradle.api.Task import java.io.* -internal fun Task.buildJacocoAgentJvmArgs(jarFile: File, reportFile: File, classFilters: KoverClassFilters): MutableList { +internal fun Task.buildJacocoAgentJvmArgs(jarFile: File, reportFile: File, classFilter: KoverClassFilter): MutableList { val agentArgs = listOfNotNull( "destfile=${reportFile.canonicalPath},append=true,inclnolocationclasses=false,dumponexit=true,output=file,jmx=false", - classFilters.includes.joinToFilterString("includes"), - classFilters.excludes.joinToFilterString("excludes") + classFilter.includes.joinToFilterString("includes"), + classFilter.excludes.joinToFilterString("excludes") ).joinToString(",") return mutableListOf("-javaagent:${jarFile.canonicalPath}=$agentArgs") diff --git a/src/main/kotlin/kotlinx/kover/lookup/DirsLookup.kt b/src/main/kotlin/kotlinx/kover/lookup/DirsLookup.kt index 16481d2b..118dbcf8 100644 --- a/src/main/kotlin/kotlinx/kover/lookup/DirsLookup.kt +++ b/src/main/kotlin/kotlinx/kover/lookup/DirsLookup.kt @@ -21,7 +21,7 @@ internal object DirsLookup { ) @Suppress("UNUSED_PARAMETER") - fun lookup(project: Project, sourceSetFilters: KoverSourceSetFilters): ProjectDirectories { + fun lookup(project: Project, sourceSetFilters: KoverSourceSetFilter): ProjectDirectories { val srcDirs = HashMap() val outDirs = HashMap() @@ -42,9 +42,9 @@ internal object DirsLookup { internal abstract class LookupAdapter { - protected abstract fun lookup(project: Project, sourceSetFilters: KoverSourceSetFilters): Dirs + protected abstract fun lookup(project: Project, sourceSetFilters: KoverSourceSetFilter): Dirs - fun lookupSafe(project: Project, sourceSetFilters: KoverSourceSetFilters): Dirs { + fun lookupSafe(project: Project, sourceSetFilters: KoverSourceSetFilter): Dirs { return try { lookup(project, sourceSetFilters) } catch (e: Throwable) { @@ -58,7 +58,7 @@ internal abstract class LookupAdapter { } } - protected fun filterSourceSet(name: String, sourceSetFilters: KoverSourceSetFilters): Boolean { + protected fun filterSourceSet(name: String, sourceSetFilters: KoverSourceSetFilter): Boolean { if (sourceSetFilters.excludes.contains(name)) { return false } diff --git a/src/main/kotlin/kotlinx/kover/lookup/adapters/AndroidPluginAdapter.kt b/src/main/kotlin/kotlinx/kover/lookup/adapters/AndroidPluginAdapter.kt index 01ef4d86..1b1e3aaf 100644 --- a/src/main/kotlin/kotlinx/kover/lookup/adapters/AndroidPluginAdapter.kt +++ b/src/main/kotlin/kotlinx/kover/lookup/adapters/AndroidPluginAdapter.kt @@ -10,7 +10,7 @@ import kotlinx.kover.lookup.* import org.gradle.api.* internal class AndroidPluginAdapter : LookupAdapter() { - override fun lookup(project: Project, sourceSetFilters: KoverSourceSetFilters): Dirs { + override fun lookup(project: Project, sourceSetFilters: KoverSourceSetFilter): Dirs { project.plugins.findPlugin("android") ?: return Dirs() val extension = project.extensions.findByType(BaseExtension::class.java) ?: return Dirs() diff --git a/src/main/kotlin/kotlinx/kover/lookup/adapters/KotlinAndroidPluginAdapter.kt b/src/main/kotlin/kotlinx/kover/lookup/adapters/KotlinAndroidPluginAdapter.kt index c8302fff..f352cb58 100644 --- a/src/main/kotlin/kotlinx/kover/lookup/adapters/KotlinAndroidPluginAdapter.kt +++ b/src/main/kotlin/kotlinx/kover/lookup/adapters/KotlinAndroidPluginAdapter.kt @@ -11,7 +11,7 @@ import org.jetbrains.kotlin.gradle.dsl.* internal class KotlinAndroidPluginAdapter : LookupAdapter() { - override fun lookup(project: Project, sourceSetFilters: KoverSourceSetFilters): Dirs { + override fun lookup(project: Project, sourceSetFilters: KoverSourceSetFilter): Dirs { project.plugins.findPlugin("kotlin-android") ?: return Dirs() val extension = project.extensions.findByType(KotlinAndroidProjectExtension::class.java) ?: return Dirs() diff --git a/src/main/kotlin/kotlinx/kover/lookup/adapters/KotlinMultiplatformPluginAdapter.kt b/src/main/kotlin/kotlinx/kover/lookup/adapters/KotlinMultiplatformPluginAdapter.kt index 3533c28c..c5539f05 100644 --- a/src/main/kotlin/kotlinx/kover/lookup/adapters/KotlinMultiplatformPluginAdapter.kt +++ b/src/main/kotlin/kotlinx/kover/lookup/adapters/KotlinMultiplatformPluginAdapter.kt @@ -15,7 +15,7 @@ import org.jetbrains.kotlin.gradle.plugin.* internal class KotlinMultiplatformPluginAdapter : LookupAdapter() { - override fun lookup(project: Project, sourceSetFilters: KoverSourceSetFilters): Dirs { + override fun lookup(project: Project, sourceSetFilters: KoverSourceSetFilter): Dirs { project.plugins.findPlugin("kotlin-multiplatform") ?: return Dirs() val extension = try { diff --git a/src/main/kotlin/kotlinx/kover/lookup/adapters/OldJavaPluginAdapter.kt b/src/main/kotlin/kotlinx/kover/lookup/adapters/OldJavaPluginAdapter.kt index c9f44d1f..d1086689 100644 --- a/src/main/kotlin/kotlinx/kover/lookup/adapters/OldJavaPluginAdapter.kt +++ b/src/main/kotlin/kotlinx/kover/lookup/adapters/OldJavaPluginAdapter.kt @@ -11,7 +11,7 @@ import org.gradle.api.tasks.* internal class OldJavaPluginAdapter : LookupAdapter() { - override fun lookup(project: Project, sourceSetFilters: KoverSourceSetFilters): Dirs { + override fun lookup(project: Project, sourceSetFilters: KoverSourceSetFilter): Dirs { project.plugins.findPlugin("java") ?: return Dirs() val sourceSetContainer = project.extensions.findByType( diff --git a/src/main/kotlin/kotlinx/kover/tasks/KoverHtmlTask.kt b/src/main/kotlin/kotlinx/kover/tasks/KoverHtmlTask.kt index 66e29507..1ead39c3 100644 --- a/src/main/kotlin/kotlinx/kover/tasks/KoverHtmlTask.kt +++ b/src/main/kotlin/kotlinx/kover/tasks/KoverHtmlTask.kt @@ -23,7 +23,7 @@ internal open class KoverHtmlTask : KoverReportTask() { this, exec, projectFiles, - classFilters.get(), + classFilter.get(), null, reportDirFile ) diff --git a/src/main/kotlin/kotlinx/kover/tasks/KoverReportTask.kt b/src/main/kotlin/kotlinx/kover/tasks/KoverReportTask.kt index bde78c82..48718a2e 100644 --- a/src/main/kotlin/kotlinx/kover/tasks/KoverReportTask.kt +++ b/src/main/kotlin/kotlinx/kover/tasks/KoverReportTask.kt @@ -19,7 +19,7 @@ internal abstract class KoverReportTask : DefaultTask() { project.objects.mapProperty(String::class.java, ProjectFiles::class.java) @get:Nested - internal val classFilters: Property = project.objects.property(KoverClassFilters::class.java) + internal val classFilter: Property = project.objects.property(KoverClassFilter::class.java) @get:Nested internal val engine: Property = project.objects.property(EngineDetails::class.java) diff --git a/src/main/kotlin/kotlinx/kover/tasks/KoverVerificationTask.kt b/src/main/kotlin/kotlinx/kover/tasks/KoverVerificationTask.kt index feb2adab..039c167d 100644 --- a/src/main/kotlin/kotlinx/kover/tasks/KoverVerificationTask.kt +++ b/src/main/kotlin/kotlinx/kover/tasks/KoverVerificationTask.kt @@ -23,7 +23,7 @@ internal open class KoverVerificationTask : KoverReportTask() { this, exec, files.get(), - classFilters.get(), + classFilter.get(), reportRules, ) resultFile.get().asFile.writeText(errors ?: "") @@ -48,7 +48,7 @@ internal open class KoverVerificationTask : KoverReportTask() { ruleBounds += ReportVerificationBound(ruleBounds.size, min, max, bound.counter, bound.valueType) } - result += ReportVerificationRule(result.size, rule.name, rule.target, rule.filters.orNull, ruleBounds) + result += ReportVerificationRule(result.size, rule.name, rule.target, rule.classFilter.orNull, ruleBounds) } return result diff --git a/src/main/kotlin/kotlinx/kover/tasks/KoverXmlTask.kt b/src/main/kotlin/kotlinx/kover/tasks/KoverXmlTask.kt index 50bdb590..f471c83d 100644 --- a/src/main/kotlin/kotlinx/kover/tasks/KoverXmlTask.kt +++ b/src/main/kotlin/kotlinx/kover/tasks/KoverXmlTask.kt @@ -18,7 +18,7 @@ internal open class KoverXmlTask : KoverReportTask() { this, exec, projectFiles, - classFilters.get(), + classFilter.get(), reportFile.get().asFile, null ) From 2e90be20bc3de271e108d2a0df3c1e7dbc815920 Mon Sep 17 00:00:00 2001 From: "Sergey.Shanshin" Date: Mon, 27 Jun 2022 15:30:55 +0300 Subject: [PATCH 03/10] Added deprecation on old properties and written migration guide --- docs/migration-to-0.6.0.md | 316 ++++++++++++++++++ .../kotlin/kotlinx/kover/api/KoverConfig.kt | 64 ++++ .../kotlinx/kover/api/KoverTaskExtension.kt | 2 +- .../kotlinx/kover/tasks/KoverHtmlTask.kt | 27 +- .../kotlinx/kover/tasks/KoverReportTask.kt | 3 +- .../kover/tasks/KoverVerificationTask.kt | 28 +- .../kotlinx/kover/tasks/KoverXmlTask.kt | 26 +- 7 files changed, 461 insertions(+), 5 deletions(-) create mode 100644 docs/migration-to-0.6.0.md diff --git a/docs/migration-to-0.6.0.md b/docs/migration-to-0.6.0.md new file mode 100644 index 00000000..a1360f73 --- /dev/null +++ b/docs/migration-to-0.6.0.md @@ -0,0 +1,316 @@ +# Main differences +The new API allows you to configure Kover more flexible and in one place. +Now there is no need to configure each Kover task separately. + +To configure reports that collect coverage only for tests from one project, the `kover { }` project extension is used. + +To configure reports that collect test coverage from different projects, the `koverMerged { }` project extension is used. +At the same time, in order to create tasks with answers, they must be explicitly activated +``` +koverMerged { + enable() +} +``` + +# Migration Issues + +## Root kover extension + +### type of "isDisabled" property changed from "Boolean" to "Property\". + +for Kotlin script: change `isDisabled = true` to `isDisabled.set(true)` + +### properties "coverageEngine", "intellijEngineVersion" and "jacocoEngineVersion" was removed. + +Use property `engine` - it combines version and coverage engine vendor. + +To use IntelliJ Coverage Engine with default version write `engine.set(kotlinx.kover.api.DefaultIntellijEngine)` +(Kotlin) or `engine = kotlinx.kover.api.DefaultIntellijEngine.INSTANCE` (Groovy). + +To use IntelliJ Coverage Engine with custom version write `engine.set(kotlinx.kover.api.IntellijEngine("version"))` +(Kotlin) or `engine = kotlinx.kover.api.IntellijEngine("version")` (Groovy). + +To use JaCoCo Coverage Engine with default version write `engine.set(kotlinx.kover.api.DefaultJacocoEngine)` +(Kotlin) or `engine = kotlinx.kover.api.DefaultJacocoEngine.INSTANCE` (Groovy). + +To use JaCoCo Coverage Engine with custom version write `engine.set(kotlinx.kover.api.JacocoEngine("version"))` +(Kotlin) or `engine = kotlinx.kover.api.JacocoEngine("version")` (Groovy). + +### property "generateReportOnCheck" was removed + +Use the properties individually for each report + +``` +kover { + xmlReport { + onCheck.set(true) + } + + htmlReport { + onCheck.set(true) + } + + verify { + onCheck.set(true) + } +} +``` + +### property "disabledProjects" was removed + +Use inclusion list in project filters + +``` +koverMerged { + enable() + filters { + projects { + includes.add(":path or unique project name") + } + } +} +``` + +If `includes` is empty - all subprojects and current project are used in merged reports. + +### property "instrumentAndroidPackage" was removed + +There is no replacement. At the moment, all classes from the packages "android." and "com.android.*" excluded from +instrumentation. + +### property "runAllTestsForProjectTask" was removed + +TBD + +## Kover extension for test task + +### type of "isDisabled" property changed from "Boolean" to "Property\". + +Solution for Kotlin script: change `isDisabled = true` to `isDisabled.set(true)` + +### "binaryReportFile" was renamed to "reportFile" + +Solution: change `binaryReportFile` to `reportFile` + +### type of "includes" property changed from "List" to "ListProperty" + +Solution for Kotlin: change `includes = listOf("com.example.*", "foo.bar.*")` +to `includes.addAll("com.example.*", "foo.bar.*")` +Solution for Groovy: change `includes = ["com.example.*", "foo.bar.*"]` +to `includes.addAll("com.example.*", "foo.bar.*")` + +### type of "excludes" property changed from "List\" to "ListProperty\" + +Solution for Kotlin: change `excludes = listOf("com.example.*", "foo.bar.*")` +to `includes.addAll("com.example.*", "foo.bar.*")` +Solution for Groovy: change `excludes = ["com.example.*", "foo.bar.*"]` +to `includes.addAll("com.example.*", "foo.bar.*")` + +## "koverXmlReport" and "koverMergedXmlReport" tasks configuration + +###property `xmlReportFile` was removed +Solution: use property in Kover extension at the root of the project + +``` +kover { + xmlReport { + reportFile.set(yourFile) + } +} +``` +* for `xmlReportFile` task use `koverMerged { ... }` extension of the project. + +### property "includes" was removed + +Solution for Kotlin: use filter in Kover extension at the root of the project + +``` +kover { + filters { + classes { + includes += listOf("foo.bar.*", "foo.biz.*") + } + } +} +``` + +Solution for Groovy: use filter in Kover extension at the root of the project + +``` +kover { + filters { + classes { + includes.addAll("foo.bar.*", "foo.biz.*") + } + } +} +``` +* for `xmlReportFile` task use `koverMerged { ... }` extension of the project. + +### property "excludes" was removed + +Solution for Kotlin: use filter in Kover extension at the root of the project + +``` +kover { + filters { + classes { + excludes += listOf("foo.bar.*", "foo.biz.*") + } + } +} +``` + +Solution for Groovy: use filter in Kover extension at the root of the project + +``` +kover { + filters { + classes { + excludes.addAll("foo.bar.*", "foo.biz.*") + } + } +} +``` +* for `xmlReportFile` task use `koverMerged { ... }` extension of the project. + +## "koverHtmlReport" and "koverMergedHtmlReport" tasks configuration + +### property "htmlReportDir" was removed + +Solution: use property in Kover extension at the root of the project + +``` +kover { + htmlReport { + reportDir.set(yourDir) + } +} +``` +* for `koverMergedHtmlReport` task use `koverMerged { ... }` extension of the project. + +### property "includes" was removed + +Solution for Kotlin: use filter in Kover extension at the root of the project + +``` +kover { + filters { + classes { + includes += listOf("foo.bar.*", "foo.biz.*") + } + } +} +``` + +Solution for Groovy: use filter in Kover extension at the root of the project + +``` +kover { + filters { + classes { + includes.addAll("foo.bar.*", "foo.biz.*") + } + } +} +``` +* for `koverMergedHtmlReport` task use `koverMerged { ... }` extension of the project. + +### property "excludes" was removed + +Solution for Kotlin: use filter in Kover extension at the root of the project + +``` +kover { + filters { + classes { + excludes += listOf("foo.bar.*", "foo.biz.*") + } + } +} +``` + +Solution for Groovy: use filter in Kover extension at the root of the project + +``` +kover { + filters { + classes { + excludes.addAll("foo.bar.*", "foo.biz.*") + } + } +} +``` +* for `koverMergedHtmlReport` task use `koverMerged { ... }` extension of the project. + +## "koverVerify" and "koverMergedVerify" tasks configuration + +### function "rule" was removed + +Solution: use function in Kover extension at the root of the project + +``` +kover { + verify { + rule { + // your verification rule + } + } +} +``` +* for `koverMergedVerify` task use `koverMerged { ... }` extension of the project. + +### property "includes" was removed + +Solution for Kotlin: use filter in Kover extension at the root of the project + +``` +kover { + filters { + classes { + includes += listOf("foo.bar.*", "foo.biz.*") + } + } +} +``` + +Solution for Groovy: use filter in Kover extension at the root of the project + +``` +kover { + filters { + classes { + includes.addAll("foo.bar.*", "foo.biz.*") + } + } +} +``` +* for `koverMergedVerify` task use `koverMerged { ... }` extension of the project. + +### property "excludes" was removed + +Solution for Kotlin: use filter in Kover extension at the root of the project + +``` +kover { + filters { + classes { + excludes += listOf("foo.bar.*", "foo.biz.*") + } + } +} +``` + +Solution for Groovy: use filter in Kover extension at the root of the project + +``` +kover { + filters { + classes { + excludes.addAll("foo.bar.*", "foo.biz.*") + } + } +} +``` +* for `koverMergedVerify` task use `koverMerged { ... }` extension of the project. + diff --git a/src/main/kotlin/kotlinx/kover/api/KoverConfig.kt b/src/main/kotlin/kotlinx/kover/api/KoverConfig.kt index 93f4e322..5f618a4a 100644 --- a/src/main/kotlin/kotlinx/kover/api/KoverConfig.kt +++ b/src/main/kotlin/kotlinx/kover/api/KoverConfig.kt @@ -71,8 +71,72 @@ public open class KoverProjectConfig @Inject constructor(objects: ObjectFactory) public fun verify(config: Action) { config.execute(verify) } + + + // DEPRECATIONS + // TODO delete in 0.7 version + @get:Internal + @Deprecated( + message = "Property was removed in Kover API version 2. Please read migration to 0.6.0 guide to solve the issue", + replaceWith = ReplaceWith("engine"), + level = DeprecationLevel.ERROR + ) + public val coverageEngine: Property = objects.property(CoverageEngineVendor::class.java) + + @get:Internal + @Deprecated( + message = "Property was removed in Kover API version 2. Please read migration to 0.6.0 guide to solve the issue", + replaceWith = ReplaceWith("engine"), + level = DeprecationLevel.ERROR + ) + public val intellijEngineVersion: Property = objects.property(String::class.java) + + @get:Internal + @Deprecated( + message = "Property was removed in Kover API version 2. Please read migration to 0.6.0 guide to solve the issue", + replaceWith = ReplaceWith("engine"), + level = DeprecationLevel.ERROR + ) + public val jacocoEngineVersion: Property = objects.property(String::class.java) + + @get:Internal + @Deprecated( + message = "Property was removed in Kover API version 2. Please read migration to 0.6.0 guide to solve the issue", + level = DeprecationLevel.ERROR + ) + public var generateReportOnCheck: Boolean = true + + @get:Internal + @Deprecated( + message = "Property was removed in Kover API version 2. Please read migration to 0.6.0 guide to solve the issue", + level = DeprecationLevel.ERROR + ) + public var disabledProjects: Set = emptySet() + + @Deprecated( + message = "Property was removed in Kover API version 2. Please read migration to 0.6.0 guide to solve the issue", + level = DeprecationLevel.ERROR + ) + @get:Internal + public var instrumentAndroidPackage: Boolean = false + + @Deprecated( + message = "Property was removed in Kover API version 2. Please read migration to 0.6.0 guide to solve the issue", + level = DeprecationLevel.ERROR + ) + @get:Internal + public var runAllTestsForProjectTask: Boolean = false } +// DEPRECATIONS +// TODO delete in 0.7 version +@Deprecated( + message = "Class was renamed in Kover API version 2", + replaceWith = ReplaceWith("KoverProjectConfig"), + level = DeprecationLevel.ERROR +) +public open class KoverExtension() + public open class KoverProjectFilters @Inject constructor(private val objects: ObjectFactory) { internal val classes: Property = objects.property(KoverClassFilter::class.java) diff --git a/src/main/kotlin/kotlinx/kover/api/KoverTaskExtension.kt b/src/main/kotlin/kotlinx/kover/api/KoverTaskExtension.kt index 049b391a..e6e2250d 100644 --- a/src/main/kotlin/kotlinx/kover/api/KoverTaskExtension.kt +++ b/src/main/kotlin/kotlinx/kover/api/KoverTaskExtension.kt @@ -55,7 +55,7 @@ public open class KoverTaskExtension(objects: ObjectFactory) { // DEPRECATIONS - // TODO delete in 0.9 version + // TODO delete in 0.7 version @get:Internal @Deprecated( message = "Property was renamed in Kover API version 2", diff --git a/src/main/kotlin/kotlinx/kover/tasks/KoverHtmlTask.kt b/src/main/kotlin/kotlinx/kover/tasks/KoverHtmlTask.kt index 1ead39c3..48345147 100644 --- a/src/main/kotlin/kotlinx/kover/tasks/KoverHtmlTask.kt +++ b/src/main/kotlin/kotlinx/kover/tasks/KoverHtmlTask.kt @@ -8,8 +8,9 @@ import kotlinx.kover.engines.commons.* import org.gradle.api.file.* import org.gradle.api.tasks.* +// TODO make internal in 0.7 version @CacheableTask -internal open class KoverHtmlTask : KoverReportTask() { +public open class KoverHtmlTask : KoverReportTask() { @get:OutputDirectory internal val reportDir: DirectoryProperty = project.objects.directoryProperty() @@ -34,4 +35,28 @@ internal open class KoverHtmlTask : KoverReportTask() { logger.lifecycle("Kover: HTML report for '$projectPath' file://${reportDirFile.canonicalPath}/index.html") } } + + + // DEPRECATIONS + // TODO delete in 0.7 version + @get:Internal + @Deprecated( + message = "Property was removed in Kover API version 2. Please read migration to 0.6.0 guide to solve the issue", + level = DeprecationLevel.ERROR + ) + val htmlReportDir: DirectoryProperty = project.objects.directoryProperty() + + @get:Internal + @Deprecated( + message = "Property was removed in Kover API version 2. Please read migration to 0.6.0 guide to solve the issue", + level = DeprecationLevel.ERROR + ) + public var includes: List = emptyList() + + @get:Internal + @Deprecated( + message = "Property was removed in Kover API version 2. Please read migration to 0.6.0 guide to solve the issue", + level = DeprecationLevel.ERROR + ) + public var excludes: List = emptyList() } diff --git a/src/main/kotlin/kotlinx/kover/tasks/KoverReportTask.kt b/src/main/kotlin/kotlinx/kover/tasks/KoverReportTask.kt index 48718a2e..ccc58868 100644 --- a/src/main/kotlin/kotlinx/kover/tasks/KoverReportTask.kt +++ b/src/main/kotlin/kotlinx/kover/tasks/KoverReportTask.kt @@ -13,7 +13,8 @@ import org.gradle.configurationcache.extensions.* import org.gradle.process.* import java.io.* -internal abstract class KoverReportTask : DefaultTask() { +// TODO make internal in 0.7 version +public abstract class KoverReportTask : DefaultTask() { @get:Nested internal val files: MapProperty = project.objects.mapProperty(String::class.java, ProjectFiles::class.java) diff --git a/src/main/kotlin/kotlinx/kover/tasks/KoverVerificationTask.kt b/src/main/kotlin/kotlinx/kover/tasks/KoverVerificationTask.kt index 039c167d..4f9340cc 100644 --- a/src/main/kotlin/kotlinx/kover/tasks/KoverVerificationTask.kt +++ b/src/main/kotlin/kotlinx/kover/tasks/KoverVerificationTask.kt @@ -7,8 +7,9 @@ import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.ListProperty import org.gradle.api.tasks.* +// TODO make internal in 0.7 version @CacheableTask -internal open class KoverVerificationTask : KoverReportTask() { +public open class KoverVerificationTask : KoverReportTask() { @get:Nested internal val rules: ListProperty = project.objects.listProperty(VerificationRule::class.java) @@ -54,5 +55,30 @@ internal open class KoverVerificationTask : KoverReportTask() { return result } + + // DEPRECATIONS + // TODO delete in 0.7 version + @Suppress("UNUSED_PARAMETER") + @Deprecated( + message = "Function was removed in Kover API version 2. Please read migration to 0.6.0 guide to solve the issue", + level = DeprecationLevel.ERROR + ) + public fun rule(configureRule: Action) { + throw Exception("Function was removed in Kover API version 2. Please read migration to 0.6.0 guide to solve the issue") + } + + @get:Internal + @Deprecated( + message = "Property was removed in Kover API version 2. Please read migration to 0.6.0 guide to solve the issue", + level = DeprecationLevel.ERROR + ) + public var includes: List = emptyList() + + @get:Internal + @Deprecated( + message = "Property was removed in Kover API version 2. Please read migration to 0.6.0 guide to solve the issue", + level = DeprecationLevel.ERROR + ) + public var excludes: List = emptyList() } diff --git a/src/main/kotlin/kotlinx/kover/tasks/KoverXmlTask.kt b/src/main/kotlin/kotlinx/kover/tasks/KoverXmlTask.kt index f471c83d..cf3709c1 100644 --- a/src/main/kotlin/kotlinx/kover/tasks/KoverXmlTask.kt +++ b/src/main/kotlin/kotlinx/kover/tasks/KoverXmlTask.kt @@ -4,8 +4,9 @@ import kotlinx.kover.engines.commons.* import org.gradle.api.file.* import org.gradle.api.tasks.* +// TODO make internal in 0.7 version @CacheableTask -internal open class KoverXmlTask : KoverReportTask() { +public open class KoverXmlTask : KoverReportTask() { @get:OutputFile internal val reportFile: RegularFileProperty = project.objects.fileProperty() @@ -24,4 +25,27 @@ internal open class KoverXmlTask : KoverReportTask() { ) } + + // DEPRECATIONS + // TODO delete in 0.7 version + @get:Internal + @Deprecated( + message = "Property was removed in Kover API version 2. Please read migration to 0.6.0 guide to solve the issue", + level = DeprecationLevel.ERROR + ) + public val xmlReportFile: RegularFileProperty = project.objects.fileProperty() + + @get:Internal + @Deprecated( + message = "Property was removed in Kover API version 2. Please read migration to 0.6.0 guide to solve the issue", + level = DeprecationLevel.ERROR + ) + public var includes: List = emptyList() + + @get:Internal + @Deprecated( + message = "Property was removed in Kover API version 2. Please read migration to 0.6.0 guide to solve the issue", + level = DeprecationLevel.ERROR + ) + public var excludes: List = emptyList() } From 8e84805745dfbd008ad05e79b59c977d1fd18655 Mon Sep 17 00:00:00 2001 From: "Sergey.Shanshin" Date: Tue, 28 Jun 2022 17:11:15 +0300 Subject: [PATCH 04/10] Overwritten the README.md file --- README.md | 534 ++++++++++-------- docs/migration-to-0.6.0.md | 12 +- .../kotlin/kotlinx/kover/api/KoverConfig.kt | 4 +- 3 files changed, 300 insertions(+), 250 deletions(-) diff --git a/README.md b/README.md index c53c9c04..fdfc3044 100644 --- a/README.md +++ b/README.md @@ -12,17 +12,16 @@ Minimal supported `Gradle` version: `6.6`. ## Table of contents - [Features](#features) - [Quickstart](#quickstart) - - [Apply plugin to a single-project build](#apply-plugin-to-a-single-project-build) + - [Apply plugin to a project](#apply-plugin) - [Applying plugins with the plugins DSL](#applying-plugins-with-the-plugins-dsl) - [Legacy Plugin Application: applying plugins with the buildscript block](#legacy-plugin-application-applying-plugins-with-the-buildscript-block) - - [Apply plugin to a multi-project build](#apply-plugin-to-a-multi-project-build) + - [Merged reports](#merged-reports) - [Configuration](#configuration) - - [Configuring JVM test task](#configuring-jvm-test-task) + - [Configuring project](#configuring-project) - [Configuring merged reports](#configuring-merged-reports) - - [Configuring project reports](#configuring-project-reports) - - [Configuring entire plugin](#configuring-entire-plugin) -- [Verification](#verification) -- [Tasks](#tasks) + - [Configuring JVM test task](#configuring-jvm-test-task) + - [Specifying Coverage Engine](#specifying-coverage-engine) +- [Tasks](#kover-default-tasks) - [Implicit plugin dependencies](#implicit-plugin-dependencies) ## Features @@ -34,7 +33,7 @@ Minimal supported `Gradle` version: `6.6`. * Customizable filters for instrumented classes ## Quickstart -### Apply plugin to a single-project build +### Apply plugin #### Applying plugins with the plugins DSL In top-level build file: @@ -43,7 +42,7 @@ In top-level build file: ```kotlin plugins { - id("org.jetbrains.kotlinx.kover") version "0.5.0" + id("org.jetbrains.kotlinx.kover") version "0.6.0-RC" } ``` @@ -53,7 +52,7 @@ plugins { ```groovy plugins { - id 'org.jetbrains.kotlinx.kover' version '0.5.0' + id 'org.jetbrains.kotlinx.kover' version '0.6.0-RC' } ``` @@ -71,7 +70,7 @@ buildscript { } dependencies { - classpath("org.jetbrains.kotlinx:kover:0.5.0") + classpath("org.jetbrains.kotlinx:kover:0.6.0-RC") } } @@ -88,7 +87,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'org.jetbrains.kotlinx:kover:0.5.0' + classpath 'org.jetbrains.kotlinx:kover:0.6.0-RC' } } @@ -96,9 +95,14 @@ apply plugin: 'kover' ``` -### Apply plugin to a multi-project build -To apply the plugin to all Gradle projects, you only need to apply the plugin to the top-level build file as shown [above](#apply-plugin-to-a-single-project-build). -Applying the plugin to subprojects if you have already applied it to the root project will cause configuration errors. +### Merged reports +Merged reports are reports that combine statistics of code coverage by test tasks from several projects. + +At the same time, for each project, its configuration of instrumentation and special filters (non-class filters) is applied. + +In all projects used for merged reports, a Kover plugin must be applied, as well as all Coverage Engines must match. + +See how to enable merge reports in [this section](#configuring-merged-reports). ## Configuration @@ -108,7 +112,9 @@ However, in some cases, custom settings are needed - this can be done by configu ### Configuring JVM test task -If you need to disable or filter instrumentation for a test task, you may configure the Kover extension for it. +In rare cases, you may need to disable instrumentation for certain classes if it causes execution errors. +It may also be convenient to ignore the test task when calculating coverage. +You may configure the Kover extension for it. For example, to configure a standard test task for Kotlin/JVM named `test`, you need to add the following code to the build script of the project where this task is declared: @@ -118,10 +124,10 @@ For example, to configure a standard test task for Kotlin/JVM named `test`, you ```kotlin tasks.test { extensions.configure(kotlinx.kover.api.KoverTaskExtension::class) { - isDisabled = false - binaryReportFile.set(file("$buildDir/custom/result.bin")) - includes = listOf("com.example.*") - excludes = listOf("com.example.subpackage.*") + isDisabled.set(false) // true to disable instrumentation tests of this task, Kover reports will not depend on the results of their execution + binaryReportFile.set(file("$buildDir/custom/result.bin")) // set file name of binary report + includes = listOf("com.example.*") // see "Instrumentation inclusion rules" below + excludes = listOf("com.example.subpackage.*") // see "Instrumentation exclusion rules" below } } ``` @@ -133,16 +139,16 @@ tasks.test { ```groovy tasks.test { kover { - disabled = false - binaryReportFile.set(file("$buildDir/custom/result.bin")) - includes = ['com.example.*'] - excludes = ['com.example.subpackage.*'] + disabled = false // true to disable instrumentation tests of this task, Kover reports will not depend on the results of their execution + binaryReportFile.set(file("$buildDir/custom/result.bin")) // set file name of binary report + includes = ['com.example.*'] // see "Instrumentation inclusion rules" below + excludes = ['com.example.subpackage.*'] // see "Instrumentation exclusion rules" below } } ``` -**For other platforms (Android, Kotlin-Multiplatform) the names may differ and you may also have several test tasks, so you first need to determine the name of the required task.** +**For other platforms (Android, Kotlin-Multiplatform) the names may differ, and you may also have several test tasks, so you first need to determine the name of the required task.** Example of configuring test task for build type `debug` in Android:
@@ -156,10 +162,10 @@ android { unitTests.all { if (it.name == "testDebugUnitTest") { it.extensions.configure(kotlinx.kover.api.KoverTaskExtension::class) { - isDisabled = false - binaryReportFile.set(file("$buildDir/custom/debug-report.bin")) - includes = listOf("com.example.*") - excludes = listOf("com.example.subpackage.*") + isDisabled = false // true to disable instrumentation tests of this task, Kover reports will not depend on the results of their execution + binaryReportFile.set(file("$buildDir/custom/debug-report.bin")) // set file name of binary report + includes = listOf("com.example.*") // see "Instrumentation inclusion rules" below + excludes = listOf("com.example.subpackage.*") // see "Instrumentation exclusion rules" below } } } @@ -180,10 +186,10 @@ android { unitTests.all { if (name == "testDebugUnitTest") { kover { - disabled = false - binaryReportFile.set(file("$buildDir/custom/debug-report.bin")) - includes = ['com.example.*'] - excludes = ['com.example.subpackage.*'] + disabled = false // true to disable instrumentation tests of this task, Kover reports will not depend on the results of their execution + binaryReportFile.set(file("$buildDir/custom/debug-report.bin")) // set file name of binary report + includes = ['com.example.*'] // see "Instrumentation inclusion rules" below + excludes = ['com.example.subpackage.*'] // see "Instrumentation exclusion rules" below } } } @@ -193,120 +199,86 @@ android {
-### Configuring merged reports -Merged reports combine classpath and coverage stats from the project in which the plugin is applied and all of its subprojects. - -If you need to change the name of the XML report file or HTML directory, you may configure the corresponding tasks in -the project in which the plugin is applied (usually this is the root project): - -
-Kotlin - -```kotlin -tasks.koverMergedHtmlReport { - isEnabled = true // false to disable report generation - htmlReportDir.set(layout.buildDirectory.dir("my-merged-report/html-result")) - - includes = listOf("com.example.*") // inclusion rules for classes - excludes = listOf("com.example.subpackage.*") // exclusion rules for classes -} +**Instrumentation inclusion rules** -tasks.koverMergedXmlReport { - isEnabled = true // false to disable report generation - xmlReportFile.set(layout.buildDirectory.file("my-merged-report/result.xml")) - - includes = listOf("com.example.*") // inclusion rules for classes - excludes = listOf("com.example.subpackage.*") // exclusion rules for classes -} -``` -
+Only the specified classes may be instrumented, the remaining classes will still be present in the report, but for them the coverage will be zero. -
-Groovy +**Instrumentation exclusion rules** -```groovy -tasks.koverMergedHtmlReport { - enabled = true // false to disable report generation - htmlReportDir.set(layout.buildDirectory.dir("my-merged-report/html-result")) +The specified classes will not be instrumented and there will be zero coverage for them. - includes = ['com.example.*'] // inclusion rules for classes - excludes = ['com.example.subpackage.*'] // exclusion rules for classes -} +Instrumentation inclusion/exclusion rule represented as a fully-qualified name of the class (several classes if wildcards are used). +File or directory names are not allowed. +It's possible to use `*` (zero or several of any chars) and `?` (one any char) wildcards. Wildcard `**` is similar to the `*`. -tasks.koverMergedXmlReport { - enabled = true // false to disable report generation - xmlReportFile.set(layout.buildDirectory.file("my-merged-report/result.xml")) +Examples `my.package.ClassName` or `my.*.*Name` but not the `my/package/ClassName.kt` or `src/my.**.ClassName` - includes = ['com.example.*'] // inclusion rules for classes - excludes = ['com.example.subpackage.*'] // exclusion rules for classes -} -``` -
+Exclusion rules have priority over inclusion ones. -### Configuring project reports -If you need to change the name of the XML report file or HTML directory for a specific project, you may configure -the corresponding tasks in this project: +### Configuring project +In the project in which the plugin is applied, you can configure instrumentation and default Kover tasks:
Kotlin ```kotlin -tasks.koverHtmlReport { - isEnabled = true // false to disable report generation - htmlReportDir.set(layout.buildDirectory.dir("my-project-report/html-result")) - - includes = listOf("com.example.*") // inclusion rules for classes - excludes = listOf("com.example.subpackage.*") // exclusion rules for classes -} - -tasks.koverXmlReport { - isEnabled = true // false to disable report generation - xmlReportFile.set(layout.buildDirectory.file("my-project-report/result.xml")) - - includes = listOf("com.example.*") // inclusion rules for classes - excludes = listOf("com.example.subpackage.*") // exclusion rules for classes -} -``` -
- -
-Groovy - -```groovy -tasks.koverHtmlReport { - enabled = true // false to disable report generation - htmlReportDir.set(layout.buildDirectory.dir("my-project-report/html-result")) - - includes = ['com.example.*'] // inclusion rules for classes - excludes = ['com.example.subpackage.*'] // exclusion rules for classes -} - -tasks.koverXmlReport { - enabled = true // false to disable report generation - xmlReportFile.set(layout.buildDirectory.file("my-project-report/result.xml")) - includes = ['com.example.*'] // inclusion rules for classes - excludes = ['com.example.subpackage.*'] // exclusion rules for classes -} -``` -
- -By default, for tasks `koverHtmlReport` and `koverXmlReport` coverage is calculated only for the tests of the one project. -If classes or functions are called from tests of another module, then you need to set a flag `runAllTestsForProjectTask` for `KoverExtension` to `true` ([see](#configuring-entire-plugin)). +kover { + isDisabled.set(false) // true to disable instrumentation and all Kover tasks in this project + engine.set(DefaultIntellijEngine) // change Coverage Engine + filters { // common filters for all default Kover tasks + classes { // common class filter for all default Kover tasks + includes += "com.example.*" // class inclusion rules + excludes += listOf("com.example.subpackage.*") // class exclusion rules + } + } -**In this case, then running tasks `koverHtmlReport` or `koverXmlReport` will trigger the execution of all active tests from all projects!** + instrumentation { + excludeTasks += "dummy-tests" // set of test tasks names to exclude from instrumentation. The results of their execution will not be presented in the report + } + xmlReport { + onCheck.set(false) // true to run koverXmlReport task during the execution of the check task + reportFile.set(layout.buildDirectory.file("my-project-report/result.xml")) // change report file name + overrideFilters { + classes { // override common class filter + includes += "com.example2.*" // override class inclusion rules + excludes += listOf("com.example2.subpackage.*") // override class exclusion rules + } + } + } -You may collect all project reports into one directory using the `koverCollectReports` task. -Also, you may specify a custom directory to collect project reports in the build directory of the project in which the plugin -is applied (usually this is the root project): + htmlReport { + onCheck.set(false) // true to run koverHtmlReport task during the execution of the check task + reportDir.set(layout.buildDirectory.dir("my-project-report/html-result")) // change report directory + overrideFilters { + classes { // override common class filter + includes += "com.example2.*" // class inclusion rules + excludes += listOf("com.example2.subpackage.*") // override class exclusion rules + } + } + } -
-Kotlin + verify { + onCheck.set(true) // true to run koverVerify task during the execution of the check task + rule { // add verification rule + isEnabled = true // false to disable rule checking + name = null // custom name for the rule + target = kotlinx.kover.api.VerificationTarget.ALL // specify by which entity the code for separate coverage evaluation will be grouped + + overrideClassFilter { // override common class filter + includes += "com.example.verify.*" // override class inclusion rules + excludes += listOf("com.example.verify.subpackage.*") // override class exclusion rules + } -```kotlin -tasks.koverCollectReports { - outputDir.set(layout.buildDirectory.dir("all-projects-reports") ) + bound { // add rule bound + minValue = 10 + maxValue = 20 + counter = kotlinx.kover.api.CounterType.LINE // change coverage metric to evaluate (LINE, INSTRUCTION, BRANCH) + valueType = kotlinx.kover.api.VerificationValueType.COVERED_PERCENTAGE // change counter value (COVERED_COUNT, MISSED_COUNT, COVERED_PERCENTAGE, MISSED_PERCENTAGE) + } + } + } } ```
@@ -315,89 +287,136 @@ tasks.koverCollectReports { Groovy ```groovy -tasks.koverCollectReports { - outputDir.set(layout.buildDirectory.dir("all-projects-reports") ) -} -``` - +kover { + isDisabled.set(false) // true to disable instrumentation and all Kover tasks in this project + engine = kotlinx.kover.api.DefaultIntellijEngine.INSTANCE // change Coverage Engine + filters { // common filters for all default Kover tasks + classes { // common class filter for all default Kover tasks + includes.add("com.example.*") // class inclusion rules + excludes.addAll("com.example.subpackage.*") // class exclusion rules + } + } -### Configuring entire plugin -In the project in which the plugin is applied, you can configure the following properties: + instrumentation { + excludeTasks.add("dummy-tests") // set of test tasks names to exclude from instrumentation. The results of their execution will not be presented in the report + } -
-Kotlin + xmlReport { + onCheck.set(false) // true to run koverXmlReport task during the execution of the check task + reportFile.set(layout.buildDirectory.file("my-project-report/result.xml")) // change report file name + overrideFilters { + classes { // override common class filter + includes.add("com.example2.*") // override class inclusion rules + excludes.addAll("com.example2.subpackage.*") // override class exclusion rules + } + } + } -```kotlin -kover { - isDisabled = false // true to disable instrumentation of all test tasks in all projects - coverageEngine.set(kotlinx.kover.api.CoverageEngine.INTELLIJ) // change instrumentation agent and reporter - intellijEngineVersion.set("1.0.656") // change version of IntelliJ agent and reporter - jacocoEngineVersion.set("0.8.7") // change version of JaCoCo agent and reporter - generateReportOnCheck = true // false to do not execute `koverMergedReport` task before `check` task - disabledProjects = setOf() // setOf("project-name") or setOf(":project-name") to disable coverage for project with path `:project-name` (`:` for the root project) - instrumentAndroidPackage = false // true to instrument packages `android.*` and `com.android.*` - runAllTestsForProjectTask = false // true to run all tests in all projects if `koverHtmlReport`, `koverXmlReport`, `koverReport`, `koverVerify` or `check` tasks executed on some project + htmlReport { + onCheck.set(false) // true to run koverHtmlReport task during the execution of the check task + reportDir.set(layout.buildDirectory.dir("my-project-report/html-result")) // change report directory + overrideFilters { + classes { // override common class filter + includes.add("com.example2.*") // class inclusion rules + excludes.addAll("com.example2.subpackage.*") // override class exclusion rules + } + } + } + + verify { + onCheck.set(true) // true to run koverVerify task during the execution of the check task + rule { // add verification rule + enabled = true // false to disable rule checking + name = null // custom name for the rule + target = 'ALL' // specify by which entity the code for separate coverage evaluation will be grouped + + overrideClassFilter { // override common class filter + includes.add("com.example.verify.*") // override class inclusion rules + excludes.addAll("com.example.verify.subpackage.*") // override class exclusion rules + } + + bound { // add rule bound + minValue = 10 + maxValue = 20 + counter = 'LINE' // change coverage metric to evaluate (LINE, INSTRUCTION, BRANCH) + valueType = 'COVERED_PERCENTAGE' // change counter value (COVERED_COUNT, MISSED_COUNT, COVERED_PERCENTAGE, MISSED_PERCENTAGE) + } + } + } } ```
-
-Groovy +How to specify the engine version, see the [section](#specifying-coverage-engine) -```groovy -kover { - disabled = false // true to disable instrumentation of all test tasks in all projects - coverageEngine.set(kotlinx.kover.api.CoverageEngine.INTELLIJ) // change instrumentation agent and reporter - intellijEngineVersion.set('1.0.656') // change version of IntelliJ agent and reporter - jacocoEngineVersion.set('0.8.7') // change version of JaCoCo agent and reporter - generateReportOnCheck = true // false to do not execute `koverMergedReport` task before `check` task - disabledProjects = [] // ["project-name"] or [":project-name"] to disable coverage for project with path `:project-name` (`:` for the root project) - instrumentAndroidPackage = false // true to instrument packages `android.*` and `com.android.*` - runAllTestsForProjectTask = false // true to run all tests in all projects if `koverHtmlReport`, `koverXmlReport`, `koverReport`, `koverVerify` or `check` tasks executed on some project +### Configuring merged reports +To create default merged reports, you need to write `koverMerged.enable()` or +``` +koverMerged { + enable() } ``` -
+in the containing project. By default, the merged reports will include this project along with all its subprojects. -## Verification -You may specify one or more rules that check the values of the code coverage counters. -Validation rules work for both types of agents. - -*The plugin currently only supports line counter values.* - - -To add a rule to check coverage of the code of all projects, you need to add configuration to the project in which the plugin -is applied (usually this is the root project): +Merged reports can be configured. To do this, you need to configure the extension in the containing project (where `koverMerged.enable()` is called)
Kotlin ```kotlin -tasks.koverMergedVerify { - includes = listOf("com.example.*") // inclusion rules for classes - excludes = listOf("com.example.subpackage.*") // exclusion rules for classes +koverMerged { + enable() // create Kover merged reports - rule { - name = "Minimum number of lines covered" - bound { - minValue = 100000 - valueType = kotlinx.kover.api.VerificationValueType.COVERED_LINES_COUNT + filters { // common filters for all default Kover merged tasks + classes { // common class filter for all default Kover merged tasks + includes += "com.example.*" // class inclusion rules + excludes += listOf("com.example.subpackage.*") // class exclusion rules + } + + projects { // common projects filter for all default Kover merged tasks + includes += listOf("project1", ":child:project") // Specifies the projects involved in the merged tasks } } - rule { - // rule without a custom name - bound { - minValue = 1 - maxValue = 1000 - valueType = kotlinx.kover.api.VerificationValueType.MISSED_LINES_COUNT + + + xmlReport { + onCheck.set(false) // true to run koverMergedXmlReport task during the execution of the check task + reportFile.set(layout.buildDirectory.file("my-merged-report/result.xml")) // change report file name + overrideClassFilter { // override common class filter + includes += "com.example2.*" // override class inclusion rules + excludes += listOf("com.example2.subpackage.*") // override class exclusion rules } } - rule { - name = "Minimal line coverage rate in percent" - bound { - minValue = 50 - // valueType is kotlinx.kover.api.VerificationValueType.COVERED_LINES_PERCENTAGE by default - } + + htmlReport { + onCheck.set(false) // true to run koverMergedHtmlReport task during the execution of the check task + reportDir.set(layout.buildDirectory.dir("my-merged-report/html-result")) // change report directory + overrideClassFilter { // override common class filter + includes += "com.example2.*" // override class inclusion rules + excludes += listOf("com.example2.subpackage.*") // override class exclusion rules + } + } + + verify { + onCheck.set(true) // true to run koverMergedVerify task during the execution of the check task + rule { // add verification rule + isEnabled = true // false to disable rule checking + name = null // custom name for the rule + target = kotlinx.kover.api.VerificationTarget.ALL // specify by which entity the code for separate coverage evaluation will be grouped + + overrideClassFilter { // override common class filter + includes += "com.example.verify.*" // override class inclusion rules + excludes += listOf("com.example.verify.subpackage.*") // override class exclusion rules + } + + bound { // add rule bound + minValue = 10 + maxValue = 20 + counter = kotlinx.kover.api.CounterType.LINE // change coverage metric to evaluate (LINE, INSTRUCTION, BRANCH) + valueType = kotlinx.kover.api.VerificationValueType.COVERED_PERCENTAGE // change counter value (COVERED_COUNT, MISSED_COUNT, COVERED_PERCENTAGE, MISSED_PERCENTAGE) + } + } } } ``` @@ -407,53 +426,71 @@ tasks.koverMergedVerify { Groovy ```groovy -tasks.koverMergedVerify { - includes = ['com.example.*'] // inclusion rules for classes - excludes = ['com.example.subpackage.*'] // exclusion rules for classes +koverMerged { + enable() // create Kover merged reports + + filters { // common filters for all default Kover merged tasks + classes { // common class filter for all default Kover merged tasks + includes.add("com.example.*") // class inclusion rules + excludes.addAll("com.example.subpackage.*") // class exclusion rules + } + + projects { // common projects filter for all default Kover merged tasks + includes.addAll("project1", ":child:project") // Specifies the projects involved in the merged tasks + } + } + - rule { - name = "Minimum number of lines covered" - bound { - minValue = 100000 - valueType = 'COVERED_LINES_COUNT' + xmlReport { + onCheck.set(false) // true to run koverMergedXmlReport task during the execution of the check task + reportFile.set(layout.buildDirectory.file("my-merged-report/result.xml")) // change report file name + overrideClassFilter { // override common class filter + includes.add("com.example2.*") // override class inclusion rules + excludes.addAll("com.example2.subpackage.*") // override class exclusion rules } } - rule { - // rule without a custom name - bound { - minValue = 1 - maxValue = 1000 - valueType = 'MISSED_LINES_COUNT' + + htmlReport { + onCheck.set(false) // true to run koverMergedHtmlReport task during the execution of the check task + reportDir.set(layout.buildDirectory.dir("my-merged-report/html-result")) // change report directory + overrideClassFilter { // override common class filter + includes.add("com.example2.*") // override class inclusion rules + excludes.addAll("com.example2.subpackage.*") // override class exclusion rules } } - rule { - name = "Minimal line coverage rate in percent" - bound { - minValue = 50 - // valueType is 'COVERED_LINES_PERCENTAGE' by default + + verify { + onCheck.set(true) // true to run koverMergedVerify task during the execution of the check task + rule { // add verification rule + isEnabled = true // false to disable rule checking + name = null // custom name for the rule + target = 'ALL' // specify by which entity the code for separate coverage evaluation will be grouped + + overrideClassFilter { // override common class filter + includes.add("com.example.verify.*") // override class inclusion rules + excludes.addAll("com.example.verify.subpackage.*") // override class exclusion rules + } + + bound { // add rule bound + minValue = 10 + maxValue = 20 + counter = 'LINE' // change coverage metric to evaluate (LINE, INSTRUCTION, BRANCH) + valueType = 'COVERED_PERCENTAGE' // change counter value (COVERED_COUNT, MISSED_COUNT, COVERED_PERCENTAGE, MISSED_PERCENTAGE) + } } } } ```
-To add rules for code coverage checks for the code of one specific project, you need to add a configuration to this project: +### Specifying Coverage Engine +#### IntelliJ Coverage Engine with default version
Kotlin ```kotlin -tasks.koverVerify { - includes = listOf("com.example.*") // inclusion rules for classes - excludes = listOf("com.example.subpackage.*") // exclusion rules for classes - - rule { - name = "Minimal line coverage rate in percent" - bound { - minValue = 75 - } - } -} +kotlinx.kover.api.DefaultIntellijEngine ```
@@ -461,39 +498,50 @@ tasks.koverVerify { Groovy ```groovy -tasks.koverVerify { - includes = ['com.example.*'] // inclusion rules for classes - excludes = ['com.example.subpackage.*'] // exclusion rules for classes - - rule { - name = "Minimal line coverage rate in percent" - bound { - minValue = 75 - } - } -} +kotlinx.kover.api.DefaultIntellijEngine.INSTANCE ``` -By default, for the task `koverVerify` coverage is calculated only for the tests of the one project. -If classes or functions are called from tests of another module, then you need to set a flag `runAllTestsForProjectTask` for `KoverExtension` to `true` ([see](#configuring-entire-plugin)). +#### IntelliJ Coverage Engine with custom version +``` +kotlinx.kover.api.IntellijEngine("1.0.668") +``` -**In this case, if verification rules are added, then running tasks `koverVerify` or `check` will trigger the execution of all active tests from all projects!** +#### JaCoCo Coverage Engine with default version +
+Kotlin -## Tasks -The plugin, when applied, automatically creates tasks for the project in which it is applied (usually this is the root project): -- `koverMergedHtmlReport` - Generates code coverage HTML report for all enabled test tasks in all projects. -- `koverMergedXmlReport` - Generates code coverage XML report for all enabled test tasks in all projects. -- `koverMergedReport` - Executes both `koverMergedXmlReport` and `koverMergedHtmlReport` tasks. Executes before `check` task if property `generateReportOnCheck` for `KoverExtension` is `true` ([see](#configuring-entire-plugin)). -- `koverMergedVerify` - Verifies code coverage metrics of all projects based on specified rules. Always executes before `check` task. -- `koverCollectReports` - Collects all projects reports into one directory. Default directory is `$buildDir/reports/kover/projects`, names for XML reports and dirs for HTML are projects names. Executing this task does not run `koverMergedXmlReport` or `koverMergedHtmlReport`, it only copies previously created reports if they exist to the output directory. +```kotlin +kotlinx.kover.api.DefaultJacocoEngine +``` +
-Tasks that are created for all projects: +
+Groovy + +```groovy +kotlinx.kover.api.DefaultJacocoEngine.INSTANCE +``` +
+ +#### JaCoCo Coverage Engine with custom version +``` +kotlinx.kover.api.JacocoEngine("0.8.8") +``` + +## Kover default tasks +Tasks that are created for project where the Kover plugin is applied: - `koverHtmlReport` - Generates code coverage HTML report for all enabled test tasks in one project. - `koverXmlReport` - Generates code coverage XML report for all enabled test tasks in one project. - `koverReport` - Executes both `koverXmlReport` and `koverHtmlReport` tasks. - `koverVerify` - Verifies code coverage metrics of one project based on specified rules. Always executes before `check` task. +Tasks that are created for project where the Kover plugin is applied and merged reports are enabled: +- `koverMergedHtmlReport` - Generates code coverage HTML report for all enabled test tasks in all projects. +- `koverMergedXmlReport` - Generates code coverage XML report for all enabled test tasks in all projects. +- `koverMergedReport` - Executes both `koverMergedXmlReport` and `koverMergedHtmlReport` tasks. +- `koverMergedVerify` - Verifies code coverage metrics of all projects based on specified rules. Always executes before `check` task. + ## Implicit plugin dependencies While the plugin is being applied, the artifacts of the JaCoCo or IntelliJ toolkit are dynamically loaded. They are downloaded from the `mavenCentral` repository. diff --git a/docs/migration-to-0.6.0.md b/docs/migration-to-0.6.0.md index a1360f73..3743316b 100644 --- a/docs/migration-to-0.6.0.md +++ b/docs/migration-to-0.6.0.md @@ -2,15 +2,17 @@ The new API allows you to configure Kover more flexible and in one place. Now there is no need to configure each Kover task separately. -To configure reports that collect coverage only for tests from one project, the `kover { }` project extension is used. - -To configure reports that collect test coverage from different projects, the `koverMerged { }` project extension is used. -At the same time, in order to create tasks with answers, they must be explicitly activated +In the new API, the plugin is used only for one project. If you need Kover in several projects, apply the plugin for each of them. +To create merged tasks (that collect test coverage from different projects), enable it by `koverMerged.enable()` or ``` koverMerged { enable() } ``` +in one project, which will be a merged report container. + +To configure reports that collect coverage only for tests from one project, the `kover { }` project extension is used. +To configure merged reports, the `koverMerged { }` project extension is used. # Migration Issues @@ -92,7 +94,7 @@ Solution for Kotlin script: change `isDisabled = true` to `isDisabled.set(true)` Solution: change `binaryReportFile` to `reportFile` -### type of "includes" property changed from "List" to "ListProperty" +### type of "includes" property changed from "List\" to "ListProperty\" Solution for Kotlin: change `includes = listOf("com.example.*", "foo.bar.*")` to `includes.addAll("com.example.*", "foo.bar.*")` diff --git a/src/main/kotlin/kotlinx/kover/api/KoverConfig.kt b/src/main/kotlin/kotlinx/kover/api/KoverConfig.kt index 5f618a4a..4055bfca 100644 --- a/src/main/kotlin/kotlinx/kover/api/KoverConfig.kt +++ b/src/main/kotlin/kotlinx/kover/api/KoverConfig.kt @@ -135,7 +135,7 @@ public open class KoverProjectConfig @Inject constructor(objects: ObjectFactory) replaceWith = ReplaceWith("KoverProjectConfig"), level = DeprecationLevel.ERROR ) -public open class KoverExtension() +public open class KoverExtension public open class KoverProjectFilters @Inject constructor(private val objects: ObjectFactory) { internal val classes: Property = objects.property(KoverClassFilter::class.java) @@ -512,7 +512,7 @@ public enum class CounterType { /** - * Type of lines counter value to compare with minimal and maximal values if them defined. + * Type of counter value to compare with minimal and maximal values if them defined. */ public enum class VerificationValueType { COVERED_COUNT, From f6bb1946568f8148df590b629d1ec45b04f622bf Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Mon, 18 Jul 2022 15:13:56 +0200 Subject: [PATCH 05/10] ~tweak readme --- README.md | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index fdfc3044..d48e9018 100644 --- a/README.md +++ b/README.md @@ -201,21 +201,20 @@ android { **Instrumentation inclusion rules** -Only the specified classes may be instrumented, the remaining classes will still be present in the report, but for them the coverage will be zero. +Only the specified classes will be instrumented, the remaining (non-included) classes will still be present in the report, but their coverage will be zero. **Instrumentation exclusion rules** -The specified classes will not be instrumented and there will be zero coverage for them. +The specified classes will not be instrumented and their coverage will be zero. -Instrumentation inclusion/exclusion rule represented as a fully-qualified name of the class (several classes if wildcards are used). +Instrumentation inclusion/exclusion rules are represented as a fully-qualified name of the class (several classes if wildcards are used). File or directory names are not allowed. -It's possible to use `*` (zero or several of any chars) and `?` (one any char) wildcards. Wildcard `**` is similar to the `*`. +It is possible to use `*` (zero or several of any chars) and `?` (one any char) wildcards. Wildcard `**` is similar to the `*`. -Examples `my.package.ClassName` or `my.*.*Name` but not the `my/package/ClassName.kt` or `src/my.**.ClassName` +Examples `my.package.ClassName` or `my.*.*Name` are allowed, while `my/package/ClassName.kt` or `src/my.**.ClassName` are not. Exclusion rules have priority over inclusion ones. - ### Configuring project In the project in which the plugin is applied, you can configure instrumentation and default Kover tasks: @@ -347,19 +346,21 @@ kover { ``` -How to specify the engine version, see the [section](#specifying-coverage-engine) +Engine version is specified separately, see [specifying coverage engine](#specifying-coverage-engine) section. ### Configuring merged reports -To create default merged reports, you need to write `koverMerged.enable()` or + +In order to create a merged report, it has to be enabled explicitly in a containing project with + `koverMerged.enable()` or ``` koverMerged { enable() } ``` -in the containing project. By default, the merged reports will include this project along with all its subprojects. +By default, merged reports include containing project along with all its subprojects. -Merged reports can be configured. To do this, you need to configure the extension in the containing project (where `koverMerged.enable()` is called) +Merged reports can also be configured in a similar manner. To do this, you need to configure the extension in the containing project (where `koverMerged.enable()` is called)
Kotlin @@ -530,11 +531,11 @@ kotlinx.kover.api.JacocoEngine("0.8.8") ``` ## Kover default tasks -Tasks that are created for project where the Kover plugin is applied: -- `koverHtmlReport` - Generates code coverage HTML report for all enabled test tasks in one project. -- `koverXmlReport` - Generates code coverage XML report for all enabled test tasks in one project. +Tasks that are created for a project where the Kover plugin is applied: +- `koverHtmlReport` - Generates code coverage HTML report for all enabled test tasks in the project. +- `koverXmlReport` - Generates code coverage XML report for all enabled test tasks in the project. - `koverReport` - Executes both `koverXmlReport` and `koverHtmlReport` tasks. -- `koverVerify` - Verifies code coverage metrics of one project based on specified rules. Always executes before `check` task. +- `koverVerify` - Verifies code coverage metrics of the project based on configured rules. Always is executed before `check` task. Tasks that are created for project where the Kover plugin is applied and merged reports are enabled: - `koverMergedHtmlReport` - Generates code coverage HTML report for all enabled test tasks in all projects. @@ -546,7 +547,7 @@ Tasks that are created for project where the Kover plugin is applied and merged ## Implicit plugin dependencies While the plugin is being applied, the artifacts of the JaCoCo or IntelliJ toolkit are dynamically loaded. They are downloaded from the `mavenCentral` repository. -For the plugin to work correctly, you need to make sure that the `mavenCentral` (or its mirror) is added to the repository list of the project in which the plugin is applied, if it doesn't already exist (usually this is the root project): +For the plugin to work correctly, you need to make sure that the `mavenCentral` (or any of its mirrors) is added to the repository list of the project in which the plugin is applied, if it doesn't already exist (usually this is the root project):
Kotlin From 703d8b9d6250918461ff47ac789135d9051559d8 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Mon, 18 Jul 2022 15:49:37 +0200 Subject: [PATCH 06/10] ~tweak migration --- docs/migration-to-0.6.0.md | 61 +++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/docs/migration-to-0.6.0.md b/docs/migration-to-0.6.0.md index 3743316b..5dabb865 100644 --- a/docs/migration-to-0.6.0.md +++ b/docs/migration-to-0.6.0.md @@ -1,8 +1,10 @@ # Main differences -The new API allows you to configure Kover more flexible and in one place. -Now there is no need to configure each Kover task separately. -In the new API, the plugin is used only for one project. If you need Kover in several projects, apply the plugin for each of them. +The new API allows you to configure Kover in a more flexible manner, while being more concise than the previous API. +From now on, there is no need to configure each Kover task separately. + +In the new API, in order to respect upcoming Gradle conventions, the plugin should be explicitly applied to +each project that needs coverage. To create merged tasks (that collect test coverage from different projects), enable it by `koverMerged.enable()` or ``` koverMerged { @@ -18,11 +20,11 @@ To configure merged reports, the `koverMerged { }` project extension is used. ## Root kover extension -### type of "isDisabled" property changed from "Boolean" to "Property\". +### type of `isDisabled` property changed from `Boolean` to `Property`. for Kotlin script: change `isDisabled = true` to `isDisabled.set(true)` -### properties "coverageEngine", "intellijEngineVersion" and "jacocoEngineVersion" was removed. +### Properties `coverageEngine`, `intellijEngineVersion` and `jacocoEngineVersion` were removed. Use property `engine` - it combines version and coverage engine vendor. @@ -38,7 +40,7 @@ To use JaCoCo Coverage Engine with default version write `engine.set(kotlinx.ko To use JaCoCo Coverage Engine with custom version write `engine.set(kotlinx.kover.api.JacocoEngine("version"))` (Kotlin) or `engine = kotlinx.kover.api.JacocoEngine("version")` (Groovy). -### property "generateReportOnCheck" was removed +### Property "generateReportOnCheck" was removed Use the properties individually for each report @@ -58,7 +60,7 @@ kover { } ``` -### property "disabledProjects" was removed +### property `disabledProjects` was removed Use inclusion list in project filters @@ -73,44 +75,46 @@ koverMerged { } ``` -If `includes` is empty - all subprojects and current project are used in merged reports. +If `includes` are empty, all subprojects and current project are used in merged reports. -### property "instrumentAndroidPackage" was removed +### Property `instrumentAndroidPackage` was removed There is no replacement. At the moment, all classes from the packages "android." and "com.android.*" excluded from instrumentation. -### property "runAllTestsForProjectTask" was removed +### property `runAllTestsForProjectTask` was removed TBD ## Kover extension for test task -### type of "isDisabled" property changed from "Boolean" to "Property\". +### type of `isDisabled` property changed from "Boolean" to `Property`". Solution for Kotlin script: change `isDisabled = true` to `isDisabled.set(true)` -### "binaryReportFile" was renamed to "reportFile" +### `binaryReportFile` was renamed to `reportFile` Solution: change `binaryReportFile` to `reportFile` -### type of "includes" property changed from "List\" to "ListProperty\" +### Type of `includes` property changed from `List` to `ListProperty` Solution for Kotlin: change `includes = listOf("com.example.*", "foo.bar.*")` to `includes.addAll("com.example.*", "foo.bar.*")` + Solution for Groovy: change `includes = ["com.example.*", "foo.bar.*"]` to `includes.addAll("com.example.*", "foo.bar.*")` -### type of "excludes" property changed from "List\" to "ListProperty\" +### type of `excludes` property changed from `List` to `ListProperty` Solution for Kotlin: change `excludes = listOf("com.example.*", "foo.bar.*")` to `includes.addAll("com.example.*", "foo.bar.*")` + Solution for Groovy: change `excludes = ["com.example.*", "foo.bar.*"]` to `includes.addAll("com.example.*", "foo.bar.*")` -## "koverXmlReport" and "koverMergedXmlReport" tasks configuration +## `koverXmlReport` and `koverMergedXmlReport` tasks configuration -###property `xmlReportFile` was removed +### Property `xmlReportFile` was removed Solution: use property in Kover extension at the root of the project ``` @@ -122,7 +126,7 @@ kover { ``` * for `xmlReportFile` task use `koverMerged { ... }` extension of the project. -### property "includes" was removed +### Property `includes` was removed Solution for Kotlin: use filter in Kover extension at the root of the project @@ -149,7 +153,7 @@ kover { ``` * for `xmlReportFile` task use `koverMerged { ... }` extension of the project. -### property "excludes" was removed +### Property `excludes` was removed Solution for Kotlin: use filter in Kover extension at the root of the project @@ -176,9 +180,9 @@ kover { ``` * for `xmlReportFile` task use `koverMerged { ... }` extension of the project. -## "koverHtmlReport" and "koverMergedHtmlReport" tasks configuration +## `koverHtmlReport` and `koverMergedHtmlReport` tasks configuration -### property "htmlReportDir" was removed +### Property `htmlReportDir` was removed Solution: use property in Kover extension at the root of the project @@ -191,7 +195,7 @@ kover { ``` * for `koverMergedHtmlReport` task use `koverMerged { ... }` extension of the project. -### property "includes" was removed +### Property `includes` was removed Solution for Kotlin: use filter in Kover extension at the root of the project @@ -218,7 +222,7 @@ kover { ``` * for `koverMergedHtmlReport` task use `koverMerged { ... }` extension of the project. -### property "excludes" was removed +### Property `excludes` was removed Solution for Kotlin: use filter in Kover extension at the root of the project @@ -245,9 +249,9 @@ kover { ``` * for `koverMergedHtmlReport` task use `koverMerged { ... }` extension of the project. -## "koverVerify" and "koverMergedVerify" tasks configuration +## `koverVerify` and `koverMergedVerify` tasks configuration -### function "rule" was removed +### Function `rule` was removed Solution: use function in Kover extension at the root of the project @@ -260,9 +264,10 @@ kover { } } ``` -* for `koverMergedVerify` task use `koverMerged { ... }` extension of the project. -### property "includes" was removed +* For `koverMergedVerify` task use `koverMerged { ... }` extension of the project. + +### Property `includes` was removed Solution for Kotlin: use filter in Kover extension at the root of the project @@ -289,7 +294,7 @@ kover { ``` * for `koverMergedVerify` task use `koverMerged { ... }` extension of the project. -### property "excludes" was removed +### Property `excludes` was removed Solution for Kotlin: use filter in Kover extension at the root of the project @@ -314,5 +319,5 @@ kover { } } ``` -* for `koverMergedVerify` task use `koverMerged { ... }` extension of the project. +* For `koverMergedVerify` task use `koverMerged { ... }` extension of the project. From a2ce0f2c1aa47bb924fba4e3009dddf5dbeecbcf Mon Sep 17 00:00:00 2001 From: "Sergey.Shanshin" Date: Tue, 26 Jul 2022 20:32:29 +0200 Subject: [PATCH 07/10] Review fixes - Kotlin version upgraded to 1.7.10 - Agent version upgraded to 1.0.675 - instrumentation config added to the test framework - added test on instrumentation config - made CoverageEngineVariant as abstract class - improved docs and comments --- build.gradle.kts | 12 ++-- gradle.properties | 1 + .../cases/InstrumentationFilteringTests.kt | 18 +++++ .../cases/counters/CountersValueTests.kt | 6 +- .../test/functional/cases/utils/Defaults.kt | 12 ++-- .../functional/core/BaseGradleScriptTest.kt | 2 +- .../kover/test/functional/core/Builder.kt | 6 +- .../kover/test/functional/core/Types.kt | 2 +- .../core/writer/KoverExtensionWriter.kt | 23 +++++-- .../kotlinx/kover/api/CoverageEngines.kt | 60 ++++++++++------ .../kotlin/kotlinx/kover/api/KoverConfig.kt | 69 ++++++++++++------- .../kotlinx/kover/api/KoverConstants.kt | 10 +-- .../kotlinx/kover/api/KoverTaskExtension.kt | 2 - .../kover/appliers/KoverMergedApplier.kt | 43 +++--------- .../kotlin/kotlinx/kover/lookup/DirsLookup.kt | 9 ++- .../lookup/adapters/AndroidPluginAdapter.kt | 4 ++ ...aPluginAdapter.kt => JavaPluginAdapter.kt} | 6 +- .../adapters/KotlinAndroidPluginAdapter.kt | 4 ++ .../KotlinMultiplatformPluginAdapter.kt | 4 ++ .../kotlinx/kover/tasks/KoverHtmlTask.kt | 9 +-- .../kotlinx/kover/tasks/KoverReportTask.kt | 2 +- .../kover/tasks/KoverVerificationTask.kt | 10 +-- .../kotlinx/kover/tasks/KoverXmlTask.kt | 9 +-- 23 files changed, 195 insertions(+), 128 deletions(-) rename src/main/kotlin/kotlinx/kover/lookup/adapters/{OldJavaPluginAdapter.kt => JavaPluginAdapter.kt} (84%) diff --git a/build.gradle.kts b/build.gradle.kts index 7d40d330..d1cfc448 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - kotlin("jvm") version "1.6.10" + kotlin("jvm") version "1.7.10" `java-gradle-plugin` `maven-publish` @@ -24,16 +24,16 @@ dependencies { // exclude transitive dependency on stdlib, the Gradle version should be used compileOnly(kotlin("stdlib")) - compileOnly("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10") + compileOnly("org.jetbrains.kotlin:kotlin-gradle-plugin:${property("kotlin.version")}") compileOnly("com.android.tools.build:gradle:4.2.2") testImplementation(kotlin("test")) "functionalTestImplementation"(gradleTestKit()) // dependencies only for plugin's classpath to work with Kotlin Multi-Platform and Android plugins - "functionalTestCompileOnly"("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10") - "functionalTestCompileOnly"("org.jetbrains.kotlin:kotlin-compiler-embeddable:1.6.10") - "functionalTestCompileOnly"("org.jetbrains.kotlin:kotlin-compiler-runner:1.6.10") + "functionalTestCompileOnly"("org.jetbrains.kotlin:kotlin-gradle-plugin:${property("kotlin.version")}") + "functionalTestCompileOnly"("org.jetbrains.kotlin:kotlin-compiler-embeddable:${property("kotlin.version")}") + "functionalTestCompileOnly"("org.jetbrains.kotlin:kotlin-compiler-runner:${property("kotlin.version")}") } java { @@ -73,7 +73,7 @@ tasks.withType().configureEach // Kover works with the stdlib of at least version `1.4.x` languageVersion = "1.4" apiVersion = "1.4" - // Kotlin compiler 1.6 issues a warning if `languageVersion` or `apiVersion` 1.4 is used - suppress it + // Kotlin compiler 1.7 issues a warning if `languageVersion` or `apiVersion` 1.4 is used - suppress it freeCompilerArgs = freeCompilerArgs + "-Xsuppress-version-warnings" } } diff --git a/gradle.properties b/gradle.properties index 0a3cd728..11800157 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,5 @@ version=0.6.0-SNAPSHOT group=org.jetbrains.kotlinx +kotlin.version=1.7.10 kotlin.code.style=official 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 2b2f7449..ea7d857b 100644 --- a/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/InstrumentationFilteringTests.kt +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/InstrumentationFilteringTests.kt @@ -46,4 +46,22 @@ internal class InstrumentationFilteringTests : BaseGradleScriptTest() { } } + @Test + fun testDisableInstrumentationOfTask() { + val build = diverseBuild(ALL_LANGUAGES, ALL_ENGINES, listOf(ProjectType.KOTLIN_JVM)) + build.addKoverRootProject { + sourcesFrom("simple") + kover { + instrumentation { + excludeTasks += "test" + } + } + } + val runner = build.prepare() + runner.run("build", "koverXmlReport") { + // if task `test` is excluded from instrumentation then the binary report is not created for it + checkDefaultBinaryReport(false) + } + } + } diff --git a/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/counters/CountersValueTests.kt b/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/counters/CountersValueTests.kt index 792564fc..a5ca67a7 100644 --- a/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/counters/CountersValueTests.kt +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/counters/CountersValueTests.kt @@ -40,10 +40,8 @@ internal class CountersValueTests : BaseGradleScriptTest() { // empty functions must be included in the report with line counter methodCounter("org.jetbrains.Different", "emptyFun", type = "LINE").assertFullyMissed() - // Instruction counters - // helloWorld contains 4 instructions. `return` ignored by reporter and the first instruction is not covered because of a bug in the IR compiler - // FIXME after https://youtrack.jetbrains.com/issue/KT-51080 is fixed, the number should become 3 - methodCounter("org.jetbrains.Different", "helloWorld").assertTotal(2) + // Instruction counters - value may depend on Kotlin compiler version + methodCounter("org.jetbrains.Different", "helloWorld").assertTotal(4) } } } 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 0311dda9..0313e618 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,15 +4,19 @@ import kotlinx.kover.api.* import kotlinx.kover.test.functional.core.ProjectType -internal fun defaultBinaryReport(engine: CoverageEngineVendor, projectType: ProjectType): String { +internal fun defaultTestTask(engine: CoverageEngineVendor, projectType: ProjectType): String { val extension = if (engine == CoverageEngineVendor.INTELLIJ) "ic" else "exec" return when (projectType) { - ProjectType.KOTLIN_JVM -> "kover/test.$extension" - ProjectType.KOTLIN_MULTIPLATFORM -> "kover/jvmTest.$extension" - ProjectType.ANDROID -> "kover/jvmTest.$extension" + ProjectType.KOTLIN_JVM -> "test.$extension" + ProjectType.KOTLIN_MULTIPLATFORM -> "jvmTest.$extension" + ProjectType.ANDROID -> "jvmTest.$extension" } } +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" diff --git a/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/BaseGradleScriptTest.kt b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/BaseGradleScriptTest.kt index de6b430b..c895357b 100644 --- a/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/BaseGradleScriptTest.kt +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/BaseGradleScriptTest.kt @@ -48,7 +48,7 @@ internal open class BaseGradleScriptTest { internal fun DiverseBuild.addKoverRootProject(builder: ProjectBuilder.() -> Unit) { addProject("root", ":") { plugins { - kotlin("1.6.20") + kotlin("1.7.10") kover("DEV") } diff --git a/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/Builder.kt b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/Builder.kt index 921a153b..6d33425e 100644 --- a/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/Builder.kt +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/Builder.kt @@ -117,14 +117,14 @@ internal class TestKoverProjectConfigState : TestKoverProjectConfig { internal class TestKoverProjectFiltersState : TestKoverProjectFilters { var classes: KoverClassFilter? = null - var sourcesets: KoverSourceSetFilter? = null + var sourceSets: KoverSourceSetFilter? = null override fun classes(config: KoverClassFilter.() -> Unit) { classes = KoverClassFilter().also(config) } - override fun sourcesets(config: KoverSourceSetFilter.() -> Unit) { - sourcesets = KoverSourceSetFilter().also(config) + override fun sourceSets(config: KoverSourceSetFilter.() -> Unit) { + sourceSets = KoverSourceSetFilter().also(config) } } 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 e93651fb..602ed310 100644 --- a/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/Types.kt +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/Types.kt @@ -91,7 +91,7 @@ internal interface TestKoverProjectHtmlConfig { internal interface TestKoverProjectFilters { fun classes(config: KoverClassFilter.() -> Unit) - fun sourcesets(config: KoverSourceSetFilter.() -> Unit) + fun sourceSets(config: KoverSourceSetFilter.() -> Unit) } internal interface TestKoverMergedConfig { 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 596e8e97..8325dc99 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 @@ -16,6 +16,7 @@ internal fun PrintWriter.printKover(kover: TestKoverProjectConfigState?, slice: 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) printVerify(kover.verify, slice, indents + 1) indented(indents, "}") @@ -55,8 +56,8 @@ private fun PrintWriter.printDisabled(isDisabled: Boolean?, slice: ProjectSlice, private fun PrintWriter.printFilters(state: TestKoverProjectFiltersState, slice: ProjectSlice, indents: Int) { val classes = state.classes - val sourcesets = state.sourcesets - if (sourcesets == null && classes == null) return + val sourceSets = state.sourceSets + if (sourceSets == null && classes == null) return indented(indents, "filters {") if (classes != null && (classes.excludes.isNotEmpty() || classes.includes.isNotEmpty())) { @@ -65,15 +66,23 @@ private fun PrintWriter.printFilters(state: TestKoverProjectFiltersState, slice: indented(indents + 1, "}") } - if (sourcesets != null) { - indented(indents + 1, "sourcesets {") - if (sourcesets.excludes.isNotEmpty()) { - indented(indents + 2, "excludes".addAllList(sourcesets.excludes, slice.language)) + if (sourceSets != null) { + indented(indents + 1, "sourceSets {") + if (sourceSets.excludes.isNotEmpty()) { + indented(indents + 2, "excludes".addAllList(sourceSets.excludes, slice.language)) } - indented(indents + 2, "excludeTests = " + sourcesets.excludeTests) + indented(indents + 2, "excludeTests = " + sourceSets.excludeTests) indented(indents + 1, "}") } indented(indents, "}") } +private fun PrintWriter.printInstrumentation(state: KoverProjectInstrumentation, slice: ProjectSlice, indents: Int) { + if (state.excludeTasks.isEmpty()) return + + indented(indents, "instrumentation {") + indented(indents + 1, "excludeTasks".addAllList(state.excludeTasks, slice.language)) + indented(indents, "}") +} + diff --git a/src/main/kotlin/kotlinx/kover/api/CoverageEngines.kt b/src/main/kotlin/kotlinx/kover/api/CoverageEngines.kt index 202148fb..b222d907 100644 --- a/src/main/kotlin/kotlinx/kover/api/CoverageEngines.kt +++ b/src/main/kotlin/kotlinx/kover/api/CoverageEngines.kt @@ -2,19 +2,39 @@ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -@file:Suppress("RedundantVisibilityModifier") - package kotlinx.kover.api import kotlinx.kover.api.KoverVersions.DEFAULT_INTELLIJ_VERSION import kotlinx.kover.api.KoverVersions.DEFAULT_JACOCO_VERSION import org.gradle.api.tasks.* -public interface CoverageEngineVariant { +public sealed class CoverageEngineVariant( @get:Input - public val vendor: CoverageEngineVendor + public val vendor: CoverageEngineVendor, @get:Input public val version: String +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as CoverageEngineVariant + + if (vendor != other.vendor) return false + if (version != other.version) return false + + return true + } + + override fun hashCode(): Int { + var result = vendor.hashCode() + result = 31 * result + version.hashCode() + return result + } + + override fun toString(): String { + return "$vendor Coverage Engine $version" + } } public enum class CoverageEngineVendor { @@ -22,34 +42,32 @@ public enum class CoverageEngineVendor { JACOCO } +// TODO make internal in 0.7 version +@Deprecated( + message = "Class was removed in Kover API version 2. Please refer to migration guide in order to migrate: ${KoverMigrations.MIGRATION_0_5_TO_0_6}", + level = DeprecationLevel.WARNING +) +public enum class CoverageEngine { + INTELLIJ, + JACOCO +} + /** * Coverage Engine by IntelliJ. */ -public class IntellijEngine(override val version: String): CoverageEngineVariant { - override val vendor: CoverageEngineVendor = CoverageEngineVendor.INTELLIJ - override fun toString(): String = "IntelliJ Coverage Engine $version" -} +public class IntellijEngine(version: String): CoverageEngineVariant(CoverageEngineVendor.INTELLIJ, version) /** * IntelliJ Coverage Engine with default version [DEFAULT_INTELLIJ_VERSION]. */ -public object DefaultIntellijEngine: CoverageEngineVariant { - override val vendor: CoverageEngineVendor = CoverageEngineVendor.INTELLIJ - override val version: String = DEFAULT_INTELLIJ_VERSION -} +public object DefaultIntellijEngine: CoverageEngineVariant(CoverageEngineVendor.INTELLIJ, DEFAULT_INTELLIJ_VERSION) /** - * Coverage Engine by JaCoCo. + * Coverage Engine by [JaCoCo](https://www.jacoco.org/jacoco/). */ -public class JacocoEngine(override val version: String): CoverageEngineVariant { - override val vendor: CoverageEngineVendor = CoverageEngineVendor.JACOCO - override fun toString(): String = "JaCoCo Coverage Engine $version" -} +public class JacocoEngine(version: String): CoverageEngineVariant(CoverageEngineVendor.JACOCO, version) /** * JaCoCo Coverage Engine with default version [DEFAULT_JACOCO_VERSION]. */ -public object DefaultJacocoEngine: CoverageEngineVariant { - override val vendor: CoverageEngineVendor = CoverageEngineVendor.JACOCO - override val version: String = DEFAULT_JACOCO_VERSION -} +public object DefaultJacocoEngine: CoverageEngineVariant(CoverageEngineVendor.JACOCO, DEFAULT_JACOCO_VERSION) diff --git a/src/main/kotlin/kotlinx/kover/api/KoverConfig.kt b/src/main/kotlin/kotlinx/kover/api/KoverConfig.kt index 4055bfca..525c55e0 100644 --- a/src/main/kotlin/kotlinx/kover/api/KoverConfig.kt +++ b/src/main/kotlin/kotlinx/kover/api/KoverConfig.kt @@ -2,8 +2,6 @@ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -@file:Suppress("RedundantVisibilityModifier") - package kotlinx.kover.api import org.gradle.api.* @@ -29,6 +27,8 @@ public open class KoverProjectConfig @Inject constructor(objects: ObjectFactory) /** * Specifies whether instrumentation is disabled for all test tasks of current project. + * + * `false` by default. */ public val isDisabled: Property = objects.property(Boolean::class.java) @@ -38,35 +38,43 @@ public open class KoverProjectConfig @Inject constructor(objects: ObjectFactory) public val engine: Property = objects.property(CoverageEngineVariant::class.java) /** - * Configures filters for all Kover tasks of current project. + * Configures filtering for all Kover's tasks of current project by class names and source sets. */ public fun filters(config: Action) { config.execute(filters) } /** - * Configures instrumentation of the test tasks of current project. + * Configures a list of tasks, the execution of tests from which is registered in the coverage counters. + * */ public fun instrumentation(config: Action) { config.execute(instrumentation) } /** - * Configures the task of generating an XML report. + * Configures the task of generating an XML report, including XML report location and whether it should be + * generated during the 'check' task. + * + * By default, [KoverPaths.PROJECT_XML_REPORT_DEFAULT_PATH] location in build directory is used. */ public fun xmlReport(config: Action) { config.execute(xmlReport) } /** - * Configures the task of generating an HTML report. + * Configures the task of generating an HTML report, including HTML report location and whether it should be + * generated during the 'check' task. + * + * By default, [KoverPaths.PROJECT_HTML_REPORT_DEFAULT_PATH] location in build directory is used. */ public fun htmlReport(config: Action) { config.execute(htmlReport) } /** - * Configures the verification task. + * Configures the verification task, including adding verification rules and whether it should be + * verified during the 'check' task. */ public fun verify(config: Action) { config.execute(verify) @@ -75,17 +83,18 @@ public open class KoverProjectConfig @Inject constructor(objects: ObjectFactory) // DEPRECATIONS // TODO delete in 0.7 version + @Suppress("DEPRECATION") @get:Internal @Deprecated( - message = "Property was removed in Kover API version 2. Please read migration to 0.6.0 guide to solve the issue", + message = "Property was removed in Kover API version 2. Please refer to migration guide in order to migrate: ${KoverMigrations.MIGRATION_0_5_TO_0_6}", replaceWith = ReplaceWith("engine"), level = DeprecationLevel.ERROR ) - public val coverageEngine: Property = objects.property(CoverageEngineVendor::class.java) + public val coverageEngine: Property = objects.property(CoverageEngine::class.java) @get:Internal @Deprecated( - message = "Property was removed in Kover API version 2. Please read migration to 0.6.0 guide to solve the issue", + message = "Property was removed in Kover API version 2. Please refer to migration guide in order to migrate: ${KoverMigrations.MIGRATION_0_5_TO_0_6}", replaceWith = ReplaceWith("engine"), level = DeprecationLevel.ERROR ) @@ -93,7 +102,7 @@ public open class KoverProjectConfig @Inject constructor(objects: ObjectFactory) @get:Internal @Deprecated( - message = "Property was removed in Kover API version 2. Please read migration to 0.6.0 guide to solve the issue", + message = "Property was removed in Kover API version 2. Please refer to migration guide in order to migrate: ${KoverMigrations.MIGRATION_0_5_TO_0_6}", replaceWith = ReplaceWith("engine"), level = DeprecationLevel.ERROR ) @@ -101,27 +110,27 @@ public open class KoverProjectConfig @Inject constructor(objects: ObjectFactory) @get:Internal @Deprecated( - message = "Property was removed in Kover API version 2. Please read migration to 0.6.0 guide to solve the issue", + message = "Property was removed in Kover API version 2. Please refer to migration guide in order to migrate: ${KoverMigrations.MIGRATION_0_5_TO_0_6}", level = DeprecationLevel.ERROR ) public var generateReportOnCheck: Boolean = true @get:Internal @Deprecated( - message = "Property was removed in Kover API version 2. Please read migration to 0.6.0 guide to solve the issue", + message = "Property was removed in Kover API version 2. Please refer to migration guide in order to migrate: ${KoverMigrations.MIGRATION_0_5_TO_0_6}", level = DeprecationLevel.ERROR ) public var disabledProjects: Set = emptySet() @Deprecated( - message = "Property was removed in Kover API version 2. Please read migration to 0.6.0 guide to solve the issue", + message = "Property was removed in Kover API version 2. Please refer to migration guide in order to migrate: ${KoverMigrations.MIGRATION_0_5_TO_0_6}", level = DeprecationLevel.ERROR ) @get:Internal public var instrumentAndroidPackage: Boolean = false @Deprecated( - message = "Property was removed in Kover API version 2. Please read migration to 0.6.0 guide to solve the issue", + message = "Property was removed in Kover API version 2. Please refer to migration guide in order to migrate: ${KoverMigrations.MIGRATION_0_5_TO_0_6}", level = DeprecationLevel.ERROR ) @get:Internal @@ -154,7 +163,7 @@ public open class KoverProjectFilters @Inject constructor(private val objects: O /** * Configures source set filter. */ - public fun sourcesets(config: Action) { + public fun sourceSets(config: Action) { val sourceSetFilters = objects.newInstance(KoverSourceSetFilter::class.java) config.execute(sourceSetFilters) sourceSets.set(sourceSetFilters) @@ -173,17 +182,21 @@ public open class KoverProjectXmlConfig @Inject constructor(objects: ObjectFacto /** * Specifies whether the XML report generation task should be executed before the `check` task. + * + * `false` by default. */ public val onCheck: Property = objects.property(Boolean::class.java) /** * Specifies file path of generated XML report file with coverage data. + * + * By default, is a value of [KoverPaths.PROJECT_XML_REPORT_DEFAULT_PATH] in the build directory. */ public val reportFile: RegularFileProperty = objects.fileProperty() /** * Override filters for the XML report generation task. - * Only the explicitly specified filters will be overrided, the rest will be inherited from the common filters (see [KoverProjectConfig.filters]). + * Only the explicitly specified filters will be overridden, the rest will be inherited from the common filters (see [KoverProjectConfig.filters]). */ public fun overrideFilters(config: Action) { config.execute(filters) @@ -195,11 +208,15 @@ public open class KoverProjectHtmlConfig @Inject constructor(private val objects /** * Specifies whether the HTML report generation task should be executed before the `check` task. + * + * `false` by default. */ public val onCheck: Property = objects.property(Boolean::class.java) /** * Specifies directory path of generated HTML report. + * + * By default, is a value of [KoverPaths.PROJECT_HTML_REPORT_DEFAULT_PATH] in the build directory. */ public val reportDir: DirectoryProperty = objects.directoryProperty() @@ -343,6 +360,8 @@ public open class KoverVerifyConfig @Inject constructor(private val objects: Obj /** * Specifies whether the verification task should be executed before the `check` task. + * + * By default, `true` for project reports and `false` for merged. */ public val onCheck: Property = objects.property(Boolean::class.java).value(true) @@ -380,13 +399,13 @@ public open class KoverClassFilter { public open class KoverSourceSetFilter { /** - * TBD + * Not implemented in beta version. */ @get:Input public val excludes: MutableSet = mutableSetOf() /** - * TBD + * Not implemented in beta version. */ @get:Input public var excludeTests: Boolean = true @@ -475,17 +494,17 @@ public open class VerificationBound { */ public enum class VerificationTarget { /** - * Count the coverage for all code. + * Counts the coverage for all code. */ ALL, /** - * Count the coverage for each class separately. + * Counts the coverage for each class separately. */ CLASS, /** - * Count the coverage for each package that has classes separately. + * Counts the coverage for each package that has classes separately. */ PACKAGE } @@ -495,17 +514,17 @@ public enum class VerificationTarget { */ public enum class CounterType { /** - * Evaluate coverage for lines. + * Evaluates coverage for lines. */ LINE, /** - * Evaluate coverage for JVM bytecode instructions. + * Evaluates coverage for JVM bytecode instructions. */ INSTRUCTION, /** - * Evaluate coverage for code branches. + * Evaluates coverage for code branches excluded dead-branches. */ BRANCH } diff --git a/src/main/kotlin/kotlinx/kover/api/KoverConstants.kt b/src/main/kotlin/kotlinx/kover/api/KoverConstants.kt index 7d3e03f1..b83bed7c 100644 --- a/src/main/kotlin/kotlinx/kover/api/KoverConstants.kt +++ b/src/main/kotlin/kotlinx/kover/api/KoverConstants.kt @@ -2,8 +2,6 @@ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -@file:Suppress("RedundantVisibilityModifier") - package kotlinx.kover.api public object KoverNames { @@ -38,8 +36,12 @@ public object KoverPaths { } public object KoverVersions { - internal const val MINIMAL_INTELLIJ_VERSION = "1.0.668" - internal const val DEFAULT_INTELLIJ_VERSION = "1.0.668" + internal const val MINIMAL_INTELLIJ_VERSION = "1.0.675" + internal const val DEFAULT_INTELLIJ_VERSION = "1.0.675" internal const val DEFAULT_JACOCO_VERSION = "0.8.8" } + +public object KoverMigrations { + public const val MIGRATION_0_5_TO_0_6 = "https://github.com/Kotlin/kotlinx-kover/blob/v0.6.0-BETA/docs/migration-to-0.6.0.md" +} diff --git a/src/main/kotlin/kotlinx/kover/api/KoverTaskExtension.kt b/src/main/kotlin/kotlinx/kover/api/KoverTaskExtension.kt index e6e2250d..fa29c968 100644 --- a/src/main/kotlin/kotlinx/kover/api/KoverTaskExtension.kt +++ b/src/main/kotlin/kotlinx/kover/api/KoverTaskExtension.kt @@ -2,8 +2,6 @@ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -@file:Suppress("RedundantVisibilityModifier") - package kotlinx.kover.api import org.gradle.api.file.RegularFileProperty diff --git a/src/main/kotlin/kotlinx/kover/appliers/KoverMergedApplier.kt b/src/main/kotlin/kotlinx/kover/appliers/KoverMergedApplier.kt index 78fa29e9..019935c6 100644 --- a/src/main/kotlin/kotlinx/kover/appliers/KoverMergedApplier.kt +++ b/src/main/kotlin/kotlinx/kover/appliers/KoverMergedApplier.kt @@ -209,14 +209,18 @@ private fun Project.engineProvider(extensionByProject: Provider> = mutableMapOf() + extensionByProject.get().forEach { (p, v) -> + map.computeIfAbsent(v.engine.get()) { mutableListOf() } += p.path + } + // check same engine variants are used - if (variants.size > 1) { - throw GradleException("Can't create Kover merge tasks: different coverage engines are used in projects") + if (map.size > 1) { + throw GradleException("Can't create Kover merge tasks: different coverage engines are used in projects.\n\tProjects by engines: $map") } - val variant = variants.first() - if (variant != ComparableEngineVariant(containerEngine.get())) { - throw GradleException("Can't create Kover merge tasks: project engines are different from the engine from the containing project '$containerPath'") + val variant = map.keys.first() + if (variant != containerEngine.get()) { + throw GradleException("Can't create Kover merge tasks: child projects engines '$variant' are different from the engine '${containerEngine.get()}' from the containing project '$containerPath'") } engineByVariant(variant, config, archiveOperations) } @@ -226,30 +230,3 @@ private fun Project.engineProvider(extensionByProject: Provider>): Provider> { return provider { extensionByProject.get().flatMap { it.key.instrumentedTasks(it.value) } } } - - -/** - * Wrapper for [CoverageEngineVariant] to group applied engines. - */ -private class ComparableEngineVariant(origin: CoverageEngineVariant) : CoverageEngineVariant { - override val vendor: CoverageEngineVendor = origin.vendor - override val version: String = origin.version - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as ComparableEngineVariant - - if (vendor != other.vendor) return false - if (version != other.version) return false - - return true - } - - override fun hashCode(): Int { - var result = vendor.hashCode() - result = 31 * result + version.hashCode() - return result - } -} diff --git a/src/main/kotlin/kotlinx/kover/lookup/DirsLookup.kt b/src/main/kotlin/kotlinx/kover/lookup/DirsLookup.kt index 118dbcf8..f3b44f00 100644 --- a/src/main/kotlin/kotlinx/kover/lookup/DirsLookup.kt +++ b/src/main/kotlin/kotlinx/kover/lookup/DirsLookup.kt @@ -14,7 +14,7 @@ open class ProjectDirectories(val sources: FileCollection, val outputs: FileColl internal object DirsLookup { private val adapters: List = listOf( - OldJavaPluginAdapter(), + JavaPluginAdapter(), AndroidPluginAdapter(), KotlinMultiplatformPluginAdapter(), KotlinAndroidPluginAdapter() @@ -40,8 +40,13 @@ internal object DirsLookup { } - +/** + * Allows to get a list of directories with source code and generated class-files that used by various Gradle plugins. + */ internal abstract class LookupAdapter { + /** + * Find the sources and output directories used by the plugin whose support is implemented in the child class. + */ protected abstract fun lookup(project: Project, sourceSetFilters: KoverSourceSetFilter): Dirs fun lookupSafe(project: Project, sourceSetFilters: KoverSourceSetFilter): Dirs { diff --git a/src/main/kotlin/kotlinx/kover/lookup/adapters/AndroidPluginAdapter.kt b/src/main/kotlin/kotlinx/kover/lookup/adapters/AndroidPluginAdapter.kt index 1b1e3aaf..a3965572 100644 --- a/src/main/kotlin/kotlinx/kover/lookup/adapters/AndroidPluginAdapter.kt +++ b/src/main/kotlin/kotlinx/kover/lookup/adapters/AndroidPluginAdapter.kt @@ -9,6 +9,10 @@ import kotlinx.kover.api.* import kotlinx.kover.lookup.* import org.gradle.api.* +/** + * Adapter to get sources and outputs of Android Gradle plugin. + * Required to support java sources for android. + */ internal class AndroidPluginAdapter : LookupAdapter() { override fun lookup(project: Project, sourceSetFilters: KoverSourceSetFilter): Dirs { project.plugins.findPlugin("android") ?: return Dirs() diff --git a/src/main/kotlin/kotlinx/kover/lookup/adapters/OldJavaPluginAdapter.kt b/src/main/kotlin/kotlinx/kover/lookup/adapters/JavaPluginAdapter.kt similarity index 84% rename from src/main/kotlin/kotlinx/kover/lookup/adapters/OldJavaPluginAdapter.kt rename to src/main/kotlin/kotlinx/kover/lookup/adapters/JavaPluginAdapter.kt index d1086689..d7989a41 100644 --- a/src/main/kotlin/kotlinx/kover/lookup/adapters/OldJavaPluginAdapter.kt +++ b/src/main/kotlin/kotlinx/kover/lookup/adapters/JavaPluginAdapter.kt @@ -9,7 +9,11 @@ import kotlinx.kover.lookup.LookupAdapter import org.gradle.api.* import org.gradle.api.tasks.* -internal class OldJavaPluginAdapter : LookupAdapter() { +/** + * Adapter to get sources and outputs of java plugin. + * Required to support java sources. + */ +internal class JavaPluginAdapter : LookupAdapter() { override fun lookup(project: Project, sourceSetFilters: KoverSourceSetFilter): Dirs { project.plugins.findPlugin("java") ?: return Dirs() diff --git a/src/main/kotlin/kotlinx/kover/lookup/adapters/KotlinAndroidPluginAdapter.kt b/src/main/kotlin/kotlinx/kover/lookup/adapters/KotlinAndroidPluginAdapter.kt index f352cb58..5ac17979 100644 --- a/src/main/kotlin/kotlinx/kover/lookup/adapters/KotlinAndroidPluginAdapter.kt +++ b/src/main/kotlin/kotlinx/kover/lookup/adapters/KotlinAndroidPluginAdapter.kt @@ -9,6 +9,10 @@ import kotlinx.kover.lookup.LookupAdapter import org.gradle.api.* import org.jetbrains.kotlin.gradle.dsl.* +/** + * Adapter to get sources and outputs of Kotlin Android Gradle plugin. + * Required to support kotlin sources for android. + */ internal class KotlinAndroidPluginAdapter : LookupAdapter() { override fun lookup(project: Project, sourceSetFilters: KoverSourceSetFilter): Dirs { diff --git a/src/main/kotlin/kotlinx/kover/lookup/adapters/KotlinMultiplatformPluginAdapter.kt b/src/main/kotlin/kotlinx/kover/lookup/adapters/KotlinMultiplatformPluginAdapter.kt index c5539f05..a9d0c2db 100644 --- a/src/main/kotlin/kotlinx/kover/lookup/adapters/KotlinMultiplatformPluginAdapter.kt +++ b/src/main/kotlin/kotlinx/kover/lookup/adapters/KotlinMultiplatformPluginAdapter.kt @@ -13,6 +13,10 @@ import org.gradle.internal.metaobject.* import org.jetbrains.kotlin.gradle.dsl.* import org.jetbrains.kotlin.gradle.plugin.* +/** + * Adapter to get sources and outputs of Kotlin Multi-Platform Gradle plugin. + * Required to support kotlin sources for multiplatform projects. + */ internal class KotlinMultiplatformPluginAdapter : LookupAdapter() { override fun lookup(project: Project, sourceSetFilters: KoverSourceSetFilter): Dirs { diff --git a/src/main/kotlin/kotlinx/kover/tasks/KoverHtmlTask.kt b/src/main/kotlin/kotlinx/kover/tasks/KoverHtmlTask.kt index 48345147..50788360 100644 --- a/src/main/kotlin/kotlinx/kover/tasks/KoverHtmlTask.kt +++ b/src/main/kotlin/kotlinx/kover/tasks/KoverHtmlTask.kt @@ -4,11 +4,12 @@ package kotlinx.kover.tasks +import kotlinx.kover.api.* import kotlinx.kover.engines.commons.* import org.gradle.api.file.* import org.gradle.api.tasks.* -// TODO make internal in 0.7 version +// TODO make internal in 0.7 version - for now it public to save access to deprecated fields to print deprecation message @CacheableTask public open class KoverHtmlTask : KoverReportTask() { @get:OutputDirectory @@ -41,21 +42,21 @@ public open class KoverHtmlTask : KoverReportTask() { // TODO delete in 0.7 version @get:Internal @Deprecated( - message = "Property was removed in Kover API version 2. Please read migration to 0.6.0 guide to solve the issue", + message = "Property was removed in Kover API version 2. Please refer to migration guide in order to migrate: ${KoverMigrations.MIGRATION_0_5_TO_0_6}", level = DeprecationLevel.ERROR ) val htmlReportDir: DirectoryProperty = project.objects.directoryProperty() @get:Internal @Deprecated( - message = "Property was removed in Kover API version 2. Please read migration to 0.6.0 guide to solve the issue", + message = "Property was removed in Kover API version 2. Please refer to migration guide in order to migrate: ${KoverMigrations.MIGRATION_0_5_TO_0_6}", level = DeprecationLevel.ERROR ) public var includes: List = emptyList() @get:Internal @Deprecated( - message = "Property was removed in Kover API version 2. Please read migration to 0.6.0 guide to solve the issue", + message = "Property was removed in Kover API version 2. Please refer to migration guide in order to migrate: ${KoverMigrations.MIGRATION_0_5_TO_0_6}", level = DeprecationLevel.ERROR ) public var excludes: List = emptyList() diff --git a/src/main/kotlin/kotlinx/kover/tasks/KoverReportTask.kt b/src/main/kotlin/kotlinx/kover/tasks/KoverReportTask.kt index ccc58868..5e5af09b 100644 --- a/src/main/kotlin/kotlinx/kover/tasks/KoverReportTask.kt +++ b/src/main/kotlin/kotlinx/kover/tasks/KoverReportTask.kt @@ -13,7 +13,7 @@ import org.gradle.configurationcache.extensions.* import org.gradle.process.* import java.io.* -// TODO make internal in 0.7 version +// TODO make internal in 0.7 version - for now it public to save access to deprecated fields to print deprecation message public abstract class KoverReportTask : DefaultTask() { @get:Nested internal val files: MapProperty = diff --git a/src/main/kotlin/kotlinx/kover/tasks/KoverVerificationTask.kt b/src/main/kotlin/kotlinx/kover/tasks/KoverVerificationTask.kt index 4f9340cc..04cacdc1 100644 --- a/src/main/kotlin/kotlinx/kover/tasks/KoverVerificationTask.kt +++ b/src/main/kotlin/kotlinx/kover/tasks/KoverVerificationTask.kt @@ -7,7 +7,7 @@ import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.ListProperty import org.gradle.api.tasks.* -// TODO make internal in 0.7 version +// TODO make internal in 0.7 version - for now it public to save access to deprecated fields to print deprecation message @CacheableTask public open class KoverVerificationTask : KoverReportTask() { @get:Nested @@ -60,23 +60,23 @@ public open class KoverVerificationTask : KoverReportTask() { // TODO delete in 0.7 version @Suppress("UNUSED_PARAMETER") @Deprecated( - message = "Function was removed in Kover API version 2. Please read migration to 0.6.0 guide to solve the issue", + message = "Function was removed in Kover API version 2. Please refer to migration guide in order to migrate: ${KoverMigrations.MIGRATION_0_5_TO_0_6}", level = DeprecationLevel.ERROR ) public fun rule(configureRule: Action) { - throw Exception("Function was removed in Kover API version 2. Please read migration to 0.6.0 guide to solve the issue") + throw Exception("Function was removed in Kover API version 2. Please refer to migration guide in order to migrate: ${KoverMigrations.MIGRATION_0_5_TO_0_6}") } @get:Internal @Deprecated( - message = "Property was removed in Kover API version 2. Please read migration to 0.6.0 guide to solve the issue", + message = "Property was removed in Kover API version 2. Please refer to migration guide in order to migrate: ${KoverMigrations.MIGRATION_0_5_TO_0_6}", level = DeprecationLevel.ERROR ) public var includes: List = emptyList() @get:Internal @Deprecated( - message = "Property was removed in Kover API version 2. Please read migration to 0.6.0 guide to solve the issue", + message = "Property was removed in Kover API version 2. Please refer to migration guide in order to migrate: ${KoverMigrations.MIGRATION_0_5_TO_0_6}", level = DeprecationLevel.ERROR ) public var excludes: List = emptyList() diff --git a/src/main/kotlin/kotlinx/kover/tasks/KoverXmlTask.kt b/src/main/kotlin/kotlinx/kover/tasks/KoverXmlTask.kt index cf3709c1..7cfe34a9 100644 --- a/src/main/kotlin/kotlinx/kover/tasks/KoverXmlTask.kt +++ b/src/main/kotlin/kotlinx/kover/tasks/KoverXmlTask.kt @@ -1,10 +1,11 @@ package kotlinx.kover.tasks +import kotlinx.kover.api.* import kotlinx.kover.engines.commons.* import org.gradle.api.file.* import org.gradle.api.tasks.* -// TODO make internal in 0.7 version +// TODO make internal in 0.7 version - for now it public to save access to deprecated fields to print deprecation message @CacheableTask public open class KoverXmlTask : KoverReportTask() { @get:OutputFile @@ -30,21 +31,21 @@ public open class KoverXmlTask : KoverReportTask() { // TODO delete in 0.7 version @get:Internal @Deprecated( - message = "Property was removed in Kover API version 2. Please read migration to 0.6.0 guide to solve the issue", + message = "Property was removed in Kover API version 2. Please refer to migration guide in order to migrate: ${KoverMigrations.MIGRATION_0_5_TO_0_6}", level = DeprecationLevel.ERROR ) public val xmlReportFile: RegularFileProperty = project.objects.fileProperty() @get:Internal @Deprecated( - message = "Property was removed in Kover API version 2. Please read migration to 0.6.0 guide to solve the issue", + message = "Property was removed in Kover API version 2. Please refer to migration guide in order to migrate: ${KoverMigrations.MIGRATION_0_5_TO_0_6}", level = DeprecationLevel.ERROR ) public var includes: List = emptyList() @get:Internal @Deprecated( - message = "Property was removed in Kover API version 2. Please read migration to 0.6.0 guide to solve the issue", + message = "Property was removed in Kover API version 2. Please refer to migration guide in order to migrate: ${KoverMigrations.MIGRATION_0_5_TO_0_6}", level = DeprecationLevel.ERROR ) public var excludes: List = emptyList() From 089df8bd22661bd275762547a57c642598820fad Mon Sep 17 00:00:00 2001 From: "Sergey.Shanshin" Date: Wed, 27 Jul 2022 17:53:11 +0200 Subject: [PATCH 08/10] Review fixes - made CoverageEngineVariant internal - improved docs and comments - improved deprecation comments - changed includes to excludes in projects filter for merged reports --- README.md | 4 +- build.gradle.kts | 7 + docs/migration-to-0.6.0.md | 134 +++++++++++++++--- .../functional/cases/MultiProjectTests.kt | 8 +- .../cases/counters/CountersValueTests.kt | 3 + .../test/functional/cases/utils/Defaults.kt | 4 + .../functional/core/BaseGradleScriptTest.kt | 4 + .../core/writer/MergedExtensionWriter.kt | 4 +- .../kotlinx/kover/api/CoverageEngines.kt | 6 +- .../kotlin/kotlinx/kover/api/KoverConfig.kt | 23 ++- .../kover/appliers/KoverMergedApplier.kt | 13 +- .../kotlinx/kover/tasks/Deprecations.kt | 23 +++ .../kotlinx/kover/tasks/KoverHtmlTask.kt | 4 +- .../kover/tasks/KoverVerificationTask.kt | 2 +- 14 files changed, 196 insertions(+), 43 deletions(-) create mode 100644 src/main/kotlin/kotlinx/kover/tasks/Deprecations.kt diff --git a/README.md b/README.md index d48e9018..e33c20c2 100644 --- a/README.md +++ b/README.md @@ -376,7 +376,7 @@ koverMerged { } projects { // common projects filter for all default Kover merged tasks - includes += listOf("project1", ":child:project") // Specifies the projects involved in the merged tasks + excludes += listOf("project1", ":child:project") // Specifies the projects excluded from the merged tasks } } @@ -437,7 +437,7 @@ koverMerged { } projects { // common projects filter for all default Kover merged tasks - includes.addAll("project1", ":child:project") // Specifies the projects involved in the merged tasks + excludes.addAll("project1", ":child:project") // Specifies the projects excluded in the merged tasks } } diff --git a/build.gradle.kts b/build.gradle.kts index d1cfc448..ac9a2de4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.plugin.* + plugins { kotlin("jvm") version "1.7.10" @@ -19,6 +21,11 @@ sourceSets { } } +// adding the ability to use internal classes inside functional tests +kotlin.target.compilations.run { + getByName("functionalTest").associateWith(getByName(KotlinCompilation.MAIN_COMPILATION_NAME)) +} + dependencies { implementation(gradleApi()) // exclude transitive dependency on stdlib, the Gradle version should be used diff --git a/docs/migration-to-0.6.0.md b/docs/migration-to-0.6.0.md index 5dabb865..50669036 100644 --- a/docs/migration-to-0.6.0.md +++ b/docs/migration-to-0.6.0.md @@ -22,10 +22,30 @@ To configure merged reports, the `koverMerged { }` project extension is used. ### type of `isDisabled` property changed from `Boolean` to `Property`. -for Kotlin script: change `isDisabled = true` to `isDisabled.set(true)` +_Error message_ + +``` +Val cannot be reassigned +``` + +_Solution for Kotlin script:_ change `isDisabled = true` to `isDisabled.set(true)` ### Properties `coverageEngine`, `intellijEngineVersion` and `jacocoEngineVersion` were removed. +_Error messages:_ + +``` +Using 'coverageEngine: Property' is an error +``` +``` +Using 'intellijEngineVersion: Property' is an error +``` +``` +Using 'jacocoEngineVersion: Property' is an error +``` + +_Solution:_ + Use property `engine` - it combines version and coverage engine vendor. To use IntelliJ Coverage Engine with default version write `engine.set(kotlinx.kover.api.DefaultIntellijEngine)` @@ -62,14 +82,21 @@ kover { ### property `disabledProjects` was removed -Use inclusion list in project filters +_Error message:_ + +```Using 'disabledProjects: Set' is an error.``` + +_Solution_ + +- read about [merged reports changes](#foo) +- use exclusion list in project filters of merged configuration extension ``` koverMerged { enable() filters { projects { - includes.add(":path or unique project name") + excludes.add(":path or unique project name") } } } @@ -88,9 +115,14 @@ TBD ## Kover extension for test task -### type of `isDisabled` property changed from "Boolean" to `Property`". +### type of `isDisabled` property changed from `Boolean` to `Property`. +_Error message_ + +``` +Val cannot be reassigned +``` -Solution for Kotlin script: change `isDisabled = true` to `isDisabled.set(true)` +_Solution for Kotlin script:_ change `isDisabled = true` to `isDisabled.set(true)` ### `binaryReportFile` was renamed to `reportFile` @@ -98,11 +130,9 @@ Solution: change `binaryReportFile` to `reportFile` ### Type of `includes` property changed from `List` to `ListProperty` -Solution for Kotlin: change `includes = listOf("com.example.*", "foo.bar.*")` -to `includes.addAll("com.example.*", "foo.bar.*")` +Solution: -Solution for Groovy: change `includes = ["com.example.*", "foo.bar.*"]` -to `includes.addAll("com.example.*", "foo.bar.*")` +```includes.addAll("com.example.*", "foo.bar.*")``` ### type of `excludes` property changed from `List` to `ListProperty` @@ -180,11 +210,54 @@ kover { ``` * for `xmlReportFile` task use `koverMerged { ... }` extension of the project. +## `KoverTaskExtension` configuration + +### Type of `excludes` and `includes` property changed from `List` to `ListProperty` +_Error message:_ +``` +Val cannot be reassigned +``` + +_Solution:_ + +``` +includes.addAll("com.example.*", "foo.bar.*") +``` +and +``` +excludes.addAll("com.example.*", "foo.bar.*") +``` + + ## `koverHtmlReport` and `koverMergedHtmlReport` tasks configuration +### Class `KoverHtmlReportTask` was removed + +_Error message:_ +``` +Using 'KoverHtmlReportTask' is an error +``` + +_Solution:_ + +Configure report by Kover project extension + +``` +kover { + htmlReport { + // HTML report settings + } +} +``` + ### Property `htmlReportDir` was removed -Solution: use property in Kover extension at the root of the project +_Error message:_ +``` +Using 'htmlReportDir: DirectoryProperty' is an error +``` + +Solution: use property `reportDir` in Kover extension at the root of the project ``` kover { @@ -197,7 +270,12 @@ kover { ### Property `includes` was removed -Solution for Kotlin: use filter in Kover extension at the root of the project +_Error message:_ +``` +Using 'includes: List' is an error +``` + +_Solution for Kotlin:_ use filter in Kover extension at the root of the project ``` kover { @@ -209,7 +287,7 @@ kover { } ``` -Solution for Groovy: use filter in Kover extension at the root of the project +_Solution for Groovy:_ use filter in Kover extension at the root of the project ``` kover { @@ -224,7 +302,12 @@ kover { ### Property `excludes` was removed -Solution for Kotlin: use filter in Kover extension at the root of the project +Error message: +``` +Using 'excludes: List' is an error +``` + +_Solution for Kotlin:_ use filter in Kover extension at the root of the project ``` kover { @@ -251,9 +334,16 @@ kover { ## `koverVerify` and `koverMergedVerify` tasks configuration -### Function `rule` was removed +### Function `rule` was removed for single-project verification + +Error message: +``` +Using 'rule(Action): Unit' is an error +``` + +_Solution:_ -Solution: use function in Kover extension at the root of the project +use function `rule` in Kover project extension ``` kover { @@ -269,6 +359,11 @@ kover { ### Property `includes` was removed +_Error message:_ +``` +Using 'includes: List' is an error +``` + Solution for Kotlin: use filter in Kover extension at the root of the project ``` @@ -296,7 +391,12 @@ kover { ### Property `excludes` was removed -Solution for Kotlin: use filter in Kover extension at the root of the project +_Error message:_ +``` +Using 'excludes: List' is an error +``` + +_Solution for Kotlin:_ use filter in Kover extension at the root of the project ``` kover { @@ -308,7 +408,7 @@ kover { } ``` -Solution for Groovy: use filter in Kover extension at the root of the project +_Solution for Groovy:_ use filter in Kover extension at the root of the project ``` kover { 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 e4cbdcc8..413b60b8 100644 --- a/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/MultiProjectTests.kt +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/MultiProjectTests.kt @@ -138,7 +138,7 @@ internal class MultiProjectTests : BaseGradleScriptTest() { } @Test - fun testIncludeProject() { + fun testExcludeProject() { val build = diverseBuild(engines = ALL_ENGINES, types = ALL_TYPES) val subPath = build.addKoverSubproject(subprojectName) { sourcesFrom("multiproject-common") @@ -152,7 +152,7 @@ internal class MultiProjectTests : BaseGradleScriptTest() { enable() filters { projects { - includes += rootName + excludes += subprojectName } } } @@ -180,7 +180,7 @@ internal class MultiProjectTests : BaseGradleScriptTest() { } @Test - fun testIncludeProjectByPath() { + fun testExcludeProjectByPath() { val build = diverseBuild(engines = ALL_ENGINES, types = ALL_TYPES) val subPath = build.addKoverSubproject(subprojectName) { sourcesFrom("multiproject-common") @@ -194,7 +194,7 @@ internal class MultiProjectTests : BaseGradleScriptTest() { enable() filters { projects { - includes += ":" + excludes += subPath } } } diff --git a/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/counters/CountersValueTests.kt b/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/counters/CountersValueTests.kt index a5ca67a7..040a7d25 100644 --- a/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/counters/CountersValueTests.kt +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/cases/counters/CountersValueTests.kt @@ -1,3 +1,6 @@ +/* + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ package kotlinx.kover.test.functional.cases.counters import kotlinx.kover.api.* 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 0313e618..09e836af 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 @@ -1,3 +1,7 @@ +/* + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + package kotlinx.kover.test.functional.cases.utils import kotlinx.kover.api.* diff --git a/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/BaseGradleScriptTest.kt b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/BaseGradleScriptTest.kt index c895357b..1895ad58 100644 --- a/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/BaseGradleScriptTest.kt +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/BaseGradleScriptTest.kt @@ -1,3 +1,7 @@ +/* + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + package kotlinx.kover.test.functional.core import kotlinx.kover.api.* diff --git a/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/MergedExtensionWriter.kt b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/MergedExtensionWriter.kt index f16193dc..f42a5a7f 100644 --- a/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/MergedExtensionWriter.kt +++ b/src/functionalTest/kotlin/kotlinx/kover/test/functional/core/writer/MergedExtensionWriter.kt @@ -37,8 +37,8 @@ private fun PrintWriter.printFilters(state: TestKoverMergedFiltersState, slice: if (projects != null) { indented(indents + 1, "projects {") - if (projects.includes.isNotEmpty()) { - indented(indents + 2, "includes".addAllList(projects.includes, slice.language)) + if (projects.excludes.isNotEmpty()) { + indented(indents + 2, "excludes".addAllList(projects.excludes, slice.language)) } indented(indents + 1, "}") } diff --git a/src/main/kotlin/kotlinx/kover/api/CoverageEngines.kt b/src/main/kotlin/kotlinx/kover/api/CoverageEngines.kt index b222d907..7058dd52 100644 --- a/src/main/kotlin/kotlinx/kover/api/CoverageEngines.kt +++ b/src/main/kotlin/kotlinx/kover/api/CoverageEngines.kt @@ -10,9 +10,9 @@ import org.gradle.api.tasks.* public sealed class CoverageEngineVariant( @get:Input - public val vendor: CoverageEngineVendor, + internal val vendor: CoverageEngineVendor, @get:Input - public val version: String + internal val version: String ) { override fun equals(other: Any?): Boolean { if (this === other) return true @@ -37,7 +37,7 @@ public sealed class CoverageEngineVariant( } } -public enum class CoverageEngineVendor { +internal enum class CoverageEngineVendor { INTELLIJ, JACOCO } diff --git a/src/main/kotlin/kotlinx/kover/api/KoverConfig.kt b/src/main/kotlin/kotlinx/kover/api/KoverConfig.kt index 525c55e0..ba6351b5 100644 --- a/src/main/kotlin/kotlinx/kover/api/KoverConfig.kt +++ b/src/main/kotlin/kotlinx/kover/api/KoverConfig.kt @@ -86,7 +86,7 @@ public open class KoverProjectConfig @Inject constructor(objects: ObjectFactory) @Suppress("DEPRECATION") @get:Internal @Deprecated( - message = "Property was removed in Kover API version 2. Please refer to migration guide in order to migrate: ${KoverMigrations.MIGRATION_0_5_TO_0_6}", + message = "Property was removed in Kover API version 2, use `engine` property instead. Please refer to migration guide in order to migrate: ${KoverMigrations.MIGRATION_0_5_TO_0_6}", replaceWith = ReplaceWith("engine"), level = DeprecationLevel.ERROR ) @@ -94,7 +94,7 @@ public open class KoverProjectConfig @Inject constructor(objects: ObjectFactory) @get:Internal @Deprecated( - message = "Property was removed in Kover API version 2. Please refer to migration guide in order to migrate: ${KoverMigrations.MIGRATION_0_5_TO_0_6}", + message = "Property was removed in Kover API version 2, use `engine.set(kotlinx.kover.api.IntellijEngine(\"version\"))` instead. Please refer to migration guide in order to migrate: ${KoverMigrations.MIGRATION_0_5_TO_0_6}", replaceWith = ReplaceWith("engine"), level = DeprecationLevel.ERROR ) @@ -102,7 +102,7 @@ public open class KoverProjectConfig @Inject constructor(objects: ObjectFactory) @get:Internal @Deprecated( - message = "Property was removed in Kover API version 2. Please refer to migration guide in order to migrate: ${KoverMigrations.MIGRATION_0_5_TO_0_6}", + message = "Property was removed in Kover API version 2, use `engine.set(kotlinx.kover.api.JacocoEngine(\"version\"))` instead. Please refer to migration guide in order to migrate: ${KoverMigrations.MIGRATION_0_5_TO_0_6}", replaceWith = ReplaceWith("engine"), level = DeprecationLevel.ERROR ) @@ -140,7 +140,7 @@ public open class KoverProjectConfig @Inject constructor(objects: ObjectFactory) // DEPRECATIONS // TODO delete in 0.7 version @Deprecated( - message = "Class was renamed in Kover API version 2", + message = "Class KoverExtension was renamed to KoverProjectConfig in Kover API version 2", replaceWith = ReplaceWith("KoverProjectConfig"), level = DeprecationLevel.ERROR ) @@ -152,7 +152,16 @@ public open class KoverProjectFilters @Inject constructor(private val objects: O internal val sourceSets: Property = objects.property(KoverSourceSetFilter::class.java) /** - * Configures class filter. + * Configures class filter in order to include and exclude specific classes. + * + * Example: + * ``` + * classes { + * excludes += "com.example.FooBar" + * includes += "com.example.*Bar" + * } + * ``` + * Excludes have priority over includes. */ public fun classes(config: Action) { val classFilter = objects.newInstance(KoverClassFilter::class.java) @@ -346,12 +355,12 @@ public open class KoverMergedHtmlConfig @Inject constructor(private val objects: public open class KoverProjectsFilter { /** - * Specifies the projects involved in the merged tasks. Both the project name (if it is unique) and the project path can be used. + * Specifies the projects excluded from subprojects (included current project) using in the merged tasks. Both the project name (if it is unique) and the project path can be used. * * If empty, the current project and all subprojects are used. */ @get:Input - public val includes: MutableList = mutableListOf() + public val excludes: MutableList = mutableListOf() } diff --git a/src/main/kotlin/kotlinx/kover/appliers/KoverMergedApplier.kt b/src/main/kotlin/kotlinx/kover/appliers/KoverMergedApplier.kt index 019935c6..0424a9e2 100644 --- a/src/main/kotlin/kotlinx/kover/appliers/KoverMergedApplier.kt +++ b/src/main/kotlin/kotlinx/kover/appliers/KoverMergedApplier.kt @@ -176,27 +176,30 @@ private inline fun Project.mergedFilesProvider( private fun filterProjects(filters: KoverProjectsFilter, allProjects: Iterable): List { - if (filters.includes.isEmpty()) { + if (filters.excludes.isEmpty()) { return allProjects.toList() } - val projectsByPath = allProjects.associateBy { p -> p.path } + val projectsByPath = allProjects.associateBy { p -> p.path }.toMutableMap() val pathsByName = allProjects.associate { it.name to mutableListOf() } allProjects.forEach { pathsByName.getValue(it.name) += it.path } - return filters.includes.map { + val excludedPaths = filters.excludes.map { if (it.startsWith(':')) { projectsByPath[it] ?: throw GradleException("Kover configuring error: not found project '$it' for merged tasks") + it } else { val paths = pathsByName[it] ?: throw GradleException("Kover configuring error: not found project '$it' for merged tasks") if (paths.size > 1) { throw GradleException("Kover configuring error: ambiguous name of the project '$it' for merged tasks: suitable projects with paths $paths. Consider using fully-qualified name starting with ':'") } - projectsByPath[paths[0]]!! + paths[0] } - } + }.toSet() + + return projectsByPath.filterNot { it.key in excludedPaths }.map { it.value } } diff --git a/src/main/kotlin/kotlinx/kover/tasks/Deprecations.kt b/src/main/kotlin/kotlinx/kover/tasks/Deprecations.kt new file mode 100644 index 00000000..b3f1d79c --- /dev/null +++ b/src/main/kotlin/kotlinx/kover/tasks/Deprecations.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.kover.tasks + +import kotlinx.kover.api.* +import org.gradle.api.* +import org.gradle.api.file.* +import org.gradle.api.tasks.Internal + +@Deprecated( + message = "Class was removed in Kover API version 2, use Kover project extension instead. Please refer to migration guide in order to migrate: ${KoverMigrations.MIGRATION_0_5_TO_0_6}", + level = DeprecationLevel.ERROR +) +open class KoverHtmlReportTask : DefaultTask() { + @Deprecated( + message = "Property was removed in Kover API version 2, use property 'htmlReport { reportDir }' in Kover project extension instead. Please refer to migration guide in order to migrate: ${KoverMigrations.MIGRATION_0_5_TO_0_6}", + level = DeprecationLevel.ERROR + ) + @get:Internal + val htmlReportDir: DirectoryProperty = project.objects.directoryProperty() +} diff --git a/src/main/kotlin/kotlinx/kover/tasks/KoverHtmlTask.kt b/src/main/kotlin/kotlinx/kover/tasks/KoverHtmlTask.kt index 50788360..ff7f324b 100644 --- a/src/main/kotlin/kotlinx/kover/tasks/KoverHtmlTask.kt +++ b/src/main/kotlin/kotlinx/kover/tasks/KoverHtmlTask.kt @@ -49,14 +49,14 @@ public open class KoverHtmlTask : KoverReportTask() { @get:Internal @Deprecated( - message = "Property was removed in Kover API version 2. Please refer to migration guide in order to migrate: ${KoverMigrations.MIGRATION_0_5_TO_0_6}", + message = "Property was removed in Kover API version 2, use property 'filters { classes { includes } }' in Kover project extension instead. Please refer to migration guide in order to migrate: ${KoverMigrations.MIGRATION_0_5_TO_0_6}", level = DeprecationLevel.ERROR ) public var includes: List = emptyList() @get:Internal @Deprecated( - message = "Property was removed in Kover API version 2. Please refer to migration guide in order to migrate: ${KoverMigrations.MIGRATION_0_5_TO_0_6}", + message = "Property was removed in Kover API version 2, use property 'filters { classes { excludes } }' in Kover project extension instead. Please refer to migration guide in order to migrate: ${KoverMigrations.MIGRATION_0_5_TO_0_6}", level = DeprecationLevel.ERROR ) public var excludes: List = emptyList() diff --git a/src/main/kotlin/kotlinx/kover/tasks/KoverVerificationTask.kt b/src/main/kotlin/kotlinx/kover/tasks/KoverVerificationTask.kt index 04cacdc1..69421c4b 100644 --- a/src/main/kotlin/kotlinx/kover/tasks/KoverVerificationTask.kt +++ b/src/main/kotlin/kotlinx/kover/tasks/KoverVerificationTask.kt @@ -60,7 +60,7 @@ public open class KoverVerificationTask : KoverReportTask() { // TODO delete in 0.7 version @Suppress("UNUSED_PARAMETER") @Deprecated( - message = "Function was removed in Kover API version 2. Please refer to migration guide in order to migrate: ${KoverMigrations.MIGRATION_0_5_TO_0_6}", + message = "Function was removed in Kover API version 2, move it in 'verify { }' in Kover project extension instead. Please refer to migration guide in order to migrate: ${KoverMigrations.MIGRATION_0_5_TO_0_6}", level = DeprecationLevel.ERROR ) public fun rule(configureRule: Action) { From 75a84d3cb84610d2d1660e55a0cb4ed1237ea874 Mon Sep 17 00:00:00 2001 From: "Sergey.Shanshin" Date: Thu, 28 Jul 2022 11:50:25 +0200 Subject: [PATCH 09/10] improved migration guide --- docs/migration-to-0.6.0.md | 151 ++++++++++++++++++++++++++++++++++--- 1 file changed, 140 insertions(+), 11 deletions(-) diff --git a/docs/migration-to-0.6.0.md b/docs/migration-to-0.6.0.md index 50669036..1c8da9ca 100644 --- a/docs/migration-to-0.6.0.md +++ b/docs/migration-to-0.6.0.md @@ -1,20 +1,149 @@ +The new API allows you to configure Kover in a more flexible manner, while being more concise than the previous API. +From now on, there is no need to configure Kover or test tasks separately. + # Main differences +- applying the plugin to the root project no longer causes it to be recursively applied to all subprojects - you must explicitly apply it to all projects that will be covered +- merged tasks are not created by default. You must explicitly enable it if necessary (for details [see](#merged-report-changes)) +- the extension `kover {}` is used to configure tasks `koverXmlReport`, `koverHtmlReport`, `koverVerify`, test tasks, instead of configuring these tasks directly +- the extension `koverMerged {}` is used to configure tasks `koverMergedXmlReport`, `koverMergedHtmlReport`, `koverMergedVerify`, instead of configuring these tasks directly +- task `koverCollectReports` was removed -The new API allows you to configure Kover in a more flexible manner, while being more concise than the previous API. -From now on, there is no need to configure each Kover task separately. +# Merged report changes -In the new API, in order to respect upcoming Gradle conventions, the plugin should be explicitly applied to -each project that needs coverage. -To create merged tasks (that collect test coverage from different projects), enable it by `koverMerged.enable()` or +In the new API, merged tasks are not created by default. To create them, you need to use the plugin in the `koverMerged` extension, it is necessary to call the function `enable()`. +e.g. +``` +koverMerged.enable() +``` +or ``` koverMerged { enable() } ``` -in one project, which will be a merged report container. +or +``` +extensions.configure { + enable() +} +``` + +Now any merged tasks settings occur only in the `koverMerged` extension. +By default, tasks use the results of measuring the coverage of the project in which they were created and all subprojects. +At the same time, it is important that the Kover plugin is used in all these projects, as well as that they use the same variant (vendor and version) of the coverage engine. + +For example, it can be done this way +``` +buildscript { + repositories { + mavenCentral() + } + + dependencies { + classpath("org.jetbrains.kotlinx:kover:0.6.0-BETA") + } +} + +apply(plugin = "kover") + +extensions.configure { + enable() + // configure merged tasks +} + +allprojects { + apply(plugin = "kover") + + extensions.configure { + // `true` - to disable the collection of coverage metrics for tests from this project + isDisabled.set(false) + + // configure engine variant + engine.set(kotlinx.kover.api.IntellijEngine("1.0.657")) + + // configure project's tasks if needed + } +} +``` + +In order not to use measurements from some projects now instead of `disabledProjects` property for `kover` extension you need to use `koverMerged` extension: +``` +extensions.configure { + enable() + filters { + projects { + excludes.addAll("project-to-exclude", ":path:to:exclude") + } + } +} +``` + +Instead of configuring the merged XML report task +``` +koverMergedXmlReport { + // config task +} +``` +or +``` +tasks.withType { + // config task +} +``` +you need to configure `koverMerged` extension: +``` +extensions.configure { + enable() + xmlReport { + // config task + } +} +``` + +Instead of configuring the merged HTML report task +``` +koverMergedHtmlReport { + // config task +} +``` +or +``` +tasks.withType { + // config task +} +``` +you need to configure `koverMerged` extension: +``` +extensions.configure { + enable() + htmlReport { + // config task + } +} +``` -To configure reports that collect coverage only for tests from one project, the `kover { }` project extension is used. -To configure merged reports, the `koverMerged { }` project extension is used. + +Instead of configuring the merged verification report task +``` +koverMergedVerify { + // config task +} +``` +or +``` +tasks.withType { + // config task +} +``` +you need to configure `koverMerged` extension: +``` +extensions.configure { + enable() + verify { + // config task + } +} +``` # Migration Issues @@ -22,7 +151,7 @@ To configure merged reports, the `koverMerged { }` project extension is used. ### type of `isDisabled` property changed from `Boolean` to `Property`. -_Error message_ +_Error message:_ ``` Val cannot be reassigned @@ -88,7 +217,7 @@ _Error message:_ _Solution_ -- read about [merged reports changes](#foo) +- read about [merged reports changes](#merged-report-changes) - use exclusion list in project filters of merged configuration extension ``` @@ -111,7 +240,7 @@ instrumentation. ### property `runAllTestsForProjectTask` was removed -TBD +In the new API for single-project reports, it is impossible to call test tasks of another project. To account for coverage from tests of another module, use merged reports. ## Kover extension for test task From 8a5a861cc432d13465e7eb36403c6d9751b97e94 Mon Sep 17 00:00:00 2001 From: "Sergey.Shanshin" Date: Thu, 28 Jul 2022 12:05:36 +0200 Subject: [PATCH 10/10] Added link to the issue --- src/main/kotlin/kotlinx/kover/appliers/KoverTaskApplier.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/kotlinx/kover/appliers/KoverTaskApplier.kt b/src/main/kotlin/kotlinx/kover/appliers/KoverTaskApplier.kt index 49b82d08..0e1853e1 100644 --- a/src/main/kotlin/kotlinx/kover/appliers/KoverTaskApplier.kt +++ b/src/main/kotlin/kotlinx/kover/appliers/KoverTaskApplier.kt @@ -81,7 +81,7 @@ private class CoverageArgumentProvider( Because android classes are not part of the project, in any case they do not get into the report, and they can be excluded from instrumentation. - FIXME Remove this code if the IntelliJ Agent stops changing project classes during instrumentation + 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.*"