diff --git a/kover-features-jvm/build.gradle.kts b/kover-features-jvm/build.gradle.kts index b6a0b18e..a396c46b 100644 --- a/kover-features-jvm/build.gradle.kts +++ b/kover-features-jvm/build.gradle.kts @@ -28,13 +28,10 @@ extensions.configure Unit) { + TextMatcherImpl(this@CheckerContextImpl, this).matcher() + } + override fun checkDefaultBinReport(mustExist: Boolean) { if (mustExist) { file(defaultBinReport) { @@ -416,6 +420,31 @@ private class VerifyReportCheckerImpl(val context: CheckerContextImpl, val conte } } +private class TextMatcherImpl(val context: CheckerContextImpl, val content: String) : TextMatcher { + override fun assertContains(expected: String) { + val regex = KoverFeatures.koverWildcardToRegex(expected).toRegex() + if (!content.contains(regex)) { + throw AssertionError("Unexpected text.\n\tActual\n[\n$content\n]\nExpected regex\n[\n$expected\n]") + } + } + + override fun assertKoverContains(expected: String) { + if (context.project.toolVariant.vendor != CoverageToolVendor.KOVER) return + val regex = KoverFeatures.koverWildcardToRegex(expected).toRegex() + if (!content.contains(regex)) { + throw AssertionError("Unexpected text for Kover Tool.\n\tActual\n[\n$content\n]\nExpected regex\n[\n$expected\n]") + } + } + + override fun assertJaCoCoContains(expected: String) { + if (context.project.toolVariant.vendor != CoverageToolVendor.JACOCO) return + val regex = KoverFeatures.koverWildcardToRegex(expected).toRegex() + if (!content.contains(regex)) { + throw AssertionError("Unexpected text for JaCoCo Tool.\n\tActual\n[\n$content\n]\nExpected regex\n[\n$expected\n]") + } + } +} + private fun Element.filter(tag: String, attributeName: String, attributeValue: String): Element? { val elements = getElementsByTagName(tag) for (i in 0 until elements.length) { diff --git a/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/checker/CheckerTypes.kt b/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/checker/CheckerTypes.kt index 9b7a4bb0..ebcb9cb9 100644 --- a/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/checker/CheckerTypes.kt +++ b/kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/checker/CheckerTypes.kt @@ -33,6 +33,8 @@ internal interface CheckerContext { fun verification(checker: VerifyReportChecker.() -> Unit) + fun String.match(matcher: TextMatcher.() -> Unit) + val defaultBinReport: String fun checkXmlReport(variantName: String = "", mustExist: Boolean = true) @@ -89,6 +91,12 @@ internal interface VerifyReportChecker { fun assertJaCoCoResult(expected: String) } +internal interface TextMatcher { + fun assertContains(expected: String) + fun assertKoverContains(expected: String) + fun assertJaCoCoContains(expected: String) +} + internal interface XmlReportChecker { fun classCounter(className: String, type: String = "INSTRUCTION"): Counter fun methodCounter(className: String, methodName: String, type: String = "INSTRUCTION"): Counter diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/appliers/tasks/VariantReportsSet.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/appliers/tasks/VariantReportsSet.kt index eacf5a66..1a669ea8 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/appliers/tasks/VariantReportsSet.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/appliers/tasks/VariantReportsSet.kt @@ -35,7 +35,7 @@ internal class VariantReportsSet( private val htmlTask: TaskProvider private val xmlTask: TaskProvider private val binTask: TaskProvider - private val verifyTask: TaskProvider + private val doVerifyTask: TaskProvider private val logTask: TaskProvider init { @@ -52,10 +52,12 @@ internal class VariantReportsSet( "Task to generate binary coverage report in IntelliJ format for ${variantSuffix()}" ) - verifyTask = project.tasks.createReportTask( - verifyTaskName(variantName), + doVerifyTask = project.tasks.createReportTask( + verifyCachedTaskName(variantName), "Task to validate coverage bounding rules for ${variantSuffix()}" ) + val verifyTask = project.tasks.register(verifyTaskName(variantName)) + logTask = project.tasks.createReportTask( logTaskName(variantName), "Task to print coverage to log for ${variantSuffix()}" @@ -93,20 +95,37 @@ internal class VariantReportsSet( if (run) listOf(binTask) else emptyList() } - verifyTask.configure { + + doVerifyTask.configure { val resultRules = config.verify.rules val converted = resultRules.map { rules -> rules.map { it.convert() } } + filters.set((config.filters).convert()) + rules.addAll(converted) + // path can't be changed resultFile.convention(project.layout.buildDirectory.file(verificationErrorsPath(variantName))) - filters.set((config.filters).convert()) - rules.addAll(converted) + description = "Cacheable task for performing verification for ${variantSuffix()}" + } + verifyTask.configure { + warningInsteadOfFailure.convention(config.verify.warningInsteadOfFailure) + errorFile.convention(doVerifyTask.flatMap { it.resultFile }) shouldRunAfter(htmlTask) shouldRunAfter(xmlTask) shouldRunAfter(binTask) shouldRunAfter(logTask) + + dependsOn(doVerifyTask) + + group = LifecycleBasePlugin.VERIFICATION_GROUP + + // always execute + outputs.upToDateWhen { false } + + val koverDisabledProvider = koverDisabled + onlyIf { !koverDisabledProvider.get() } } runOnCheck += config.verify.onCheck.map { run -> if (run) listOf(verifyTask) else emptyList() @@ -117,6 +136,9 @@ internal class VariantReportsSet( onlyIf { fileWithMessage.asFile.get().exists() } + + // always execute + outputs.upToDateWhen { false } } logTask.configure { @@ -146,7 +168,7 @@ internal class VariantReportsSet( htmlTask.assign(variant) xmlTask.assign(variant) binTask.assign(variant) - verifyTask.assign(variant) + doVerifyTask.assign(variant) logTask.assign(variant) } diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/commons/Naming.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/commons/Naming.kt index 30a1606a..222fef40 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/commons/Naming.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/commons/Naming.kt @@ -84,6 +84,10 @@ internal fun xmlReportTaskName(variant: String) = "$XML_REPORT_NAME${variant.cap */ internal fun binaryReportTaskName(variant: String) = "$BINARY_REPORT_NAME${variant.capitalized()}" +/** + * Name for cached verifying task for specified report namespace. + */ +internal fun verifyCachedTaskName(variant: String) = "koverCachedVerify${variant.capitalized()}" /** * Name for verifying task for specified report namespace. */ diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/dsl/KoverReportsConfig.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/dsl/KoverReportsConfig.kt index 703093fc..9abcf61a 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/dsl/KoverReportsConfig.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/dsl/KoverReportsConfig.kt @@ -103,6 +103,9 @@ public interface KoverReportsConfig { * rule("custom rule name") { * // named verification rule * } + * + * // fail on verification error + * warningInsteadOfFailure = false * } * ``` */ @@ -313,6 +316,9 @@ public interface KoverReportSetConfig { * rule("Custom Name") { * // ... * } + * + * // fail on verification error + * warningInsteadOfFailure = false * } * ``` */ @@ -884,6 +890,9 @@ public interface KoverBinaryTaskConfig { * rule("Custom Name") { * // ... * } + * + * // fail on verification error + * warningInsteadOfFailure = false * } * ``` */ @@ -910,6 +919,9 @@ public interface KoverVerifyTaskConfig: KoverVerificationRulesConfig { * rule("custom rule name") { * // named verification rule * } + * + * // fail on verification error + * warningInsteadOfFailure = false * } * ``` */ @@ -926,6 +938,15 @@ public interface KoverVerificationRulesConfig { * The name will be displayed in case of a verification error if Kover Tool was used. */ public fun rule(name: String, config: Action) + + /** + * In case of a verification error, print a message to the log with the warn level instead of the Gradle task execution error. + * + * Gradle task error if `false`, warn message if `true`. + * + * `false` by default. + */ + public val warningInsteadOfFailure: Property } /** diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/dsl/internal/ReportsImpl.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/dsl/internal/ReportsImpl.kt index fb79fa31..1bde791a 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/dsl/internal/ReportsImpl.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/dsl/internal/ReportsImpl.kt @@ -137,6 +137,11 @@ internal abstract class KoverVerificationRulesConfigImpl @Inject constructor( ) : KoverVerificationRulesConfig { internal abstract val rules: ListProperty + init { + @Suppress("LeakingThis") + warningInsteadOfFailure.convention(false) + } + override fun rule(config: Action) { val newRule = objects.newInstance(objects, "") config(newRule) @@ -152,6 +157,7 @@ internal abstract class KoverVerificationRulesConfigImpl @Inject constructor( internal fun extendFrom(other: KoverVerificationRulesConfigImpl) { rules.addAll(other.rules) + warningInsteadOfFailure.convention(other.warningInsteadOfFailure) } internal fun clean() { diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/reports/KoverDoVerifyTask.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/reports/KoverDoVerifyTask.kt new file mode 100644 index 00000000..8400490d --- /dev/null +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/reports/KoverDoVerifyTask.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.kover.gradle.plugin.tasks.reports + +import kotlinx.kover.gradle.plugin.commons.VerificationRule +import kotlinx.kover.gradle.plugin.tools.generateErrorMessage +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.ListProperty +import org.gradle.api.tasks.* + +@CacheableTask +internal abstract class KoverDoVerifyTask : AbstractKoverReportTask() { + @get:Nested + abstract val rules: ListProperty + + @get:OutputFile + abstract val resultFile: RegularFileProperty + + @TaskAction + fun verify() { + val enabledRules = rules.get().filter { it.isEnabled } + val violations = tool.get().verify(enabledRules, context()) + + val errorMessage = generateErrorMessage(violations) + resultFile.get().asFile.writeText(errorMessage) + } + +} diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/reports/KoverVerifyTask.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/reports/KoverVerifyTask.kt index 50b0fc96..ec52d43e 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/reports/KoverVerifyTask.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/reports/KoverVerifyTask.kt @@ -4,27 +4,35 @@ package kotlinx.kover.gradle.plugin.tasks.reports -import kotlinx.kover.gradle.plugin.commons.VerificationRule +import kotlinx.kover.gradle.plugin.commons.KoverVerificationException import kotlinx.kover.gradle.plugin.dsl.tasks.KoverVerifyReport +import org.gradle.api.DefaultTask import org.gradle.api.file.RegularFileProperty -import org.gradle.api.provider.ListProperty -import org.gradle.api.tasks.CacheableTask -import org.gradle.api.tasks.Nested -import org.gradle.api.tasks.OutputFile -import org.gradle.api.tasks.TaskAction +import org.gradle.api.provider.Property +import org.gradle.api.tasks.* +import org.gradle.work.DisableCachingByDefault -@CacheableTask -internal abstract class KoverVerifyTask : AbstractKoverReportTask(), KoverVerifyReport { - @get:Nested - abstract val rules: ListProperty +@DisableCachingByDefault +internal abstract class KoverVerifyTask : DefaultTask(), KoverVerifyReport { + @get:Input + abstract val warningInsteadOfFailure: Property - @get:OutputFile - abstract val resultFile: RegularFileProperty + @get:InputFile + abstract val errorFile: RegularFileProperty @TaskAction fun verify() { - val enabledRules = rules.get().filter { it.isEnabled } - tool.get().verify(enabledRules, resultFile.get().asFile, context()) + val errorMessage = errorFile.get().asFile.readText() + if (errorMessage.isEmpty()) { + // no errors + return + } + + if (warningInsteadOfFailure.get()) { + logger.warn("Kover Verification Error\n$errorMessage") + } else { + throw KoverVerificationException(errorMessage) + } } } diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/CoverageTool.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/CoverageTool.kt index e0ad49de..d78269fe 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/CoverageTool.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/CoverageTool.kt @@ -4,6 +4,7 @@ package kotlinx.kover.gradle.plugin.tools +import kotlinx.kover.features.jvm.RuleViolations import kotlinx.kover.gradle.plugin.commons.* import kotlinx.kover.gradle.plugin.commons.VerificationRule import kotlinx.kover.gradle.plugin.dsl.* @@ -112,7 +113,7 @@ internal interface CoverageTool { /** * Perform verification. */ - fun verify(rules: List, outputFile: File, context: ReportContext) + fun verify(rules: List, context: ReportContext): List /** * Calculate coverage according to the specified parameters [request], for each grouped entity. diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/Verification.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/Verification.kt index 0e90f6b6..226af0c7 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/Verification.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/Verification.kt @@ -43,6 +43,9 @@ internal data class CoverageRequest( ): Serializable internal fun generateErrorMessage(violations: List): String { + if (violations.isEmpty()) { + return "" + } val messageBuilder = StringBuilder() violations.forEach { rule -> diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/JacocoTool.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/JacocoTool.kt index 403877b2..05aa9107 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/JacocoTool.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/JacocoTool.kt @@ -4,6 +4,8 @@ package kotlinx.kover.gradle.plugin.tools.jacoco +import kotlinx.kover.features.jvm.KoverLegacyFeatures +import kotlinx.kover.features.jvm.RuleViolations import kotlinx.kover.gradle.plugin.commons.ReportContext import kotlinx.kover.gradle.plugin.commons.VerificationRule import kotlinx.kover.gradle.plugin.tools.CoverageRequest @@ -47,8 +49,8 @@ internal class JacocoTool(override val variant: CoverageToolVariant) : CoverageT throw GradleException("It is not possible to generate an Kover binary report for JaCoCo. Please use Kover toolset") } - override fun verify(rules: List, outputFile: File, context: ReportContext) { - context.jacocoVerify(rules, outputFile) + override fun verify(rules: List, context: ReportContext): List { + return context.doJacocoVerify(rules) } override fun collectCoverage( diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/Verification.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/Verification.kt index be9b5eea..5c598d6a 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/Verification.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/jacoco/Verification.kt @@ -11,32 +11,15 @@ import kotlinx.kover.features.jvm.Rule import kotlinx.kover.features.jvm.RuleViolations import kotlinx.kover.gradle.plugin.commons.* import kotlinx.kover.gradle.plugin.dsl.AggregationType -import kotlinx.kover.gradle.plugin.dsl.GroupingEntityType import kotlinx.kover.gradle.plugin.dsl.CoverageUnit -import kotlinx.kover.gradle.plugin.tools.generateErrorMessage +import kotlinx.kover.gradle.plugin.dsl.GroupingEntityType import kotlinx.kover.gradle.plugin.tools.kover.convert import kotlinx.kover.gradle.plugin.util.ONE_HUNDRED import org.gradle.internal.reflect.JavaMethod -import java.io.File import java.math.BigDecimal import java.util.* -internal fun ReportContext.jacocoVerify( - rules: List, - outputFile: File -) { - val violations = doJacocoVerify(rules) - - val errorMessage = generateErrorMessage(violations) - outputFile.writeText(errorMessage) - - if (violations.isNotEmpty()) { - throw KoverVerificationException(errorMessage) - } -} - - internal fun ReportContext.doJacocoVerify(rules: List): List { callAntReport(projectPath) { diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverTool.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverTool.kt index 3707ad0f..9affbb2a 100644 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverTool.kt +++ b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/KoverTool.kt @@ -5,6 +5,8 @@ package kotlinx.kover.gradle.plugin.tools.kover import kotlinx.kover.features.jvm.KoverFeatures +import kotlinx.kover.features.jvm.KoverLegacyFeatures +import kotlinx.kover.features.jvm.RuleViolations import kotlinx.kover.gradle.plugin.commons.ReportContext import kotlinx.kover.gradle.plugin.commons.VerificationRule import kotlinx.kover.gradle.plugin.tools.CoverageRequest @@ -53,10 +55,15 @@ internal class KoverTool(override val variant: CoverageToolVariant) : CoverageTo override fun verify( rules: List, - outputFile: File, context: ReportContext - ) { - context.koverVerify(rules, outputFile) + ): List{ + return KoverLegacyFeatures.verify( + rules.map { it.convert() }, + context.tempDir, + context.filters.toKoverFeatures(), + context.files.reports.toList(), + context.files.outputs.toList() + ) } override fun collectCoverage(request: CoverageRequest, outputFile: File, context: ReportContext) { diff --git a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/Verification.kt b/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/Verification.kt deleted file mode 100644 index 0b96524c..00000000 --- a/kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tools/kover/Verification.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.kover.gradle.plugin.tools.kover - -import kotlinx.kover.features.jvm.KoverLegacyFeatures -import kotlinx.kover.gradle.plugin.commons.* -import kotlinx.kover.gradle.plugin.tools.generateErrorMessage -import java.io.File - -internal fun ReportContext.koverVerify(specifiedRules: List, outputReportFile: File) { - val violations = KoverLegacyFeatures.verify( - specifiedRules.map { it.convert() }, - tempDir, - filters.toKoverFeatures(), - files.reports.toList(), - files.outputs.toList() - ) - - val errorMessage = generateErrorMessage(violations) - outputReportFile.writeText(errorMessage) - - if (violations.isNotEmpty()) { - throw KoverVerificationException(errorMessage) - } -}