Skip to content

Commit 5380bc9

Browse files
shanshinsandwwraith
andcommitted
Implemented Kover Features artifact to integration with Android Gradle 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 <[email protected]>
1 parent ec84816 commit 5380bc9

File tree

17 files changed

+387
-50
lines changed

17 files changed

+387
-50
lines changed

docs/index.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
- [Kover Gradle Plugin](gradle-plugin)
2-
- [Kover Command Line Interface](cli)
2+
- [Kover Command Line Interface](cli)
3+
- [Kover offline instrumentation](offline-instrumentation)

docs/offline-instrumentation/index.md

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,32 @@ Offline instrumentation is suitable when using runtime environments that do not
1111

1212
### Class instrumentation
1313

14-
For instrumentation, you must first build the application, then the root directories for the class files
15-
must be passed to Kover CLI as arguments, see [Kover CLI](../cli#offline-instrumentation) for the technical detils.
14+
#### Instrumentation by Kover CLI
15+
The Kover CLI is a fat jar that needs to be called and passed certain commands through arguments.
16+
17+
For instrumentation, you must first build the application, then the root directories for the class files
18+
must be passed to Kover CLI as arguments, see [Kover CLI](../cli#offline-instrumentation) for the technical details.
19+
20+
#### Instrumentation by Kover Features
21+
Kover Features is a library that provides capabilities similar to Kover CLI and Kover Gradle plugin.
22+
23+
You can declare a dependency on Kover Features using following coordinates: `org.jetbrains.kotlinx:kover-features-jvm:0.7.5`.
24+
25+
Then you can use the Kover Features classes to instrument the bytecode of each class:
26+
```kotlin
27+
import kotlinx.kover.features.jvm.KoverFeatures
28+
// ...
29+
30+
val instrumenter = KoverFeatures.createOfflineInstrumenter()
31+
32+
// read class-file with name `fileName` bytes to `classBytes`
33+
val instrumentedBytes = instrumenter.instrument(classBytes, fileName)
34+
// save `instrumentedBytes` to file
35+
```
1636

1737
### Dump coverage result
1838

19-
To run classes instrumented offline, you'll need to add `org.jetbrains.kotlinx:kover-offline` artifact to the application's classpath.
39+
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.
2040

2141
There are several ways to get coverage:
2242

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

6686
## Logging
67-
`org.jetbrains.kotlinx:kover-offline` has its own logging system.
87+
`org.jetbrains.kotlinx:kover-offline-runtime` has its own logging system.
88+
89+
By default, warning and error messages are printed to standard error stream.
6890

69-
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.
91+
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.
7092

7193
## Examples
7294

7395
### Gradle example for binary report
7496

7597
Example of a custom binary report production using Kover tool CLI in Gradle
76-
```
98+
```kotlin
7799
plugins {
78100
kotlin("jvm") version "1.8.0"
79101
application

kover-cli/build.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ kotlin {
3333
}
3434

3535
dependencies {
36-
implementation(libs.intellij.reporter)
36+
implementation(project(":kover-features-jvm"))
37+
3738
implementation(libs.args4j)
3839

3940
testImplementation(kotlin("test"))

kover-cli/src/main/kotlin/kotlinx/kover/cli/commands/OfflineInstrumentCommand.kt

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,8 @@
1616

1717
package kotlinx.kover.cli.commands
1818

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

6463

6564
override fun call(output: PrintWriter, errorWriter: PrintWriter): Int {
66-
// disable ConDy for offline instrumentations
67-
System.setProperty("coverage.condy.enable", "false")
68-
69-
val outputRoots = ArrayList<File>(roots.size)
70-
for (i in roots.indices) {
71-
outputRoots.add(outputDir!!)
72-
}
73-
val filters = Filters(
74-
includeClasses.asPatterns(),
75-
excludeClasses.asPatterns(),
76-
excludeAnnotation.asPatterns()
65+
val filters = KoverLegacyFeatures.ClassFilters(
66+
includeClasses.asRegex().toSet(),
67+
excludeClasses.asRegex().toSet(),
68+
excludeAnnotation.asRegex().toSet()
7769
)
7870

7971
try {
80-
OfflineInstrumentationApi.instrument(roots, outputRoots, filters, countHits)
72+
KoverLegacyFeatures.instrument(outputDir!!, roots, filters, countHits)
8173
} catch (e: Exception) {
8274
errorWriter.println("Instrumentation failed: " + e.message)
8375
return -1

kover-cli/src/main/kotlin/kotlinx/kover/cli/commands/ReportCommand.kt

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616

1717
package kotlinx.kover.cli.commands
1818

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

7878

7979
override fun call(output: PrintWriter, errorWriter: PrintWriter): Int {
80-
val filters = Filters(
81-
includeClasses.asPatterns(),
82-
excludeClasses.asPatterns(),
83-
excludeAnnotation.asPatterns()
80+
val filters = ClassFilters(
81+
includeClasses.asRegex().toSet(),
82+
excludeClasses.asRegex().toSet(),
83+
excludeAnnotation.asRegex().toSet()
8484
)
85+
8586
var fail = false
8687
if (xmlFile != null) {
8788
try {
88-
ReportApi.xmlReport(xmlFile, title ?: "Kover XML Report", binaryReports, outputRoots, sourceRoots, filters)
89+
KoverLegacyFeatures.generateXmlReport(xmlFile, binaryReports, outputRoots, sourceRoots, title ?: "Kover XML Report", filters)
8990
} catch (e: IOException) {
9091
fail = true
9192
errorWriter.println("XML generation failed: " + e.message)
9293
}
9394
}
9495
if (htmlDir != null) {
9596
try {
96-
ReportApi.htmlReport(htmlDir, title, null, binaryReports, outputRoots, sourceRoots, filters)
97+
KoverLegacyFeatures.generateHtmlReport(htmlDir, binaryReports, outputRoots, sourceRoots, title ?: "Kover HTML Report", filters)
9798
} catch (e: IOException) {
9899
fail = true
99100
errorWriter.println("HTML generation failed: " + e.message)

kover-cli/src/main/kotlin/kotlinx/kover/cli/util/KoverUtils.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@
1616

1717
package kotlinx.kover.cli.util
1818

19-
import java.util.regex.Pattern
20-
21-
internal fun List<String>.asPatterns(): List<Pattern> = map { Pattern.compile(it.wildcardsToRegex()) }
19+
internal fun List<String>.asRegex(): List<String> = map { it.wildcardsToRegex() }
2220

2321
/**
2422
* Replaces characters `*` or `.` to `.*` and `.` regexp characters and also add escape char '\' before regexp metacharacters (see [regexMetacharactersSet]).

kover-features-jvm/build.gradle.kts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright 2000-2024 JetBrains s.r.o.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
plugins {
18+
java
19+
id("kover-publishing-conventions")
20+
}
21+
22+
extensions.configure<Kover_publishing_conventions_gradle.KoverPublicationExtension> {
23+
description.set("Implementation of calling the main features of Kover programmatically")
24+
fatJar.set(true)
25+
}
26+
27+
java {
28+
sourceCompatibility = JavaVersion.VERSION_1_7
29+
targetCompatibility = JavaVersion.VERSION_1_7
30+
}
31+
32+
repositories {
33+
mavenCentral()
34+
}
35+
36+
tasks.processResources {
37+
val version = if (project.hasProperty("releaseVersion")) {
38+
project.property("releaseVersion").toString()
39+
} else {
40+
project.version.toString()
41+
}
42+
43+
filesMatching("**/kover.version") {
44+
filter {
45+
it.replace("\$version", version)
46+
}
47+
}
48+
}
49+
50+
dependencies {
51+
implementation(libs.intellij.reporter)
52+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package kotlinx.kover.features.jvm;
2+
3+
/**
4+
* Internal class to control JVM ConDy settings.
5+
*/
6+
final class ConDySettings {
7+
8+
private ConDySettings() {
9+
// no-op
10+
}
11+
12+
private static final String CONDY_SYSTEM_PARAM_NAME = "coverage.condy.enable";
13+
14+
/**
15+
* Disable JVM ConDy during instrumentation.
16+
*
17+
* @return previous value of ConDy setting
18+
*/
19+
static String disableConDy() {
20+
// disable ConDy for offline instrumentations
21+
return System.setProperty(CONDY_SYSTEM_PARAM_NAME, "false");
22+
}
23+
24+
/**
25+
* Restore previous value of JVM ConDy setting.
26+
*
27+
* @param prevValue new setting value
28+
*/
29+
static void restoreConDy(String prevValue) {
30+
if (prevValue == null) {
31+
System.clearProperty(CONDY_SYSTEM_PARAM_NAME);
32+
} else {
33+
System.setProperty(CONDY_SYSTEM_PARAM_NAME, prevValue);
34+
}
35+
}
36+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package kotlinx.kover.features.jvm;
2+
3+
import java.io.InputStream;
4+
import java.util.Scanner;
5+
6+
/**
7+
* A class for using features via Java calls.
8+
*/
9+
public class KoverFeatures {
10+
private static final String koverVersion = readVersion();
11+
12+
/**
13+
* Getting the Kover version.
14+
*
15+
* @return The version of Kover used in these utilities.
16+
*/
17+
public static String getVersion() {
18+
return koverVersion;
19+
}
20+
21+
/**
22+
* Create instance to instrument already compiled class-files.
23+
*
24+
* @return instrumenter for offline instrumentation.
25+
*/
26+
public static OfflineInstrumenter createOfflineInstrumenter() {
27+
return new OfflineInstrumenterImpl(false);
28+
}
29+
30+
private static String readVersion() {
31+
String version = "unrecognized";
32+
// read version from file in resources
33+
try (InputStream stream = KoverFeatures.class.getClassLoader().getResourceAsStream("kover.version")) {
34+
if (stream != null) {
35+
version = new Scanner(stream).nextLine();
36+
}
37+
} catch (Throwable e) {
38+
// can't read
39+
}
40+
return version;
41+
}
42+
}

0 commit comments

Comments
 (0)