| 
 | 1 | +package org.scoverage  | 
 | 2 | + | 
 | 3 | +import groovy.xml.XmlParser  | 
 | 4 | +import org.gradle.api.GradleException  | 
 | 5 | +import org.gradle.internal.impldep.com.google.common.annotations.VisibleForTesting  | 
 | 6 | +import org.slf4j.Logger  | 
 | 7 | +import java.io.File  | 
 | 8 | +import java.io.FileNotFoundException  | 
 | 9 | + | 
 | 10 | +import java.text.DecimalFormat  | 
 | 11 | +import java.text.NumberFormat  | 
 | 12 | +import java.util.*  | 
 | 13 | +import kotlin.jvm.Throws  | 
 | 14 | + | 
 | 15 | +/**  | 
 | 16 | + * Handles different types of coverage Scoverage can measure.  | 
 | 17 | + *  | 
 | 18 | + * @param configurationName Name of enum option the way it appears in the build configuration.  | 
 | 19 | + * @param fileName Name of file with coverage data.  | 
 | 20 | + * @param paramName Name of param in XML file with coverage value.  | 
 | 21 | + * @param factor Used to normalize coverage value.  | 
 | 22 | + */  | 
 | 23 | +enum class CoverageType(  | 
 | 24 | +    private val configurationName: String,  | 
 | 25 | +    val fileName: String,  | 
 | 26 | +    val paramName: String,  | 
 | 27 | +    private val factor: Double  | 
 | 28 | +) {  | 
 | 29 | +    Line("Line", "cobertura.xml", "line-rate", 1.0),  | 
 | 30 | +    Statement("Statement", "scoverage.xml", "statement-rate", 100.0),  | 
 | 31 | +    Branch("Branch", "scoverage.xml", "branch-rate", 100.0);  | 
 | 32 | + | 
 | 33 | +    /** Normalize coverage value to [0, 1] */  | 
 | 34 | +    fun normalize(value: Double): Double = value / factor  | 
 | 35 | + | 
 | 36 | +    companion object {  | 
 | 37 | +        fun find(configurationName: String): CoverageType? {  | 
 | 38 | +            return values().find { it -> it.configurationName.lowercase() == configurationName.lowercase() }  | 
 | 39 | +        }  | 
 | 40 | +    }  | 
 | 41 | +}  | 
 | 42 | + | 
 | 43 | +/**  | 
 | 44 | + * Throws a GradleException if overall coverage dips below the configured percentage.  | 
 | 45 | + */  | 
 | 46 | +class CoverageChecker(private val logger: Logger) {  | 
 | 47 | + | 
 | 48 | +    @JvmOverloads  | 
 | 49 | +    @Throws(GradleException::class)  | 
 | 50 | +    fun checkLineCoverage(  | 
 | 51 | +        reportDir: File,  | 
 | 52 | +        coverageType: CoverageType,  | 
 | 53 | +        minimumRate: Double,  | 
 | 54 | +        nf: NumberFormat = NumberFormat.getInstance(Locale.getDefault())  | 
 | 55 | +    ) {  | 
 | 56 | +        logger.info("Checking coverage. Type: {}. Minimum rate: {}", coverageType, minimumRate)  | 
 | 57 | + | 
 | 58 | +        val parser = XmlParser()  | 
 | 59 | +        parser.setFeature("http://apache.org/xml/features/disallow-doctype-decl", false)  | 
 | 60 | +        parser.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false)  | 
 | 61 | + | 
 | 62 | +        val df = DecimalFormat("#.##")  | 
 | 63 | + | 
 | 64 | +        try {  | 
 | 65 | +            val reportFile = File(reportDir, coverageType.fileName)  | 
 | 66 | +            val xml = parser.parse(reportFile)  | 
 | 67 | +            val coverageValue: Double = nf.parse(xml.attribute(coverageType.paramName) as String).toDouble()  | 
 | 68 | +            val overallRate: Double = coverageType.normalize(coverageValue)  | 
 | 69 | + | 
 | 70 | +            val difference = minimumRate - overallRate  | 
 | 71 | + | 
 | 72 | +            if (difference > 1e-7) {  | 
 | 73 | +                val iss = df.format(overallRate * 100)  | 
 | 74 | +                val needed = df.format(minimumRate * 100)  | 
 | 75 | +                throw GradleException(errorMsg(iss, needed, coverageType))  | 
 | 76 | +            }  | 
 | 77 | +        } catch (fnfe: FileNotFoundException) {  | 
 | 78 | +            throw GradleException(fileNotFoundErrorMsg(coverageType), fnfe)  | 
 | 79 | +        }  | 
 | 80 | +    }  | 
 | 81 | + | 
 | 82 | +    companion object {  | 
 | 83 | +        @VisibleForTesting  | 
 | 84 | +        internal fun errorMsg(actual: String, expected: String, type: CoverageType): String {  | 
 | 85 | +            return "Only $actual% of project is covered by tests instead of $expected% (coverageType: $type)"  | 
 | 86 | +        }  | 
 | 87 | + | 
 | 88 | +        @VisibleForTesting  | 
 | 89 | +        internal fun fileNotFoundErrorMsg(coverageType: CoverageType): String {  | 
 | 90 | +            return "Coverage file (type: $coverageType) not found, check your configuration."  | 
 | 91 | +        }  | 
 | 92 | +    }  | 
 | 93 | + | 
 | 94 | +}  | 
0 commit comments