Skip to content

Commit 1eb1fca

Browse files
authored
Introduced Offline Runtime API for saving binary report
- implemented API method to get binary report as a byte array - implemented API method to save binary report - bump Intellij Coverage Library to `1.0.740` Co-authored-by: Leonid Startsev <sandwwraith@users.noreply.github.com> Resolves #503 PR #500
1 parent b3ca174 commit 1eb1fca

File tree

6 files changed

+181
-11
lines changed

6 files changed

+181
-11
lines changed

docs/offline-instrumentation/index.md

+50-8
Original file line numberDiff line numberDiff line change
@@ -18,27 +18,56 @@ must be passed to Kover CLI as arguments, see [Kover CLI](../cli#offline-instrum
1818

1919
To run classes instrumented offline, you'll need to add `org.jetbrains.kotlinx:kover-offline` artifact to the application's classpath.
2020

21-
There are two ways to get coverage:
21+
There are several ways to get coverage:
2222

23-
- Run tests to get a binary report file, then run [Kover CLI](../cli#generating-reports) to get HTML or XML report from binary report
24-
- Call `KoverRuntime.collectByDirs` or `KoverRuntime.collect` in the same process after the tests are finished
23+
- [Save binary report file when the JVM is shut down](#save-binary-report-on-shut-down)
24+
- [Save binary report in runtime by Kover API](#save-binary-report-in-runtime)
25+
- [Get binary report in runtime by Kover API](#get-binary-report-in-runtime)
26+
- [Get coverage details in runtime by Kover API](#get-coverage-details-in-runtime)
2527

26-
One or both of these ways can be used at the same time.
28+
Binary reports are presented in `ic` format, and can later be used in the [Kover CLI](../cli#generating-reports) to generate HTML or XML reports.
2729

28-
#### Binary report file
30+
#### Save binary report on shut down
2931

30-
You'll also need to pass the system property `kover.offline.report.path` to the application with the path where you want a binary report to be saved.
31-
This binary file can be used to generate human-readable reports using [Kover CLI](../cli#generating-reports).
32+
You'll need to pass the system property `kover.offline.report.path` to the application with the path where you want a binary report to be saved.
3233

33-
#### In-process reporting
34+
If this property is specified, then at the end of the JVM process,
35+
the binary coverage report will be saved to a file at the path passed in the parameter value.
36+
37+
If the file does not exist, it will be created. If a file with that name already exists, it will be overwritten.
38+
39+
#### Save binary report in runtime
40+
41+
Inside the same JVM process in which the tests were run, call Java static method `kotlinx.kover.offline.runtime.api.KoverRuntime.saveReport`.
42+
43+
If the file does not exist, it will be created. If a file already exists, it will be overwritten.
44+
45+
Calling this method is allowed only after all tests are completed. If the method is called in parallel with the execution of the measured code, the coverage value is unpredictable.
46+
47+
#### Get binary report in runtime
48+
49+
Inside the same JVM process in which the tests were run, call Java static method `kotlinx.kover.offline.runtime.api.KoverRuntime.getReport`.
50+
This method will return byte array with a binary coverage report, which can be saved to a file later.
51+
It is important that this byte array cannot be appended to an already existing file, and must be saved to a separate file.
52+
53+
Calling this method is allowed only after all tests are completed. If the method is called in parallel with the execution of the measured code, the coverage value is unpredictable.
54+
55+
#### Get coverage details in runtime
3456

3557
Inside the same JVM process in which the tests were run, call Java static method `kotlinx.kover.offline.runtime.api.KoverRuntime.collectByDirs` or `kotlinx.kover.offline.runtime.api.KoverRuntime.collect`.
3658

3759
For correct generation of the report, it is necessary to pass the bytecode of the non-instrumented classes.
3860
This can be done by specifying the directories where the class-files are stored, or a byte array with the bytecode of the application non-instrumented classes.
3961

62+
Calling these methods is allowed only after all tests are completed. If the method is called in parallel with the execution of the measured code, the coverage value is unpredictable.
63+
4064
See [example](#example-of-using-the-api).
4165

66+
## Logging
67+
`org.jetbrains.kotlinx:kover-offline` has its own logging system.
68+
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.
70+
4271
## Examples
4372

4473
### Gradle example for binary report
@@ -140,6 +169,19 @@ tasks.register("koverReport") {
140169

141170
### Example of using the API
142171
```kotlin
172+
val reportFile = Files.createTempFile("kover-report-", ".ic").toFile()
173+
174+
// save binary report to file
175+
KoverRuntime.saveReport(reportFile)
176+
177+
// get binary report as byte array
178+
val bytes = KoverRuntime.getReport()
179+
180+
// check reports are same
181+
val bytesFromFile = reportFile.readBytes()
182+
assertContentEquals(bytesFromFile, bytes)
183+
184+
143185
// the directory with class files can be transferred using the system property, any other methods are possible
144186
val outputDir = File(System.getProperty("output.dir"))
145187
val coverage = KoverRuntime.collectByDirs(listOf(outputDir))

gradle/libs.versions.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[versions]
22

3-
intellij-coverage = "1.0.738"
3+
intellij-coverage = "1.0.740"
44
junit = "5.9.0"
55
kotlinx-bcv = "0.13.2"
66
kotlinx-dokka = "1.8.10"

kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/dsl/KoverVersions.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public object KoverVersions {
1515
/**
1616
* Kover coverage tool version.
1717
*/
18-
public const val KOVER_TOOL_VERSION = "1.0.738"
18+
public const val KOVER_TOOL_VERSION = "1.0.740"
1919

2020
/**
2121
* JaCoCo coverage tool version used by default.

kover-offline-runtime/examples/runtime-api/src/test/kotlin/Tests.kt

+15
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,29 @@ package org.jetbrains.kotlinx.kover
22

33
import kotlinx.kover.offline.runtime.api.KoverRuntime
44
import java.io.File
5+
import java.nio.file.Files
56
import kotlin.test.Test
7+
import kotlin.test.assertContentEquals
68
import kotlin.test.assertEquals
79

810
class Tests {
911
@Test
1012
fun test() {
1113
MainClass().readState()
1214

15+
val reportFile = Files.createTempFile("kover-report-", ".ic").toFile()
16+
17+
// save binary report to file
18+
KoverRuntime.saveReport(reportFile)
19+
20+
// get binary report as byte array
21+
val bytes = KoverRuntime.getReport()
22+
23+
// check reports are same
24+
val bytesFromFile = reportFile.readBytes()
25+
assertContentEquals(bytesFromFile, bytes)
26+
27+
1328
val outputDir = File(System.getProperty("output.dir"))
1429
val coverage = KoverRuntime.collectByDirs(listOf(outputDir))
1530

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package kotlinx.kover.offline.runtime;
2+
3+
import com.intellij.rt.coverage.offline.api.CoverageRuntime;
4+
import kotlinx.kover.offline.runtime.api.KoverRuntime;
5+
6+
import java.io.File;
7+
8+
import static kotlinx.kover.offline.runtime.api.KoverRuntime.LOG_FILE_PROPERTY_NAME;
9+
import static kotlinx.kover.offline.runtime.api.KoverRuntime.REPORT_PROPERTY_NAME;
10+
11+
/**
12+
* Class for initializing the Kover offline instrumentation runtime.
13+
* <p>
14+
* This class is initialized by the instrumented code by class name when any of the instrumented method is executed for the first time.
15+
* Therefore, all initialization code must be placed in the {@code <clinit>} method.
16+
*/
17+
class KoverInit {
18+
19+
static {
20+
String reportNameSavedOnExitProp = System.getProperty(REPORT_PROPERTY_NAME);
21+
String logFileProp = System.getProperty(LOG_FILE_PROPERTY_NAME);
22+
23+
if (logFileProp != null) {
24+
CoverageRuntime.setLogPath(new File(LOG_FILE_PROPERTY_NAME));
25+
} else {
26+
CoverageRuntime.setLogPath(new File(KoverRuntime.DEFAULT_LOG_FILE_NAME));
27+
}
28+
29+
if (reportNameSavedOnExitProp != null) {
30+
saveOnExit(reportNameSavedOnExitProp);
31+
}
32+
}
33+
34+
private static void saveOnExit(final String fileName) {
35+
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
36+
public void run() {
37+
try {
38+
KoverRuntime.saveReport(new File(fileName));
39+
} catch (Throwable e) {
40+
System.err.println("Kover error: failed to save report file '" + fileName +"': " + e.getMessage());
41+
}
42+
}
43+
}));
44+
}
45+
46+
private KoverInit() {
47+
// no instances
48+
}
49+
}

kover-offline-runtime/src/main/java/kotlinx/kover/offline/runtime/api/KoverRuntime.java

+65-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import com.intellij.rt.coverage.offline.api.CoverageRuntime;
88

9-
import java.io.File;
9+
import java.io.*;
1010
import java.util.ArrayList;
1111
import java.util.List;
1212

@@ -16,6 +16,31 @@
1616
*/
1717
public class KoverRuntime {
1818

19+
/**
20+
* Default name of file with Kover offline logs.
21+
*
22+
* Can be overridden using the {@link KoverRuntime#LOG_FILE_PROPERTY_NAME} property.
23+
*/
24+
public static final String DEFAULT_LOG_FILE_NAME = "kover-offline.log";
25+
26+
/**
27+
* JVM property name used to define the path where the offline report will be stored.
28+
* <p>
29+
* If this property is specified, then at the end of the JVM process,
30+
* the binary coverage report will be saved to a file at the path passed in the parameter value.
31+
* <p>
32+
* If the file does not exist, it will be created. If a file with that name already exists, it will be overwritten.
33+
*/
34+
public static final String REPORT_PROPERTY_NAME = "kover.offline.report.path";
35+
36+
/**
37+
* JVM property name used to define the path to the file with Kover offline logs.
38+
*
39+
*<p>
40+
* If this property is not specified, the logs are saved to the {@link KoverRuntime#DEFAULT_LOG_FILE_NAME} file located in the current directory.
41+
*/
42+
public static final String LOG_FILE_PROPERTY_NAME = "kover.offline.log.file.path";
43+
1944
/**
2045
* Get classes coverage. For the correct collection of coverage, an analysis of the class-files is required.
2146
* <p/>
@@ -42,6 +67,45 @@ public static List<ClassCoverage> collect(List<byte[]> classFiles) {
4267
return convertClasses(CoverageRuntime.collectClassfileData(classFiles));
4368
}
4469

70+
/**
71+
* Save coverage binary report in file with ic format.
72+
* If the file does not exist, it will be created. If a file already exists, it will be overwritten.
73+
* <p/>
74+
* Calling this method is allowed only after all tests are completed. If the method is called in parallel with the execution of the measured code, the coverage value is unpredictable.
75+
*
76+
*
77+
* @param file the file to save binary report
78+
* @throws IOException in case of any error working with files
79+
*/
80+
public static void saveReport(File file) throws IOException {
81+
if (!file.exists()) {
82+
file.getParentFile().mkdirs();
83+
file.createNewFile();
84+
}
85+
86+
try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file)); DataOutputStream outputStream = new DataOutputStream(out)) {
87+
CoverageRuntime.dumpIcReport(outputStream);
88+
}
89+
}
90+
91+
/**
92+
* Get content of the coverage binary report with ic format.
93+
* The resulting byte array can be directly saved to an ic file for working with the CLI
94+
* <p/>
95+
* Calling this method is allowed only after all tests are completed. If the method is called in parallel with the execution of the measured code, the coverage value is unpredictable.
96+
*
97+
*
98+
* @return byte array with binary report in ic format
99+
* @throws IOException in case of any error working with files
100+
*/
101+
public static byte[] getReport() throws IOException {
102+
ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream();
103+
try (DataOutputStream outputStream = new DataOutputStream(byteArrayStream)) {
104+
CoverageRuntime.dumpIcReport(outputStream);
105+
}
106+
return byteArrayStream.toByteArray();
107+
}
108+
45109
private static List<ClassCoverage> convertClasses(List<com.intellij.rt.coverage.offline.api.ClassCoverage> origins) {
46110
ArrayList<ClassCoverage> result = new ArrayList<>(origins.size());
47111
for (com.intellij.rt.coverage.offline.api.ClassCoverage classCoverage : origins) {

0 commit comments

Comments
 (0)