All Downloads are FREE. Search and download functionalities are using the official Maven repository.

kotlinx.kover.gradle.plugin.tools.jacoco.Verification.kt Maven / Gradle / Ivy

There is a newer version: 0.9.0-RC
Show newest version
/*
 * 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.jacoco

import groovy.lang.GroovyObject
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.MetricType
import kotlinx.kover.gradle.plugin.tools.BoundViolations
import kotlinx.kover.gradle.plugin.tools.RuleViolations
import kotlinx.kover.gradle.plugin.tools.generateErrorMessage
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) {
        invokeWithBody("check", mapOf("failonviolation" to "false", "violationsproperty" to "jacocoErrors")) {
            rules.forEach {
                val entityType = when (it.entityType) {
                    GroupingEntityType.APPLICATION -> "BUNDLE"
                    GroupingEntityType.CLASS -> "CLASS"
                    GroupingEntityType.PACKAGE -> "PACKAGE"
                }
                invokeWithBody("rule", mapOf("element" to entityType)) {
                    it.bounds.forEach { b ->
                        val limitArgs = mutableMapOf()
                        limitArgs["counter"] = when (b.metric) {
                            MetricType.LINE -> "LINE"
                            MetricType.INSTRUCTION -> "INSTRUCTION"
                            MetricType.BRANCH -> "BRANCH"
                        }

                        var min: BigDecimal? = b.minValue
                        var max: BigDecimal? = b.maxValue
                        when (b.aggregation) {
                            AggregationType.COVERED_COUNT -> {
                                limitArgs["value"] = "COVEREDCOUNT"
                            }

                            AggregationType.MISSED_COUNT -> {
                                limitArgs["value"] = "MISSEDCOUNT"
                            }

                            AggregationType.COVERED_PERCENTAGE -> {
                                limitArgs["value"] = "COVEREDRATIO"
                                min = min?.divide(ONE_HUNDRED)?.setScale(4)
                                max = max?.divide(ONE_HUNDRED)?.setScale(4)
                            }

                            AggregationType.MISSED_PERCENTAGE -> {
                                limitArgs["value"] = "MISSEDRATIO"
                                min = min?.divide(ONE_HUNDRED)?.setScale(4)
                                max = max?.divide(ONE_HUNDRED)?.setScale(4)
                            }
                        }

                        if (min != null) {
                            limitArgs["minimum"] = min.toPlainString()
                        }

                        if (max != null) {
                            limitArgs["maximum"] = max.toPlainString()
                        }
                        invokeMethod("limit", limitArgs)
                    }
                }
            }
        }
    }

    return services.antBuilder.violations()
}


private val errorMessageRegex =
    "Rule violated for (\\w+) (.+): (\\w+) (.+) is ([\\d\\.]+), but expected (\\w+) is ([\\d\\.]+)".toRegex()

private fun GroovyObject.violations(): List {
    val project = getProperty("project")
    val properties = JavaMethod.of(
        project,
        Hashtable::class.java, "getProperties"
    ).invoke(project, *arrayOfNulls(0))
    val allErrorsString = properties["jacocoErrors"] as String? ?: return emptyList()

    // sorting lines to get a stable order of errors
    return allErrorsString.lines().sorted().map {
        val match = errorMessageRegex.find(it)
            ?: throw KoverCriticalException("Can't parse JaCoCo verification error string:\n$it")

        val entityType = match.groupValues[1].asEntityType(it)
        val entityName = match.groupValues[2].run { if (this == ":") null else this }
        val metric = match.groupValues[3].asMetricType(it)
        val agg = match.groupValues[4].asAggType(it)
        val value = match.groupValues[5].asValue(it, agg)
        val isMax = match.groupValues[6].asIsMax(it)
        val expected = match.groupValues[7].asValue(it, agg)

        RuleViolations(entityType, listOf(BoundViolations(isMax, expected, value, metric, agg, entityName)))
    }.toList()
}

private fun String.asEntityType(line: String): GroupingEntityType = when (this) {
    "bundle" -> GroupingEntityType.APPLICATION
    "class" -> GroupingEntityType.CLASS
    "package" -> GroupingEntityType.PACKAGE
    else -> throw KoverCriticalException("Unknown JaCoCo entity type '$this' in verification error:\n$line")
}

private fun String.asMetricType(line: String): MetricType = when (this) {
    "lines" -> MetricType.LINE
    "instructions" -> MetricType.INSTRUCTION
    "branches" -> MetricType.BRANCH
    else -> throw KoverCriticalException("Unknown JaCoCo metric type '$this' in verification error:\n$line")
}

private fun String.asAggType(line: String): AggregationType = when (this) {
    "covered ratio" -> AggregationType.COVERED_PERCENTAGE
    "missed ratio" -> AggregationType.MISSED_PERCENTAGE
    "covered count" -> AggregationType.COVERED_COUNT
    "missed count" -> AggregationType.MISSED_COUNT
    else -> throw KoverCriticalException("Unknown JaCoCo aggregation type '$this' in verification error:\n$line")
}

private fun String.asIsMax(line: String): Boolean = when (this) {
    "minimum" -> false
    "maximum" -> true
    else -> throw KoverCriticalException("Unknown JaCoCo direction '$this' in verification error:\n$line")
}

private fun String.asValue(line: String, aggregationType: AggregationType): BigDecimal {
    val value = toBigDecimalOrNull()
        ?: throw KoverCriticalException("Illegal JaCoCo metric value '$this' in verification error:\n$line")

    return if (aggregationType.isPercentage) {
        value * ONE_HUNDRED
    } else {
        value
    }
}







© 2015 - 2025 Weber Informatics LLC | Privacy Policy