Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replaced JaCoCo ant-calls with programmatic calls of JaCoCo's classes #668

Merged
merged 3 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 { boundString ->
messageBuilder.append(" ")
messageBuilder.appendLine(boundString)
}
}
}

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