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

Add support for Kotlin coverage #508

Merged
merged 2 commits into from
Mar 20, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions kotlin/internal/jvm/compile.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@ def _run_kt_builder_action(
args.add_all("--source_jars", srcs.src_jars + generated_src_jars, omit_if_empty = True)
args.add_all("--deps_artifacts", deps_artifacts, omit_if_empty = True)
args.add_all("--kotlin_friend_paths", associates.jars, map_each = _associate_utils.flatten_jars)
args.add("--instrument_coverage", ctx.coverage_instrumented())

# Collect and prepare plugin descriptor for the worker.
args.add_all(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,6 @@ kt_bootstrap_library(
"@kotlin_rules_maven//:com_google_protobuf_protobuf_java",
"@kotlin_rules_maven//:com_google_protobuf_protobuf_java_util",
"@kotlin_rules_maven//:javax_inject_javax_inject",
"@bazel_tools//tools/jdk:JacocoCoverage",
],
)
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ class KotlinBuilder @Inject internal constructor(
BUILD_KOTLIN("--build_kotlin"),
STRICT_KOTLIN_DEPS("--strict_kotlin_deps"),
REDUCED_CLASSPATH_MODE("--reduced_classpath_mode"),
INSTRUMENT_COVERAGE("--instrument_coverage")
}
}

Expand Down Expand Up @@ -241,6 +242,7 @@ class KotlinBuilder @Inject internal constructor(

root.compileJava = argMap.mandatorySingle(JavaBuilderFlags.BUILD_JAVA).toBoolean()
root.compileKotlin = argMap.mandatorySingle(KotlinBuilderFlags.BUILD_KOTLIN).toBoolean()
root.instrumentCoverage = argMap.mandatorySingle(KotlinBuilderFlags.INSTRUMENT_COVERAGE).toBoolean()

with(root.outputsBuilder) {
argMap.optionalSingle(JavaBuilderFlags.OUTPUT)?.let { jar = it }
Expand Down Expand Up @@ -280,6 +282,8 @@ class KotlinBuilder @Inject internal constructor(
.toString()
generatedStubClasses =
workingDir.resolveNewDirectories(getOutputDirPath(moduleName, "stubs")).toString()
coverageMetadataClasses =
workingDir.resolveNewDirectories(getOutputDirPath(moduleName, "coverage-metadata")).toString()
}

with(root.inputsBuilder) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package io.bazel.kotlin.builder.tasks.jvm

import io.bazel.kotlin.builder.utils.bazelRuleKind
import io.bazel.kotlin.builder.utils.jars.JarCreator
import io.bazel.kotlin.model.JvmCompilationTask
import org.jacoco.core.instr.Instrumenter
import org.jacoco.core.runtime.OfflineInstrumentationAccessGenerator
import java.nio.file.FileVisitResult
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.SimpleFileVisitor
import java.nio.file.attribute.BasicFileAttributes

internal fun JvmCompilationTask.createCoverageInstrumentedJar() {
val instrumentedClassesDirectory = Paths.get(directories.coverageMetadataClasses)
Files.createDirectories(instrumentedClassesDirectory)

val instr = Instrumenter(OfflineInstrumentationAccessGenerator())

instrumentRecursively(instr, instrumentedClassesDirectory, Paths.get(directories.classes))
instrumentRecursively(instr, instrumentedClassesDirectory, Paths.get(directories.javaClasses))
instrumentRecursively(instr, instrumentedClassesDirectory, Paths.get(directories.generatedClasses))

val pathsForCoverage = instrumentedClassesDirectory.resolve( "${Paths.get(outputs.jar).fileName}-paths-for-coverage.txt")
Files.write(
pathsForCoverage,
inputs.javaSourcesList + inputs.kotlinSourcesList
)

JarCreator(
path = Paths.get(outputs.jar),
normalize = true,
verbose = false
).also {
it.addDirectory(Paths.get(directories.classes))
it.addDirectory(Paths.get(directories.javaClasses))
it.addDirectory(Paths.get(directories.generatedClasses))
it.addDirectory(instrumentedClassesDirectory)
it.setJarOwner(info.label, info.bazelRuleKind)
it.execute()
}
}

private fun instrumentRecursively(instr: Instrumenter, metadataDir: Path, root: Path) {
val visitor = object: SimpleFileVisitor<Path>() {
override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult {
if (file.toFile().extension != "class") {
return FileVisitResult.CONTINUE
}

val absoluteUninstrumentedCopy = Paths.get("$file.uninstrumented")
val uninstrumentedCopy = metadataDir.resolve(root.relativize(absoluteUninstrumentedCopy))

Files.createDirectories(uninstrumentedCopy.parent)
Files.move(file, uninstrumentedCopy)

Files.newInputStream(uninstrumentedCopy).buffered().use { input ->
Files.newOutputStream(file).buffered().use { output ->
instr.instrument(input, output, file.toString())
}
}

return FileVisitResult.CONTINUE
}
}
Files.walkFileTree(root, visitor)
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,11 @@ class KotlinJvmTaskExecutor @Inject internal constructor(
}

if (outputs.jar.isNotEmpty()) {
context.execute("create jar", ::createOutputJar)
if (instrumentCoverage) {
context.execute("create instrumented jar", ::createCoverageInstrumentedJar)
} else {
context.execute("create jar", ::createOutputJar)
}
}
if (outputs.abijar.isNotEmpty()) {
context.execute("create abi jar", ::createAbiJar)
Expand Down
3 changes: 3 additions & 0 deletions src/main/protobuf/kotlin_model.proto
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ message JvmCompilationTask {
string generated_java_sources = 7;
// The destination directory the Java compiler should use for class files.
string java_classes = 8;
// The destination directory for code coverage metadata.
string coverage_metadata_classes = 9;
}

// Outputs produced by the builder.
Expand Down Expand Up @@ -164,6 +166,7 @@ message JvmCompilationTask {
Inputs inputs = 4;
bool compile_java = 5;
bool compile_kotlin = 6;
bool instrument_coverage = 7;
}

message JsCompilationTask {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ public enum DirectoryType {
SOURCE_GEN("generated sources directory", Paths.get("generated_sources")),
JAVA_SOURCE_GEN("generated sources directory", Paths.get("generated_java_sources")),
GENERATED_STUBS("generated kotlin stubs directory", Paths.get("stubs")),
INCREMENTAL_DATA("generated kotlin stubs class directory", Paths.get("temp","incrementalData"));
INCREMENTAL_DATA("generated kotlin stubs class directory", Paths.get("temp", "incrementalData")),
COVERAGE_METADATA("generated kotlin stubs class directory", Paths.get("temp", "coverage-metadata"));

final String name;
final Path relativePath;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ public final class KotlinJvmTestBuilder extends KotlinAbstractTestBuilder<JvmCom
DirectoryType.SOURCE_GEN,
DirectoryType.JAVA_SOURCE_GEN,
DirectoryType.GENERATED_CLASSES,
DirectoryType.TEMP);
DirectoryType.TEMP,
DirectoryType.COVERAGE_METADATA);

private TaskBuilder taskBuilderInstance = new TaskBuilder();
private static KotlinBuilderTestComponent component;
Expand All @@ -72,8 +73,8 @@ void setupForNext(CompilationTaskInfo.Builder taskInfo) {
.setGeneratedJavaSources(directory(DirectoryType.JAVA_SOURCE_GEN).toAbsolutePath().toString())
.setGeneratedStubClasses(directory(DirectoryType.GENERATED_STUBS).toAbsolutePath().toString())
.setTemp(directory(DirectoryType.TEMP).toAbsolutePath().toString())
.setGeneratedClasses(
directory(DirectoryType.GENERATED_CLASSES).toAbsolutePath().toString());
.setGeneratedClasses(directory(DirectoryType.GENERATED_CLASSES).toAbsolutePath().toString())
.setCoverageMetadataClasses(directory(DirectoryType.COVERAGE_METADATA).toAbsolutePath().toString());
}

@Override
Expand Down Expand Up @@ -161,6 +162,11 @@ public TaskBuilder compileKotlin() {
return this;
}

public TaskBuilder coverage() {
taskBuilder.setInstrumentCoverage(true);
return this;
}

public void addAnnotationProcessors(AnnotationProcessor... annotationProcessors) {
Preconditions.checkState(
taskBuilder.getInputs().getProcessorsList().isEmpty(), "processors already set");
Expand Down
6 changes: 6 additions & 0 deletions src/test/kotlin/io/bazel/kotlin/builder/tasks/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ kt_rules_test(
srcs = ["jvm/KotlinBuilderJvmStrictDepsTest.kt"],
)

kt_rules_test(
name = "KotlinBuilderJvmCoverageTest",
size = "large",
srcs = ["jvm/KotlinBuilderJvmCoverageTest.kt"],
)

kt_rules_test(
name = "JdepsMergerTest",
srcs = ["jvm/JdepsMergerTest.kt"],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright 2020 The Bazel Authors. All rights reserved.
*
* 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.
*
*/
package io.bazel.kotlin.builder.tasks.jvm;

import io.bazel.kotlin.builder.DirectoryType
import io.bazel.kotlin.builder.KotlinJvmTestBuilder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import java.util.function.Consumer

@RunWith(JUnit4::class)
class KotlinBuilderJvmCoverageTest {
val ctx = KotlinJvmTestBuilder()

@Test
fun `generates coverage metadata`() {
val deps = ctx.runCompileTask(Consumer { c: KotlinJvmTestBuilder.TaskBuilder ->
c.addSource("JavaClass.java",
"""
package something;

class JavaClass {
}
""")
c.addSource("KotlinClass.kt",
"""
package something

class KotlinClass{}
""")
c.outputJar()
c.compileKotlin()
c.compileJava()
c.coverage()
c.outputJdeps()
c.outputJavaJdeps()
})

ctx.assertFilesExist(DirectoryType.COVERAGE_METADATA, "something/JavaClass.class.uninstrumented")
ctx.assertFilesExist(DirectoryType.COVERAGE_METADATA, "something/KotlinClass.class.uninstrumented")
ctx.assertFilesExist(DirectoryType.COVERAGE_METADATA, "jar_file.jar-paths-for-coverage.txt")
}
}