Skip to content

Commit

Permalink
Replaced JaCoCo ant-calls with programmatic calls of JaCoCo's classes
Browse files Browse the repository at this point in the history
Resolves #630
Fixes #666
  • Loading branch information
shanshin committed Aug 7, 2024
1 parent 1a01acc commit 8cd1d87
Show file tree
Hide file tree
Showing 20 changed files with 438 additions and 331 deletions.
3 changes: 2 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ maven-embedder = "3.9.8"
maven-api = "3.0"
maven-resolver = "1.9.21"
maven-slf4j = "1.7.36"
jacoco = "0.8.12"

[libraries]

Expand Down Expand Up @@ -40,7 +41,7 @@ maven-resolver-file = { module = "org.apache.maven.resolver:maven-resolver-trans
maven-resolver-http = { module = "org.apache.maven.resolver:maven-resolver-transport-http", version.ref = "maven-resolver" }

maven-slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "maven-slf4j" }

jacoco-reporter = {module = "org.jacoco:org.jacoco.report", version.ref = "jacoco" }

[plugins]
gradle-pluginPublish = { id = "com.gradle.plugin-publish", version.ref = "gradle-plugin-publish" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,11 +197,12 @@ public object KoverLegacyFeatures {
messageBuilder.appendLine("$namedRule violated: ${rule.violations[0].format(rule)}")
} else {
messageBuilder.appendLine("$namedRule violated:")

rule.violations.forEach { bound ->
messageBuilder.append(" ")
messageBuilder.appendLine(bound.format(rule))
}
rule.violations.map { bound -> bound.format(rule) }
.toSortedSet()
.forEach { bound ->
messageBuilder.append(" ")
messageBuilder.appendLine(bound.format(rule))
}
}
}

Expand Down Expand Up @@ -300,5 +301,5 @@ private fun BoundViolation.format(rule: RuleViolations): String {

val expectedValue = if (isMax) bound.maxValue else bound.minValue

return "$metricText $valueTypeText$entityText is $value, but expected $directionText is $expectedValue"
return "$metricText $valueTypeText$entityText is $value, but expected $directionText is ${expectedValue?.toPlainString()}"
}
1 change: 1 addition & 0 deletions kover-gradle-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ dependencies {
// exclude transitive dependency on stdlib, the Gradle version should be used
compileOnly(kotlin("stdlib"))
compileOnly(libs.gradlePlugin.kotlin)
compileOnly(libs.jacoco.reporter)

functionalTestImplementation(kotlin("test"))
functionalTestImplementation(libs.junit.jupiter)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,9 @@ internal class LoggingTaskTests {
taskOutput("koverPrintCoverage") {
assertEquals(
"Coverage for classes:\n" +
"Class org.jetbrains.ExampleClass covered instructions=7\n" +
"Class org.jetbrains.Unused covered instructions=0\n" +
"Class org.jetbrains.SecondClass covered instructions=7\n" +
"Class org.jetbrains.Unused covered instructions=0\n\n",
"Class org.jetbrains.ExampleClass covered instructions=7\n\n",
this
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,14 @@ internal class VerificationTests {

run("koverHtmlReport", "koverVerify", errorExpected = true) {
verification {
assertKoverResult("""Rule 'counts rule' violated:
lines covered percentage is 46.590900, but expected minimum is 58
lines covered count is 41, but expected maximum is 3
assertResult("""Rule 'counts rule' violated:
lines covered count is *, but expected maximum is 3
lines covered percentage is *, but expected minimum is 58
Rule 'fully uncovered instructions by classes' violated:
instructions missed percentage for class 'org.jetbrains.kover.test.functional.verification.FullyCovered' is 0.000000, but expected minimum is 100
instructions missed percentage for class 'org.jetbrains.kover.test.functional.verification.FullyCovered' is *, but expected minimum is 100
instructions missed percentage for class 'org.jetbrains.kover.test.functional.verification.PartiallyCoveredFirst' is *, but expected minimum is 100
instructions missed percentage for class 'org.jetbrains.kover.test.functional.verification.PartiallyCoveredSecond' is *, but expected minimum is 100
instructions missed percentage for class 'org.jetbrains.kover.test.functional.verification.subpackage.SubFullyCovered' is 0.000000, but expected minimum is 100
instructions missed percentage for class 'org.jetbrains.kover.test.functional.verification.subpackage.SubFullyCovered' is *, but expected minimum is 100
instructions missed percentage for class 'org.jetbrains.kover.test.functional.verification.subpackage.SubPartiallyCoveredFirst' is *, but expected minimum is 100
instructions missed percentage for class 'org.jetbrains.kover.test.functional.verification.subpackage.SubPartiallyCoveredSecond' is *, but expected minimum is 100
Rule 'fully covered instructions by packages' violated:
Expand All @@ -122,28 +122,6 @@ Rule 'branches by classes' violated:
Rule 'missed packages' violated:
lines missed count for package 'org.jetbrains.kover.test.functional.verification' is 23, but expected maximum is 1
lines missed count for package 'org.jetbrains.kover.test.functional.verification.subpackage' is 24, but expected maximum is 1
""")

assertJaCoCoResult("""Rule violated: lines covered count is 41, but expected maximum is 3
Rule violated: lines covered percentage is 46.5900, but expected minimum is 58.0000
Rule violated: branches covered count for class 'org.jetbrains.kover.test.functional.verification.FullyCovered' is 0, but expected minimum is 1000
Rule violated: instructions missed percentage for class 'org.jetbrains.kover.test.functional.verification.FullyCovered' is 0.0000, but expected minimum is 100.0000
Rule violated: branches covered count for class 'org.jetbrains.kover.test.functional.verification.PartiallyCoveredFirst' is 2, but expected minimum is 1000
Rule violated: instructions missed percentage for class 'org.jetbrains.kover.test.functional.verification.PartiallyCoveredFirst' is *, but expected minimum is 100.0000
Rule violated: branches covered count for class 'org.jetbrains.kover.test.functional.verification.PartiallyCoveredSecond' is 1, but expected minimum is 1000
Rule violated: instructions missed percentage for class 'org.jetbrains.kover.test.functional.verification.PartiallyCoveredSecond' is *, but expected minimum is 100.0000
Rule violated: branches covered count for class 'org.jetbrains.kover.test.functional.verification.Uncovered' is 0, but expected minimum is 1000
Rule violated: branches covered count for class 'org.jetbrains.kover.test.functional.verification.subpackage.SubFullyCovered' is 0, but expected minimum is 1000
Rule violated: instructions missed percentage for class 'org.jetbrains.kover.test.functional.verification.subpackage.SubFullyCovered' is 0.0000, but expected minimum is 100.0000
Rule violated: branches covered count for class 'org.jetbrains.kover.test.functional.verification.subpackage.SubPartiallyCoveredFirst' is 0, but expected minimum is 1000
Rule violated: instructions missed percentage for class 'org.jetbrains.kover.test.functional.verification.subpackage.SubPartiallyCoveredFirst' is *, but expected minimum is 100.0000
Rule violated: branches covered count for class 'org.jetbrains.kover.test.functional.verification.subpackage.SubPartiallyCoveredSecond' is 1, but expected minimum is 1000
Rule violated: instructions missed percentage for class 'org.jetbrains.kover.test.functional.verification.subpackage.SubPartiallyCoveredSecond' is *, but expected minimum is 100.0000
Rule violated: branches covered count for class 'org.jetbrains.kover.test.functional.verification.subpackage.SubUncovered' is 0, but expected minimum is 1000
Rule violated: instructions covered percentage for package 'org.jetbrains.kover.test.functional.verification.subpackage' is *, but expected minimum is 100.0000
Rule violated: lines missed count for package 'org.jetbrains.kover.test.functional.verification.subpackage' is 24, but expected maximum is 1
Rule violated: instructions covered percentage for package 'org.jetbrains.kover.test.functional.verification' is *, but expected minimum is 100.0000
Rule violated: lines missed count for package 'org.jetbrains.kover.test.functional.verification' is 23, but expected maximum is 1
""")
}
}
Expand All @@ -169,8 +147,7 @@ Rule violated: lines missed count for package 'org.jetbrains.kover.test.function

run("koverVerify", errorExpected = true) {
verification {
assertKoverResult("Rule 'root rule' violated: lines covered percentage is *, but expected minimum is 99\n")
assertJaCoCoResult("Rule violated: lines covered percentage is *, but expected minimum is 99.0000\n")
assertResult("Rule 'root rule' violated: lines covered percentage is *, but expected minimum is 99\n")
}
}
}
Expand Down Expand Up @@ -198,8 +175,7 @@ Rule violated: lines missed count for package 'org.jetbrains.kover.test.function
taskOutput(":koverVerify") {
match {
assertContains("Kover Verification Error")
assertKoverContains("Rule 'root rule' violated: lines covered percentage is *, but expected minimum is 99\n")
assertJaCoCoContains("Rule violated: lines covered percentage is *, but expected minimum is 99.0000\n")
assertContains("Rule 'root rule' violated: lines covered percentage is *, but expected minimum is 99\n")
}
}
}
Expand Down Expand Up @@ -233,8 +209,7 @@ Rule violated: lines missed count for package 'org.jetbrains.kover.test.function
taskOutput(":koverVerify") {
match {
assertContains("Kover Verification Error")
assertKoverContains("Rule 'root rule' violated: lines covered percentage is *, but expected minimum is 99\n")
assertJaCoCoContains("Rule violated: lines covered percentage is *, but expected minimum is 99.0000\n")
assertContains("Rule 'root rule' violated: lines covered percentage is *, but expected minimum is 99\n")
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -403,19 +403,10 @@ private class XmlReportCheckerImpl(val context: CheckerContextImpl, file: File)
}

private class VerifyReportCheckerImpl(val context: CheckerContextImpl, val content: String) : VerifyReportChecker {
override fun assertKoverResult(expected: String) {
if (context.project.toolVariant.vendor != CoverageToolVendor.KOVER) return
val regex = KoverFeatures.koverWildcardToRegex(expected).toRegex()
if (!content.matches(regex)) {
throw AssertionError("Unexpected verification result for Kover Tool.\n\tActual\n[\n$content\n]\nExpected regex\n[\n$expected\n]")
}
}

override fun assertJaCoCoResult(expected: String) {
if (context.project.toolVariant.vendor != CoverageToolVendor.JACOCO) return
override fun assertResult(expected: String) {
val regex = KoverFeatures.koverWildcardToRegex(expected).toRegex()
if (!content.matches(regex)) {
throw AssertionError("Unexpected verification result for JaCoCo Tool.\n\tActual\n[\n$content\n]\nExpected regex\n[\n$expected\n]")
throw AssertionError("Unexpected verification result.\n\tActual\n[\n$content\n]\nExpected regex\n[\n$expected\n]")
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,7 @@ internal interface Counter {
}

internal interface VerifyReportChecker {
fun assertKoverResult(expected: String)
fun assertJaCoCoResult(expected: String)
fun assertResult(expected: String)
}

internal interface TextMatcher {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ internal class ReportContext(

internal class GradleReportServices(
val antBuilder: AntBuilder,
val objects: ObjectFactory
val objects: ObjectFactory,
val workerExecutor: WorkerExecutor
)

internal data class ReportFilters(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public object KoverVersions {
/**
* JaCoCo coverage tool version used by default.
*/
public const val JACOCO_TOOL_DEFAULT_VERSION = "0.8.11"
public const val JACOCO_TOOL_DEFAULT_VERSION = "0.8.12"

/**
* Current version of Kover Gradle Plugin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.*
import org.gradle.api.tasks.*
import org.gradle.kotlin.dsl.*
import org.gradle.workers.WorkerExecutor
import java.io.File
import javax.inject.Inject

Expand Down Expand Up @@ -65,10 +66,13 @@ internal abstract class AbstractKoverReportTask : DefaultTask() {
@get:Inject
protected abstract val obj: ObjectFactory

@get:Inject
protected abstract val workerExecutor: WorkerExecutor

private val rootDir: File = project.rootDir

protected fun context(): ReportContext {
val services = GradleReportServices(ant, obj)
val services = GradleReportServices(ant, obj, workerExecutor)
return ReportContext(collectAllFiles(), filters.get(), reportClasspath, temporaryDir, projectPath, services)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,7 @@ internal abstract class KoverDoVerifyTask @Inject constructor(@get:Internal over
@TaskAction
fun verify() {
val enabledRules = rules.get().filter { it.isEnabled }
val violations = tool.get().verify(enabledRules, context())

val errorMessage = KoverLegacyFeatures.violationMessage(violations)
resultFile.get().asFile.writeText(errorMessage)
tool.get().verify(enabledRules, resultFile.get().asFile, context())
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ internal interface CoverageTool {
/**
* Perform verification.
*/
fun verify(rules: List<VerificationRule>, context: ReportContext): List<RuleViolations>
fun verify(rules: List<VerificationRule>, output: File, context: ReportContext)

/**
* Calculate coverage according to the specified parameters [request], for each grouped entity.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright 2017-2024 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 kotlinx.kover.features.jvm.KoverFeatures.koverWildcardToRegex
import kotlinx.kover.gradle.plugin.commons.ArtifactContent
import kotlinx.kover.gradle.plugin.commons.ReportContext
import kotlinx.kover.gradle.plugin.commons.ReportFilters
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Property
import org.gradle.workers.WorkParameters
import org.jacoco.core.analysis.Analyzer
import org.jacoco.core.analysis.CoverageBuilder
import org.jacoco.core.tools.ExecFileLoader
import org.jacoco.report.DirectorySourceFileLocator
import org.jacoco.report.IReportVisitor
import org.jacoco.report.MultiSourceFileLocator
import java.io.File

internal interface CommonJacocoParameters: WorkParameters {
val filters: Property<ReportFilters>

val files: Property<ArtifactContent>
val tempDir: DirectoryProperty
val projectPath: Property<String>
}


internal fun IReportVisitor.loadContent(name: String?, content: ArtifactContent, filters: ReportFilters) {
val loader = ExecFileLoader()
content.reports.forEach { file ->
file.inputStream().use { loader.load(it) }
}
val sessionInfoStore = loader.sessionInfoStore
val executionDataStore = loader.executionDataStore

val builder = CoverageBuilder()
val analyzer = Analyzer(executionDataStore, builder)

val classfiles = collectClassFiles(content.outputs, filters)
classfiles.forEach { classfile ->
analyzer.analyzeAll(classfile)
}
val bundle = builder.getBundle(name)

visitInfo(sessionInfoStore.infos, executionDataStore.contents)

val sourceLocator = MultiSourceFileLocator(DEFAULT_TAB_WIDTH)
content.sources.forEach { sourceDir ->
sourceLocator.add(DirectorySourceFileLocator(sourceDir, null, DEFAULT_TAB_WIDTH))
}
visitBundle(bundle, sourceLocator)
}

private const val DEFAULT_TAB_WIDTH = 4

private fun collectClassFiles(outputs: Iterable<File>, filters: ReportFilters): Collection<File> {
val filesByClassName = mutableMapOf<String, File>()
outputs.forEach { output ->
output.walk().forEach { file ->
if (file.isFile && file.name.endsWith(CLASS_FILE_EXTENSION)) {
val className = file.toRelativeString(output).filenameToClassname()
filesByClassName[className] = file
}
}
}

return if (filters.excludesClasses.isNotEmpty() || filters.includesClasses.isNotEmpty()) {
val excludeRegexes = filters.excludesClasses.map { koverWildcardToRegex(it).toRegex() }
val includeRegexes = filters.includesClasses.map { koverWildcardToRegex(it).toRegex() }

filesByClassName.filterKeys { className ->
((includeRegexes.isEmpty() || includeRegexes.any { regex -> className.matches(regex) })
// if the exclusion rules are declared, then the file should not fit any of them
&& excludeRegexes.none { regex -> className.matches(regex) })
}.values
} else {
filesByClassName.values
}
}

internal fun <T : CommonJacocoParameters> T.fillCommonParameters(context: ReportContext) {
filters.convention(context.filters)
files.convention(context.files)
tempDir.set(context.tempDir)
projectPath.convention(context.projectPath)
}

/**
* Replaces characters `|` or `\` to `.` and remove postfix `.class`.
*/
internal fun String.filenameToClassname(): String {
return this.replace(File.separatorChar, '.').removeSuffix(CLASS_FILE_EXTENSION)
}

/**
* Extension of class-files.
*/
internal const val CLASS_FILE_EXTENSION = ".class"

This file was deleted.

Loading

0 comments on commit 8cd1d87

Please sign in to comment.