Skip to content

Commit ec94ad9

Browse files
authored
Fixed adding data to an already existing binary report file
If the build cache is not used and the build directory is not cleared before the test run, then the coverage data from previous test runs are merged with the data from the recent run. In this case, the report may include coverage from those tests that were removed in the latest version of the code. To solve this, it is necessary to delete the binary report file before each run of the test task. Fixes #489 PR #490
1 parent 2547f52 commit ec94ad9

File tree

8 files changed

+130
-24
lines changed

8 files changed

+130
-24
lines changed

kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/cases/ReportsCachingTests.kt

+22-14
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*/
44
package kotlinx.kover.gradle.plugin.test.functional.cases
55

6+
import kotlinx.kover.gradle.plugin.commons.CoverageToolVendor
67
import kotlinx.kover.gradle.plugin.test.functional.framework.configurator.*
78
import kotlinx.kover.gradle.plugin.test.functional.framework.starter.*
89

@@ -29,36 +30,39 @@ internal class ReportsCachingTests {
2930
addProjectWithKover {
3031
sourcesFrom("simple")
3132
}
32-
reportAndCheck("SUCCESS", true)
33+
reportAndCheck("SUCCESS", cached = true)
3334

3435
run("clean", "--build-cache") {
3536
checkDefaultBinReport(false)
3637
checkDefaultReports(false)
3738
}
38-
reportAndCheck("FROM-CACHE", true)
39+
reportAndCheck("FROM-CACHE", cached = true)
3940
}
4041

4142
@SlicedGeneratedTest(allTools = true)
42-
fun BuildConfigurator.testOuOfDateOnSources() {
43+
fun BuildConfigurator.testOutOfDateOnSources() {
4344
useLocalCache()
4445

4546
addProjectWithKover {
4647
sourcesFrom("simple")
4748
}
48-
reportAndCheck("SUCCESS", true)
49+
reportAndCheck("SUCCESS", cached = true)
4950

5051
edit("src/main/kotlin/Sources.kt") {
5152
"$it\n class Additional"
5253
}
5354
// tasks must be restarted after the source code is edited
54-
reportAndCheck("SUCCESS", true)
55+
reportAndCheck("SUCCESS", cached = true)
5556

5657
edit("src/test/kotlin/TestClass.kt") {
5758
"$it\n class AdditionalTest"
5859
}
5960

60-
// tasks must be restarted after tests are edited
61-
reportAndCheck("SUCCESS", true)
61+
// test task must be restarted after test class is edited
62+
// , but reports can be up-to-date because no sources changed.
63+
// For JaCoCo .exec binary report contains instrumentation time, so it's unstable between builds and can cause the report generation.
64+
// For Kover it is also not guaranteed to have a stable binary .ic file, so sometimes reports can be regenerated.
65+
reportAndCheck("SUCCESS", "UP-TO-DATE",true)
6266
}
6367

6468
@SlicedGeneratedTest(allTools = true)
@@ -68,27 +72,31 @@ internal class ReportsCachingTests {
6872
addProjectWithKover {
6973
sourcesFrom("simple")
7074
}
71-
reportAndCheck("SUCCESS", true)
75+
reportAndCheck("SUCCESS", cached = true)
7276
run("clean", "--build-cache") {
7377
checkDefaultBinReport(false)
7478
checkDefaultReports(false)
7579
}
76-
reportAndCheck("FROM-CACHE", true)
80+
reportAndCheck("FROM-CACHE", cached = true)
7781
}
7882

7983

80-
private fun BuildConfigurator.reportAndCheck(outcome: String, cached: Boolean = false) {
84+
private fun BuildConfigurator.reportAndCheck(
85+
outcome: String,
86+
reportsAlternativeOutcome: String = outcome,
87+
cached: Boolean = false
88+
) {
8189
val args = if (cached) {
82-
arrayOf("koverXmlReport", "koverHtmlReport", "--build-cache")
90+
arrayOf("koverXmlReport", "koverHtmlReport", "--build-cache", "--info")
8391
} else {
84-
arrayOf("koverXmlReport", "koverHtmlReport")
92+
arrayOf("koverXmlReport", "koverHtmlReport", "--info")
8593
}
8694
run(*args) {
8795
checkDefaultBinReport()
8896
checkDefaultReports()
8997
checkOutcome("test", outcome)
90-
checkOutcome("koverXmlReport", outcome)
91-
checkOutcome("koverHtmlReport", outcome)
98+
checkOutcome("koverXmlReport", outcome, reportsAlternativeOutcome)
99+
checkOutcome("koverHtmlReport", outcome, reportsAlternativeOutcome)
92100
}
93101
}
94102
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
package kotlinx.kover.gradle.plugin.test.functional.cases
5+
6+
import kotlinx.kover.gradle.plugin.test.functional.framework.configurator.BuildConfigurator
7+
import kotlinx.kover.gradle.plugin.test.functional.framework.starter.SlicedGeneratedTest
8+
9+
internal class ReportsUpToDateTests {
10+
11+
@SlicedGeneratedTest(allTools = true)
12+
fun BuildConfigurator.testDeleteTest() {
13+
addProjectWithKover {
14+
sourcesFrom("simple")
15+
}
16+
17+
add("src/test/kotlin/ExtraTestClass.kt") {
18+
"""
19+
package org.jetbrains.serialuser
20+
21+
import org.jetbrains.Unused
22+
import kotlin.test.Test
23+
24+
class AdditionalTest {
25+
@Test
26+
fun extra() {
27+
Unused().functionInUsedClass()
28+
}
29+
}
30+
""".trimMargin()
31+
}
32+
run("koverXmlReport") {
33+
xmlReport {
34+
classCounter("org.jetbrains.Unused").assertCovered()
35+
}
36+
}
37+
38+
// report should be regenerated if test are deleted
39+
delete("src/test/kotlin/ExtraTestClass.kt")
40+
run("koverXmlReport") {
41+
xmlReport {
42+
classCounter("org.jetbrains.Unused").assertFullyMissed()
43+
}
44+
}
45+
}
46+
47+
}
48+

kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/checker/Checker.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,11 @@ private class CheckerContextImpl(
135135
checkHtmlReport(mustExist = mustExist)
136136
}
137137

138-
override fun checkOutcome(taskNameOrPath: String, expectedOutcome: String) {
138+
override fun checkOutcome(taskNameOrPath: String, vararg expectedOutcome: String) {
139139
val taskPath = taskNameOrPath.asPath()
140140
val outcome = result.taskOutcome(taskPath) ?: noTaskFound(taskNameOrPath, taskPath)
141141

142-
assertEquals(expectedOutcome, outcome, "Unexpected outcome for task '$taskPath'")
142+
assertContains(expectedOutcome.toSet(), outcome, "Unexpected outcome for task '$taskPath'")
143143
}
144144

145145
override fun taskNotCalled(taskNameOrPath: String) {

kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/checker/CheckerTypes.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ internal interface CheckerContext {
3737

3838
fun checkXmlReport(variantName: String = "", mustExist: Boolean = true)
3939
fun checkHtmlReport(variantName: String = "", mustExist: Boolean = true)
40-
fun checkOutcome(taskNameOrPath: String, expectedOutcome: String)
40+
fun checkOutcome(taskNameOrPath: String, vararg expectedOutcome: String)
4141
fun taskNotCalled(taskNameOrPath: String)
4242
fun checkDefaultReports(mustExist: Boolean = true)
4343
fun checkDefaultBinReport(mustExist: Boolean = true)

kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/configurator/BuildConfigurator.kt

+20-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,18 @@ internal data class TestFileEditStep(
3939
val filePath: String,
4040
val editor: (String) -> String
4141
): TestExecutionStep() {
42-
override val name: String = "Edit: $filePath"
42+
override val name: String = "Edit file: $filePath"
43+
}
44+
45+
internal data class TestFileAddStep(
46+
val filePath: String,
47+
val editor: () -> String
48+
): TestExecutionStep() {
49+
override val name: String = "Add file: $filePath"
50+
}
51+
52+
internal data class TestFileDeleteStep(val filePath: String): TestExecutionStep() {
53+
override val name: String = "Delete file: $filePath"
4354
}
4455

4556
private open class TestBuildConfigurator : BuildConfigurator {
@@ -80,6 +91,14 @@ private open class TestBuildConfigurator : BuildConfigurator {
8091
steps += TestFileEditStep(filePath, editor)
8192
}
8293

94+
override fun add(filePath: String, editor: () -> String) {
95+
steps += TestFileAddStep(filePath, editor)
96+
}
97+
98+
override fun delete(filePath: String) {
99+
steps += TestFileDeleteStep(filePath)
100+
}
101+
83102
override fun useLocalCache(use: Boolean) {
84103
useCache = use
85104
}

kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/configurator/ConfiguratorTypes.kt

+12
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ internal interface BuildConfigurator {
2424

2525
fun edit(filePath: String, editor: (String) -> String)
2626

27+
fun add(filePath: String, editor: () -> String)
28+
29+
fun delete(filePath: String)
30+
2731
fun useLocalCache(use: Boolean = true)
2832

2933
fun prepare(): TestBuildConfig
@@ -72,6 +76,14 @@ internal abstract class BuilderConfiguratorWrapper(private val origin: BuildConf
7276
origin.edit(filePath, editor)
7377
}
7478

79+
override fun add(filePath: String, editor: () -> String) {
80+
origin.add(filePath, editor)
81+
}
82+
83+
override fun delete(filePath: String) {
84+
origin.delete(filePath)
85+
}
86+
7587
override fun useLocalCache(use: Boolean) {
7688
origin.useLocalCache(use)
7789
}

kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/runner/StepsRunner.kt

+19-6
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,7 @@ ${this.targetDir.buildScript()}
3030
}
3131

3232
is TestFileEditStep -> {
33-
if (File(step.filePath).isAbsolute) {
34-
throw Exception("It is not allowed to edit a file by an absolute path. For $description")
35-
}
36-
37-
val file = this.targetDir.resolve(step.filePath)
33+
val file = projectFile(step.filePath, description)
3834
if (!file.exists()) {
3935
throw Exception("Project file not found for editing. For $description")
4036
}
@@ -43,12 +39,29 @@ ${this.targetDir.buildScript()}
4339
val newContent = step.editor(content)
4440
file.writeText(newContent)
4541
}
46-
}
4742

43+
is TestFileAddStep -> {
44+
val file = projectFile(step.filePath, description)
4845

46+
file.writeText(step.editor())
47+
}
48+
49+
is TestFileDeleteStep -> {
50+
val file = projectFile(step.filePath, description)
51+
file.delete()
52+
}
53+
}
4954
}
5055
}
5156

57+
private fun GradleBuild.projectFile(path: String, description: String): File {
58+
if (File(path).isAbsolute) {
59+
throw Exception("It is not allowed to edit a file by an absolute path. For $description")
60+
}
61+
62+
return targetDir.resolve(path)
63+
}
64+
5265
private fun File.buildScript(): String {
5366
var file = this.resolve("build.gradle")
5467
if (file.exists() && file.isFile) return file.readText()

kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/appliers/JvmTestTaskConfigurator.kt

+6
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ internal class JvmTestTaskConfigurator(
3939
}
4040
testTask.dependsOn(data.findAgentJarTask)
4141

42+
testTask.doFirst {
43+
// delete report so that when the data is re-measured, it is not appended to an already existing file
44+
// see https://github.com/Kotlin/kotlinx-kover/issues/489
45+
binReportProvider.get().asFile.delete()
46+
}
47+
4248
// Always excludes android classes, see https://github.com/Kotlin/kotlinx-kover/issues/89
4349
val excluded = data.excludedClasses + listOf("android.*", "com.android.*")
4450

0 commit comments

Comments
 (0)