Skip to content

Commit

Permalink
Implemented Kover Maven Plugin
Browse files Browse the repository at this point in the history
Resolves #51

Co-authored-by: Leonid Startsev <sandwwraith@users.noreply.github.com>

PR #654
  • Loading branch information
shanshin authored Jul 18, 2024
1 parent 54637f7 commit 1739cc5
Show file tree
Hide file tree
Showing 113 changed files with 5,655 additions and 69 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Kover is a set of solutions for collecting test coverage of Kotlin code compiled

Kover Toolset:
- [Kover Gradle Plugin](#kover-gradle-plugin)
- [Kover Maven Plugin](#kover-maven-plugin)
- [Kover CLI](#kover-cli)
- [Kover offline instrumentation](#kover-offline-instrumentation)
- [Kover JVM agent](#kover-jvm-agent)
Expand Down Expand Up @@ -119,6 +120,21 @@ It is in its infancy, it is recommended to use it only for test or pet projects.

Refer to the [documentation](https://kotlin.github.io/kotlinx-kover/gradle-plugin/aggregated.html) for details.

## Kover Maven Plugin
The Kover Maven Plugin can be applied by specifying build plugin
```xml
<plugin>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kover-maven-plugin</artifactId>
<version>0.8.2</version>
</plugin>
```

The list of Kover goals is specified in [this document section](https://kotlin.github.io/kotlinx-kover/maven-plugin#goals).

For full information about latest stable release of Kover Gradle Plugin, please refer to the [documentation](https://kotlin.github.io/kotlinx-kover/maven-plugin).


## Kover CLI
Standalone JVM application used for offline instrumentation and generation of human-readable reports.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ extension.callDokkaHtml.convention(false)

tasks.register("releaseDocs") {
dependsOn(
tasks.matching { extension.callDokkaHtml.get() && it.name == "dokkaHtml" }
tasks.named { it == "dokkaHtml" }.matching { extension.callDokkaHtml.get() }
)

doLast {
Expand Down
28 changes: 25 additions & 3 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
[versions]

intellij-coverage = "1.0.760"
intellij-coverage = "1.0.761"
junit = "5.9.0"
kotlinx-bcv = "0.13.2"
kotlinx-dokka = "1.8.10"
args4j = "2.33"
gradle-plugin-publish = "1.2.1"
maven-plugin-development = "0.4.3"
maven-embedder = "3.9.8"
maven-api = "3.0"
maven-resolver = "1.9.21"
maven-slf4j = "1.7.36"

[libraries]

Expand All @@ -20,9 +25,26 @@ junit-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref

args4j = { module = "args4j:args4j", version.ref = "args4j" }

gradlePlugin-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin" }
gradlePlugin-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin" }

maven-embedder = { module = "org.apache.maven:maven-embedder", version.ref = "maven-embedder" }
maven-compat = { module = "org.apache.maven:maven-compat", version.ref = "maven-embedder" }
maven-slf4j-provider = { module = "org.apache.maven:maven-slf4j-provider", version.ref = "maven-embedder" }

maven-plugin-annotations = { module = "org.apache.maven.plugin-tools:maven-plugin-annotations", version.ref = "maven-api" }
maven-core = { module = "org.apache.maven:maven-core", version.ref = "maven-api" }
maven-reporting-api = { module = "org.apache.maven.reporting:maven-reporting-api", version.ref = "maven-api" }

maven-resolver-basic = { module = "org.apache.maven.resolver:maven-resolver-connector-basic", version.ref = "maven-resolver" }
maven-resolver-file = { module = "org.apache.maven.resolver:maven-resolver-transport-file", version.ref = "maven-resolver" }
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" }


[plugins]
gradle-pluginPublish = { id = "com.gradle.plugin-publish", version.ref = "gradle-plugin-publish" }
gradle-pluginPublish = { id = "com.gradle.plugin-publish", version.ref = "gradle-plugin-publish" }
kotlinx-binaryCompatibilityValidator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "kotlinx-bcv" }
kotlinx-dokka = { id = "org.jetbrains.dokka", version.ref = "kotlinx-dokka" }
mavenPluginDevelopment = { id = "de.benediktritter.maven-plugin-development", version.ref = "maven-plugin-development" }

4 changes: 2 additions & 2 deletions kover-cli/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ plugins {
id("kover-release-conventions")
}

extensions.configure<Kover_publishing_conventions_gradle.KoverPublicationExtension> {
koverPublication {
description.set("Command Line Interface for Kotlin Coverage Toolchain")
}

Expand Down Expand Up @@ -65,7 +65,7 @@ repositories {
mavenCentral()
}

extensions.configure<Kover_docs_conventions_gradle.KoverDocsExtension> {
koverDocs {
docsDirectory.set("cli")
description.set("Kover Command Line Interface")
}
1 change: 1 addition & 0 deletions kover-features-jvm/api/kover-features-jvm.api
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ public final class kotlinx/kover/features/jvm/KoverLegacyFeatures {
public final fun generateXmlReport (Ljava/io/File;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/lang/String;Lkotlinx/kover/features/jvm/ClassFilters;)V
public final fun instrument (Ljava/io/File;Ljava/util/List;Lkotlinx/kover/features/jvm/ClassFilters;Z)V
public final fun verify (Ljava/util/List;Ljava/io/File;Lkotlinx/kover/features/jvm/ClassFilters;Ljava/util/List;Ljava/util/List;)Ljava/util/List;
public final fun violationMessage (Ljava/util/List;)Ljava/lang/String;
}

public abstract interface class kotlinx/kover/features/jvm/OfflineInstrumenter {
Expand Down
2 changes: 1 addition & 1 deletion kover-features-jvm/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ plugins {
id("kover-release-conventions")
}

extensions.configure<Kover_publishing_conventions_gradle.KoverPublicationExtension> {
koverPublication {
description.set("Implementation of calling the main features of Kover programmatically")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,30 @@ public object KoverLegacyFeatures {

return result
}

public fun violationMessage(violations: List<RuleViolations>): String {
if (violations.isEmpty()) {
return ""
}
val messageBuilder = StringBuilder()

violations.forEach { rule ->
val namedRule = if (rule.rule.name.isNotEmpty()) "Rule '${rule.rule.name}'" else "Rule"

if (rule.violations.size == 1) {
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))
}
}
}

return messageBuilder.toString()
}
}

/**
Expand Down Expand Up @@ -251,3 +275,30 @@ public data class ClassFilters(
*/
public val excludeInheritedFrom: Set<String>
)

private fun BoundViolation.format(rule: RuleViolations): String {
val directionText = if (isMax) "maximum" else "minimum"

val metricText = when (bound.coverageUnits) {
CoverageUnit.LINE -> "lines"
CoverageUnit.INSTRUCTION -> "instructions"
CoverageUnit.BRANCH -> "branches"
}

val valueTypeText = when (bound.aggregationForGroup) {
AggregationType.COVERED_COUNT -> "covered count"
AggregationType.MISSED_COUNT -> "missed count"
AggregationType.COVERED_PERCENTAGE -> "covered percentage"
AggregationType.MISSED_PERCENTAGE -> "missed percentage"
}

val entityText = when (rule.rule.groupBy) {
GroupingBy.APPLICATION -> ""
GroupingBy.CLASS -> " for class '$entityName'"
GroupingBy.PACKAGE -> " for package '$entityName'"
}

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

return "$metricText $valueTypeText$entityText is $value, but expected $directionText is $expectedValue"
}
4 changes: 2 additions & 2 deletions kover-gradle-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -189,13 +189,13 @@ tasks.dokkaHtml {
}
}

extensions.configure<Kover_docs_conventions_gradle.KoverDocsExtension> {
koverDocs {
docsDirectory.set("gradle-plugin")
description.set("Kover Gradle Plugin")
callDokkaHtml.set(true)
}

extensions.configure<Kover_publishing_conventions_gradle.KoverPublicationExtension> {
koverPublication {
description.set("Kover Gradle Plugin - Kotlin code coverage")
//`java-gradle-plugin` plugin already creates publication with name `pluginMaven`
addPublication.set(false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

package kotlinx.kover.gradle.plugin.tasks.reports

import kotlinx.kover.features.jvm.KoverLegacyFeatures
import kotlinx.kover.gradle.plugin.commons.VerificationRule
import kotlinx.kover.gradle.plugin.tools.generateErrorMessage
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.*
Expand All @@ -24,7 +24,7 @@ internal abstract class KoverDoVerifyTask @Inject constructor(@get:Internal over
val enabledRules = rules.get().filter { it.isEnabled }
val violations = tool.get().verify(enabledRules, context())

val errorMessage = generateErrorMessage(violations)
val errorMessage = KoverLegacyFeatures.violationMessage(violations)
resultFile.get().asFile.writeText(errorMessage)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,57 +41,3 @@ internal data class CoverageRequest(
val header: String?,
val lineFormat: String,
): Serializable

internal fun generateErrorMessage(violations: List<RuleViolations>): String {
if (violations.isEmpty()) {
return ""
}
val messageBuilder = StringBuilder()

violations.forEach { rule ->
val namedRule = if (rule.rule.name.isNotEmpty()) "Rule '${rule.rule.name}'" else "Rule"

if (rule.violations.size == 1) {
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))
}
}
}

return messageBuilder.toString()
}

private fun BoundViolation.format(rule: RuleViolations): String {
val directionText = if (isMax) "maximum" else "minimum"

val metricText = when (bound.coverageUnits) {
FeatureCoverageUnit.LINE -> "lines"
FeatureCoverageUnit.INSTRUCTION -> "instructions"
FeatureCoverageUnit.BRANCH -> "branches"
}

val valueTypeText = when (bound.aggregationForGroup) {
FeatureAggregationType.COVERED_COUNT -> "covered count"
FeatureAggregationType.MISSED_COUNT -> "missed count"
FeatureAggregationType.COVERED_PERCENTAGE -> "covered percentage"
FeatureAggregationType.MISSED_PERCENTAGE -> "missed percentage"
}

val entityText = when (rule.rule.groupBy) {
GroupingBy.APPLICATION -> ""
GroupingBy.CLASS -> " for class '$entityName'"
GroupingBy.PACKAGE -> " for package '$entityName'"
}

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

return "$metricText $valueTypeText$entityText is $value, but expected $directionText is $expectedValue"
}

private typealias FeatureCoverageUnit = kotlinx.kover.features.jvm.CoverageUnit
private typealias FeatureAggregationType = kotlinx.kover.features.jvm.AggregationType
4 changes: 2 additions & 2 deletions kover-jvm-agent/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ plugins {
id("kover-release-conventions")
}

extensions.configure<Kover_publishing_conventions_gradle.KoverPublicationExtension> {
koverPublication {
description.set("Kover JVM instrumentation agent")
}

extensions.configure<Kover_docs_conventions_gradle.KoverDocsExtension> {
koverDocs {
docsDirectory.set("jvm-agent")
description.set("Kover JVM instrumentation agent")
}
Expand Down
101 changes: 101 additions & 0 deletions kover-maven-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation

/*
* Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

plugins {
kotlin("jvm")
id("kover-publishing-conventions")
id("kover-docs-conventions")
id("kover-release-conventions")

alias(libs.plugins.mavenPluginDevelopment)
}


repositories {
mavenCentral()
}

sourceSets {
create("functionalTest")
}

// name of configuration for functionalTest source set with implementation dependencies
val functionalTestImplementation = "functionalTestImplementation"


dependencies {
implementation(project(":kover-features-jvm"))
implementation(project(":kover-jvm-agent"))

snapshotRelease(project(":kover-features-jvm"))
snapshotRelease(project(":kover-jvm-agent"))

compileOnly(libs.maven.plugin.annotations)
compileOnly(libs.maven.core)
implementation(libs.maven.reporting.api)

functionalTestImplementation(libs.maven.embedder)
functionalTestImplementation(libs.maven.compat)


functionalTestImplementation(libs.maven.resolver.basic)
functionalTestImplementation(libs.maven.resolver.file)
functionalTestImplementation(libs.maven.resolver.http)
functionalTestImplementation(libs.maven.slf4j.api)
functionalTestImplementation(libs.maven.slf4j.provider)


functionalTestImplementation(kotlin("test"))
functionalTestImplementation(libs.junit.jupiter)
functionalTestImplementation(libs.junit.params)
}

mavenPlugin {
goalPrefix = "kover"
}

kotlin {
jvmToolchain {
languageVersion.set(JavaLanguageVersion.of(8))
}
}

val functionalTest by tasks.registering(Test::class) {
group = LifecycleBasePlugin.VERIFICATION_GROUP
testClassesDirs = sourceSets["functionalTest"].output.classesDirs
classpath = sourceSets["functionalTest"].runtimeClasspath

// use JUnit 5
useJUnitPlatform()

dependsOn(tasks.collectRepository)

val localRepository = layout.buildDirectory.dir("maven-collected")
doFirst {
systemProperties["kotlinVersion"] = embeddedKotlinVersion
systemProperties["koverVersion"] = version

val dir = localRepository.get().asFile
dir.deleteRecursively()
dir.mkdirs()

tasks.collectRepository.get().repositories.forEach { repository ->
repository.copyRecursively(dir)
}

systemProperties["snapshotRepository"] = dir.absolutePath
}
}

tasks.check {
dependsOn(functionalTest)
}

koverDocs {
docsDirectory.set("maven-plugin")
description.set("Kover Maven Plugin")
callDokkaHtml.set(false)
}
Loading

0 comments on commit 1739cc5

Please sign in to comment.