Skip to content

Commit

Permalink
Implemented Kover Features artifact to integration with Android Gradl…
Browse files Browse the repository at this point in the history
…e Plugin

1. Created published Kover artifact kover-features-jvm
2. Added Java API for offline instrumentation of single class
3. Created kotlinx.kover.features.jvm.KoverLegacyFeatures and put the methods used by the Kover CLI there
4. Removed the dependency on intellij-coverage-reporter from the Kover CLI and add the dependency on kover-features-jvm

Resolves #534
PR #536

Co-authored-by: Leonid Startsev <sandwwraith@users.noreply.github.com>
  • Loading branch information
shanshin and sandwwraith committed Feb 16, 2024
1 parent ec84816 commit 5380bc9
Show file tree
Hide file tree
Showing 17 changed files with 387 additions and 50 deletions.
3 changes: 2 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
- [Kover Gradle Plugin](gradle-plugin)
- [Kover Command Line Interface](cli)
- [Kover Command Line Interface](cli)
- [Kover offline instrumentation](offline-instrumentation)
34 changes: 28 additions & 6 deletions docs/offline-instrumentation/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,32 @@ Offline instrumentation is suitable when using runtime environments that do not

### Class instrumentation

For instrumentation, you must first build the application, then the root directories for the class files
must be passed to Kover CLI as arguments, see [Kover CLI](../cli#offline-instrumentation) for the technical detils.
#### Instrumentation by Kover CLI
The Kover CLI is a fat jar that needs to be called and passed certain commands through arguments.

For instrumentation, you must first build the application, then the root directories for the class files
must be passed to Kover CLI as arguments, see [Kover CLI](../cli#offline-instrumentation) for the technical details.

#### Instrumentation by Kover Features
Kover Features is a library that provides capabilities similar to Kover CLI and Kover Gradle plugin.

You can declare a dependency on Kover Features using following coordinates: `org.jetbrains.kotlinx:kover-features-jvm:0.7.5`.

Then you can use the Kover Features classes to instrument the bytecode of each class:
```kotlin
import kotlinx.kover.features.jvm.KoverFeatures
// ...

val instrumenter = KoverFeatures.createOfflineInstrumenter()

// read class-file with name `fileName` bytes to `classBytes`
val instrumentedBytes = instrumenter.instrument(classBytes, fileName)
// save `instrumentedBytes` to file
```

### Dump coverage result

To run classes instrumented offline, you'll need to add `org.jetbrains.kotlinx:kover-offline` artifact to the application's classpath.
To run classes instrumented offline (with CLI) or programmatically (with Kover Features), you'll need to add `org.jetbrains.kotlinx:kover-offline-runtime` artifact to the application's classpath.

There are several ways to get coverage:

Expand Down Expand Up @@ -64,16 +84,18 @@ Calling these methods is allowed only after all tests are completed. If the meth
See [example](#example-of-using-the-api).

## Logging
`org.jetbrains.kotlinx:kover-offline` has its own logging system.
`org.jetbrains.kotlinx:kover-offline-runtime` has its own logging system.

By default, warning and error messages are printed to standard error stream.

By default, error messages are saved to a file in the working directory with the name `kover-offline.log`. To change the path to this file, pass the `kover.offline.log.file.path` system property with new path.
It is also possible to save all log messages to a file, to do this, you need to pass the system property `kover.offline.log.file.path` with path to the log file.

## Examples

### Gradle example for binary report

Example of a custom binary report production using Kover tool CLI in Gradle
```
```kotlin
plugins {
kotlin("jvm") version "1.8.0"
application
Expand Down
3 changes: 2 additions & 1 deletion kover-cli/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ kotlin {
}

dependencies {
implementation(libs.intellij.reporter)
implementation(project(":kover-features-jvm"))

implementation(libs.args4j)

testImplementation(kotlin("test"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@

package kotlinx.kover.cli.commands

import com.intellij.rt.coverage.instrument.api.OfflineInstrumentationApi
import com.intellij.rt.coverage.report.api.Filters
import kotlinx.kover.cli.util.asPatterns
import kotlinx.kover.cli.util.asRegex
import kotlinx.kover.features.jvm.KoverLegacyFeatures
import org.kohsuke.args4j.Argument
import org.kohsuke.args4j.Option
import java.io.File
Expand Down Expand Up @@ -63,21 +62,14 @@ internal class OfflineInstrumentCommand : Command {


override fun call(output: PrintWriter, errorWriter: PrintWriter): Int {
// disable ConDy for offline instrumentations
System.setProperty("coverage.condy.enable", "false")

val outputRoots = ArrayList<File>(roots.size)
for (i in roots.indices) {
outputRoots.add(outputDir!!)
}
val filters = Filters(
includeClasses.asPatterns(),
excludeClasses.asPatterns(),
excludeAnnotation.asPatterns()
val filters = KoverLegacyFeatures.ClassFilters(
includeClasses.asRegex().toSet(),
excludeClasses.asRegex().toSet(),
excludeAnnotation.asRegex().toSet()
)

try {
OfflineInstrumentationApi.instrument(roots, outputRoots, filters, countHits)
KoverLegacyFeatures.instrument(outputDir!!, roots, filters, countHits)
} catch (e: Exception) {
errorWriter.println("Instrumentation failed: " + e.message)
return -1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@

package kotlinx.kover.cli.commands

import com.intellij.rt.coverage.report.api.Filters
import com.intellij.rt.coverage.report.api.ReportApi
import kotlinx.kover.cli.util.asPatterns
import kotlinx.kover.cli.util.asRegex
import kotlinx.kover.features.jvm.KoverLegacyFeatures
import kotlinx.kover.features.jvm.KoverLegacyFeatures.ClassFilters
import org.kohsuke.args4j.Argument
import org.kohsuke.args4j.Option
import java.io.File
Expand Down Expand Up @@ -77,23 +77,24 @@ internal class ReportCommand : Command {


override fun call(output: PrintWriter, errorWriter: PrintWriter): Int {
val filters = Filters(
includeClasses.asPatterns(),
excludeClasses.asPatterns(),
excludeAnnotation.asPatterns()
val filters = ClassFilters(
includeClasses.asRegex().toSet(),
excludeClasses.asRegex().toSet(),
excludeAnnotation.asRegex().toSet()
)

var fail = false
if (xmlFile != null) {
try {
ReportApi.xmlReport(xmlFile, title ?: "Kover XML Report", binaryReports, outputRoots, sourceRoots, filters)
KoverLegacyFeatures.generateXmlReport(xmlFile, binaryReports, outputRoots, sourceRoots, title ?: "Kover XML Report", filters)
} catch (e: IOException) {
fail = true
errorWriter.println("XML generation failed: " + e.message)
}
}
if (htmlDir != null) {
try {
ReportApi.htmlReport(htmlDir, title, null, binaryReports, outputRoots, sourceRoots, filters)
KoverLegacyFeatures.generateHtmlReport(htmlDir, binaryReports, outputRoots, sourceRoots, title ?: "Kover HTML Report", filters)
} catch (e: IOException) {
fail = true
errorWriter.println("HTML generation failed: " + e.message)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@

package kotlinx.kover.cli.util

import java.util.regex.Pattern

internal fun List<String>.asPatterns(): List<Pattern> = map { Pattern.compile(it.wildcardsToRegex()) }
internal fun List<String>.asRegex(): List<String> = map { it.wildcardsToRegex() }

/**
* Replaces characters `*` or `.` to `.*` and `.` regexp characters and also add escape char '\' before regexp metacharacters (see [regexMetacharactersSet]).
Expand Down
52 changes: 52 additions & 0 deletions kover-features-jvm/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright 2000-2024 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

plugins {
java
id("kover-publishing-conventions")
}

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

java {
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
}

repositories {
mavenCentral()
}

tasks.processResources {
val version = if (project.hasProperty("releaseVersion")) {
project.property("releaseVersion").toString()
} else {
project.version.toString()
}

filesMatching("**/kover.version") {
filter {
it.replace("\$version", version)
}
}
}

dependencies {
implementation(libs.intellij.reporter)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package kotlinx.kover.features.jvm;

/**
* Internal class to control JVM ConDy settings.
*/
final class ConDySettings {

private ConDySettings() {
// no-op
}

private static final String CONDY_SYSTEM_PARAM_NAME = "coverage.condy.enable";

/**
* Disable JVM ConDy during instrumentation.
*
* @return previous value of ConDy setting
*/
static String disableConDy() {
// disable ConDy for offline instrumentations
return System.setProperty(CONDY_SYSTEM_PARAM_NAME, "false");
}

/**
* Restore previous value of JVM ConDy setting.
*
* @param prevValue new setting value
*/
static void restoreConDy(String prevValue) {
if (prevValue == null) {
System.clearProperty(CONDY_SYSTEM_PARAM_NAME);
} else {
System.setProperty(CONDY_SYSTEM_PARAM_NAME, prevValue);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package kotlinx.kover.features.jvm;

import java.io.InputStream;
import java.util.Scanner;

/**
* A class for using features via Java calls.
*/
public class KoverFeatures {
private static final String koverVersion = readVersion();

/**
* Getting the Kover version.
*
* @return The version of Kover used in these utilities.
*/
public static String getVersion() {
return koverVersion;
}

/**
* Create instance to instrument already compiled class-files.
*
* @return instrumenter for offline instrumentation.
*/
public static OfflineInstrumenter createOfflineInstrumenter() {
return new OfflineInstrumenterImpl(false);
}

private static String readVersion() {
String version = "unrecognized";
// read version from file in resources
try (InputStream stream = KoverFeatures.class.getClassLoader().getResourceAsStream("kover.version")) {
if (stream != null) {
version = new Scanner(stream).nextLine();
}
} catch (Throwable e) {
// can't read
}
return version;
}
}
Loading

0 comments on commit 5380bc9

Please sign in to comment.