Skip to content

Commit cc98ce1

Browse files
committed
scoverage#171 Migrate CoverageChecker (and its test) from Groovy to Kotlin
1 parent 6e95ca2 commit cc98ce1

File tree

4 files changed

+136
-131
lines changed

4 files changed

+136
-131
lines changed

build.gradle.kts

+15
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ plugins {
22
`java-gradle-plugin`
33
id("com.gradle.plugin-publish") version "0.15.0"
44
id("org.jetbrains.gradle.plugin.idea-ext") version "1.0"
5+
kotlin("jvm") version "1.5.31"
56
}
67

78
repositories {
@@ -38,17 +39,30 @@ pluginBundle {
3839
}
3940

4041
apply(plugin = "maven-publish")
42+
// TODO #171 remove once all groovy classes are migrated to kotlin
4143
apply(plugin = "groovy")
4244

4345
java {
4446
sourceCompatibility = JavaVersion.VERSION_1_8
4547
targetCompatibility = JavaVersion.VERSION_1_8
4648
}
4749

50+
// TODO #171 remove once all groovy classes are migrated to kotlin
51+
val compileKotlinTask = tasks.compileKotlin.get()
52+
tasks {
53+
named<GroovyCompile>("compileGroovy") {
54+
dependsOn(compileKotlin)
55+
classpath = classpath.plus(files(compileKotlinTask.destinationDirectory))
56+
}
57+
}
58+
4859
dependencies {
4960
compileOnly("org.scoverage:scalac-scoverage-plugin_2.13:1.4.2")
5061
implementation(group = "commons-io", name = "commons-io", version = "2.6")
5162

63+
implementation(kotlin("script-runtime"))
64+
testImplementation(kotlin("test"))
65+
5266
testImplementation("junit:junit:4.12")
5367
testImplementation("org.junit.jupiter:junit-jupiter-api:5.5.2")
5468
testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.5.2")
@@ -122,6 +136,7 @@ gradlePlugin {
122136
testSourceSets(sourceSets["functionalTest"], sourceSets["crossScalaVersionTest"])
123137
}
124138

139+
// TODO #171 remove once all groovy classes are migrated to kotlin
125140
val groovydocJar by tasks.registering(Jar::class) {
126141
from("$buildDir/docs/groovydoc")
127142
classifier = "groovydoc"

src/main/groovy/org/scoverage/CoverageChecker.groovy

-98
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
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+
}

src/test/groovy/org/scoverage/CoverageCheckerTest.groovy renamed to src/test/kotlin/org/scoverage/CoverageCheckerTest.kt

+27-33
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,26 @@ import org.slf4j.LoggerFactory
1111

1212
import java.nio.file.Paths
1313
import java.text.NumberFormat
14+
import java.util.*
1415

15-
import static org.junit.Assert.assertThat
16-
import static org.junit.jupiter.api.Assertions.assertThrows
16+
import org.junit.Assert.assertThat
17+
import org.junit.jupiter.api.Assertions.assertThrows
1718

1819
class CoverageCheckerTest {
1920

20-
private NumberFormat numberFormat = NumberFormat.getInstance(Locale.US)
21+
private val numberFormat = NumberFormat.getInstance(Locale.US)
2122

22-
private File reportDir = Paths.get(getClass().getClassLoader().getResource("checkTask").toURI()).toFile()
23+
private val reportDir = Paths.get(javaClass.getClassLoader().getResource("checkTask").toURI()).toFile()
2324

24-
private CoverageChecker checker = new CoverageChecker(LoggerFactory.getLogger(CoverageCheckerTest.class))
25+
private val checker = CoverageChecker(LoggerFactory.getLogger(CoverageCheckerTest::class.java))
2526

26-
@Rule
27-
public TemporaryFolder tempDir = new TemporaryFolder()
27+
@get:Rule
28+
val tempDir = TemporaryFolder()
2829

2930
// error when report file is not there
3031

3132
@Test
32-
void failsWhenReportFileIsNotFound() {
33+
fun failsWhenReportFileIsNotFound() {
3334
assertFailure(CoverageChecker.fileNotFoundErrorMsg(CoverageType.Line), {
3435
checker.checkLineCoverage(tempDir.getRoot(), CoverageType.Line, 0.0, numberFormat)
3536
})
@@ -38,85 +39,78 @@ class CoverageCheckerTest {
3839
// line coverage
3940

4041
@Test
41-
void failsWhenLineRateIsBelowTarget() {
42+
fun failsWhenLineRateIsBelowTarget() {
4243
assertFailure(CoverageChecker.errorMsg("66", "100", CoverageType.Line), {
4344
checker.checkLineCoverage(reportDir, CoverageType.Line, 1.0, numberFormat)
4445
})
4546
}
4647

4748
@Test
48-
void doesNotFailWhenLineRateIsAtTarget() {
49+
fun doesNotFailWhenLineRateIsAtTarget() {
4950
checker.checkLineCoverage(reportDir, CoverageType.Line, 0.66, numberFormat)
5051
}
5152

5253
@Test
53-
void doesNotFailWhenLineRateIsAboveTarget() {
54+
fun doesNotFailWhenLineRateIsAboveTarget() {
5455
checker.checkLineCoverage(reportDir, CoverageType.Line, 0.6, numberFormat)
5556
}
5657

5758
// Statement coverage
5859

5960
@Test
60-
void failsWhenStatementRateIsBelowTarget() {
61-
assertFailure(CoverageChecker.errorMsg(numberFormat.format(new Double(33.33)), "100", CoverageType.Statement), {
61+
fun failsWhenStatementRateIsBelowTarget() {
62+
assertFailure(CoverageChecker.errorMsg(numberFormat.format(33.33), "100", CoverageType.Statement), {
6263
checker.checkLineCoverage(reportDir, CoverageType.Statement, 1.0, numberFormat)
6364
})
6465
}
6566

6667
@Test
67-
void doesNotFailWhenStatementRateIsAtTarget() {
68+
fun doesNotFailWhenStatementRateIsAtTarget() {
6869
checker.checkLineCoverage(reportDir, CoverageType.Statement, 0.33, numberFormat)
6970
}
7071

7172
@Test
72-
void doesNotFailWhenStatementRateIsAboveTarget() {
73+
fun doesNotFailWhenStatementRateIsAboveTarget() {
7374
checker.checkLineCoverage(reportDir, CoverageType.Statement, 0.3, numberFormat)
7475
}
7576

7677
// Branch coverage
7778

7879
@Test
79-
void failsWhenBranchRateIsBelowTarget() {
80+
fun failsWhenBranchRateIsBelowTarget() {
8081
assertFailure(CoverageChecker.errorMsg("50", "100", CoverageType.Branch), {
8182
checker.checkLineCoverage(reportDir, CoverageType.Branch, 1.0, numberFormat)
8283
})
8384
}
8485

8586
@Test
86-
void doesNotFailWhenBranchRateIsAtTarget() {
87+
fun doesNotFailWhenBranchRateIsAtTarget() {
8788
checker.checkLineCoverage(reportDir, CoverageType.Branch, 0.5, numberFormat)
8889
}
8990

9091
@Test
91-
void doesNotFailWhenBranchRateIsAboveTarget() {
92+
fun doesNotFailWhenBranchRateIsAboveTarget() {
9293
checker.checkLineCoverage(reportDir, CoverageType.Branch, 0.45, numberFormat)
9394
}
9495

95-
private void assertFailure(String message, Executable executable) {
96-
GradleException e = assertThrows(GradleException.class, executable)
97-
assertThat(e, new CauseMatcher(message))
96+
private fun assertFailure(message: String, executable: Executable) {
97+
val e: GradleException = assertThrows(GradleException::class.java, executable)
98+
assertThat(e, CauseMatcher(message))
9899
}
99100
}
100101

101102
/**
102103
* Copied from the Internet, just to check if we have correct exception thrown.
103104
*/
104-
class CauseMatcher extends TypeSafeMatcher<GradleException> {
105-
106-
private final String expectedMessage
107-
108-
CauseMatcher(String expectedMessage) {
109-
this.expectedMessage = expectedMessage
110-
}
105+
class CauseMatcher(private val expectedMessage: String): TypeSafeMatcher<GradleException>() {
111106

112107
@Override
113-
protected boolean matchesSafely(GradleException item) {
114-
return item.getMessage().contains(expectedMessage)
108+
protected override fun matchesSafely(item: GradleException): Boolean {
109+
return item.message?.contains(expectedMessage) == true
115110
}
116111

117-
@Override
118-
void describeTo(Description description) {
112+
override fun describeTo(description: Description) {
119113
description.appendText("expects message ")
120114
.appendValue(expectedMessage)
121115
}
122-
}
116+
}

0 commit comments

Comments
 (0)