Skip to content

Commit

Permalink
add support for code coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
hchauvin committed May 12, 2018
1 parent 2ab1268 commit 41c42db
Show file tree
Hide file tree
Showing 25 changed files with 399 additions and 39 deletions.
6 changes: 5 additions & 1 deletion kotlin/builder/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,17 @@ kotlin_worker_lib(
"@io_bazel_rules_kotlin_javax_inject_javax_inject//jar",
"@io_bazel_rules_kotlin_com_google_guava_guava//jar",
"@io_bazel_rules_kotlin_com_google_code_gson_gson//jar",
"@io_bazel_rules_kotlin_org_jacoco_org_jacoco_core//jar",
],
exports = ["//kotlin/builder/proto"],
runtime_deps = [
"@com_github_jetbrains_kotlin//:kotlin-stdlib",
"@com_github_jetbrains_kotlin//:kotlin-stdlib-jdk7",
"@com_github_jetbrains_kotlin//:kotlin-stdlib-jdk8",
"@com_github_jetbrains_kotlin//:kotlin-reflect"
"@com_github_jetbrains_kotlin//:kotlin-reflect",
"@io_bazel_rules_kotlin_org_ow2_asm_asm_commons//jar",
"@io_bazel_rules_kotlin_org_ow2_asm_asm//jar",
"@io_bazel_rules_kotlin_org_ow2_asm_asm_tree//jar",
]
)

Expand Down
11 changes: 11 additions & 0 deletions kotlin/builder/integrationtests/KotlinBuilderActionTests.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.bazel.kotlin.builder;

import io.bazel.kotlin.builder.mode.jvm.actions.JacocoProcessor;
import io.bazel.kotlin.builder.mode.jvm.actions.KotlinCompiler;
import org.junit.Test;

Expand All @@ -11,4 +12,14 @@ public void testCompileSimple() {
assertFileExists(DirectoryType.CLASSES, "something/AClass.class");
assertFileDoesNotExist(outputs().getOutput());
}

@Test
public void testCoverage() {
addSource("AClass.kt", "package something;" + "class AClass{}");
instance(KotlinCompiler.class).compile(builderCommand());
instance(JacocoProcessor.class).instrument(builderCommand());
assertFileExists(DirectoryType.CLASSES, "something/AClass.class");
assertFileExists(DirectoryType.CLASSES, "something/AClass.class.uninstrumented");
assertFileDoesNotExist(outputs().getOutput());
}
}
9 changes: 9 additions & 0 deletions kotlin/builder/integrationtests/KotlinBuilderTestCase.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ abstract class KotlinBuilderTestCase {
private String label = null;
private Path inputSourceDir = null;

protected void setPostProcessor(String postProcessor) {
builder.setInfo(builder.getInfo().toBuilder().setPostProcessor(postProcessor));
}

@Before
public void setupNext() {
resetTestContext("a_test_" + counter.incrementAndGet());
Expand Down Expand Up @@ -150,6 +154,11 @@ void assertFileExists(DirectoryType dir, String filePath) {
assertFileExists(file.toString());
}

void assertFileDoesNotExist(DirectoryType dir, String filePath) {
Path file = DirectoryType.select(dir, builderCommand()).resolve(filePath);
assertFileDoesNotExist(file.toString());
}

void assertFileDoesNotExist(String filePath) {
assertWithMessage("file exisst: " + filePath).that(new File(filePath).exists()).isFalse();
}
Expand Down
10 changes: 10 additions & 0 deletions kotlin/builder/integrationtests/KotlinBuilderTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public void testSimpleCompile() {
addSource("AClass.kt", "package something;" + "class AClass{}");
runCompileTask();
assertFileExists(DirectoryType.CLASSES, "something/AClass.class");
assertFileDoesNotExist(DirectoryType.CLASSES, "something/AClass.class.uninstrumented");
}

@Test
Expand All @@ -31,6 +32,15 @@ public void testMixedModeCompile() {
assertFileExists(outputs().getOutput());
}

@Test
public void testCoverage() {
setPostProcessor("jacoco");
addSource("AClass.kt", "package something;" + "class AClass{}");
runCompileTask();
assertFileExists(DirectoryType.CLASSES, "something/AClass.class");
assertFileExists(DirectoryType.CLASSES, "something/AClass.class.uninstrumented");
}

private void runCompileTask() {
int timeoutSeconds = 10;
KotlinJvmCompilationExecutor executor = instance(KotlinJvmCompilationExecutor.class);
Expand Down
Binary file modified kotlin/builder/proto/jars/libkotlin_model_proto-speed.jar
Binary file not shown.
2 changes: 2 additions & 0 deletions kotlin/builder/proto/kotlin_model.proto
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ message BuilderCommand {

// derived from plugins
repeated string encoded_plugin_descriptors=9;

string post_processor=10;
}

message Outputs {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,9 @@ private class DefaultBuildCommandBuilder @Inject constructor(
with(root.infoBuilder) {
label = argMap.mandatorySingle(JavaBuilderFlags.TARGET_LABEL.flag)
ruleKind = argMap.mandatorySingle(JavaBuilderFlags.RULE_KIND.flag)
kotlinModuleName = argMap.optionalSingle("--kotlin_module_name")
passthroughFlags = argMap.optionalSingle("--kotlin_passthrough_flags")
kotlinModuleName = argMap.optionalSingle("--kotlin_module_name") ?: ""
passthroughFlags = argMap.optionalSingle("--kotlin_passthrough_flags") ?: ""
postProcessor = argMap.optionalSingle("--post_processor") ?: ""
toolchainInfoBuilder.commonBuilder.apiVersion = argMap.mandatorySingle("--kotlin_api_version")
toolchainInfoBuilder.commonBuilder.languageVersion = argMap.mandatorySingle("--kotlin_language_version")
toolchainInfoBuilder.jvmBuilder.jvmTarget = argMap.mandatorySingle("--kotlin_jvm_target")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import io.bazel.kotlin.builder.mode.jvm.actions.JDepsGenerator
import io.bazel.kotlin.builder.mode.jvm.actions.JavaCompiler
import io.bazel.kotlin.builder.mode.jvm.actions.KotlinCompiler
import io.bazel.kotlin.builder.mode.jvm.actions.OutputJarCreator
import io.bazel.kotlin.builder.mode.jvm.actions.JacocoProcessor
import io.bazel.kotlin.builder.mode.jvm.utils.KotlinCompilerOutputSink
import io.bazel.kotlin.model.KotlinModel.BuilderCommand
import java.io.File
Expand All @@ -46,14 +47,20 @@ private class DefaultKotlinJvmCompilationExecutor @Inject constructor(
private val outputSink: KotlinCompilerOutputSink,
private val javaCompiler: JavaCompiler,
private val jDepsGenerator: JDepsGenerator,
private val outputJarCreator: OutputJarCreator
private val outputJarCreator: OutputJarCreator,
private val jacocoProcessor: JacocoProcessor
) : KotlinJvmCompilationExecutor {
override fun compile(command: BuilderCommand): Result {
val context = Context()
val commandWithApSources = context.execute("kapt") {
runAnnotationProcessors(command)
}
compileClasses(context, commandWithApSources)
if (command.info.postProcessor == "jacoco") {
context.execute("instrument class files") {
jacocoProcessor.instrument(commandWithApSources)
}
}
context.execute("create jar") {
outputJarCreator.createOutputJar(commandWithApSources)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright 2018 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.mode.jvm.actions

import io.bazel.kotlin.builder.KotlinToolchain
import org.jacoco.core.instr.Instrumenter
import org.jacoco.core.runtime.OfflineInstrumentationAccessGenerator
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
import java.io.IOException
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
import io.bazel.kotlin.model.KotlinModel
import com.google.devtools.build.lib.view.proto.Deps
import com.google.inject.ImplementedBy
import com.google.inject.Inject

@ImplementedBy(DefaultJacocoProcessor::class)
interface JacocoProcessor {
fun instrument(command: KotlinModel.BuilderCommand)
}

class DefaultJacocoProcessor @Inject constructor(
val compiler: KotlinToolchain.KotlincInvoker
) : JacocoProcessor {
override fun instrument(command: KotlinModel.BuilderCommand) {
val classDir = Paths.get(command.outputs.classDirectory)
val instr = Instrumenter(OfflineInstrumentationAccessGenerator())

// Runs Jacoco instrumentation processor over all .class files.
Files.walkFileTree(
classDir,
object : SimpleFileVisitor<Path>() {
override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult {
if (!file.fileName.toString().endsWith(".class")) {
return FileVisitResult.CONTINUE
}

val uninstrumentedCopy = Paths.get(file.toString() + ".uninstrumented")
Files.move(file, uninstrumentedCopy)
BufferedInputStream(Files.newInputStream(uninstrumentedCopy)).use { input ->
BufferedOutputStream(Files.newOutputStream(file)).use { output ->
instr.instrument(input, output, file.toString())
}
}
return FileVisitResult.CONTINUE
}
})
}
}
17 changes: 15 additions & 2 deletions kotlin/internal/compile.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ def _kotlin_do_compile_action(ctx, rule_kind, output_jar, compile_jars):
if len(plugin_info.annotation_processors) > 0:
args += [ "--kt-plugins", plugin_info.to_json() ]

# Post-process class files with the Jacoco offline instrumenter, if needed.
if ctx.coverage_instrumented() or ctx.attr.internal_coverage_instrumented:
args += [ "--post_processor", "jacoco" ]

# Declare and write out argument file.
args_file = ctx.actions.declare_file(ctx.label.name + ".jar-2.params")
ctx.actions.write(args_file, "\n".join(args))
Expand Down Expand Up @@ -140,7 +144,7 @@ def _make_java_provider(ctx, auto_deps=[]):
transitive_runtime_jars=my_transitive_runtime_jars
)

def _make_providers(ctx, java_info, transitive_files=depset(order="default")):
def _make_providers(ctx, java_info, transitive_files=depset(order="default"), extra_runfiles=[]):
kotlin_info=kt.info.KtInfo(
srcs=ctx.files.srcs,
# intelij aspect needs this.
Expand All @@ -154,9 +158,13 @@ def _make_providers(ctx, java_info, transitive_files=depset(order="default")):
),
)

files = [ctx.outputs.jar]
if hasattr(ctx.outputs, "executable"):
files.append(ctx.outputs.executable)
default_info = DefaultInfo(
files=depset([ctx.outputs.jar]),
files=depset(files),
runfiles=ctx.runfiles(
files=extra_runfiles + [ctx.outputs.jar],
transitive_files=transitive_files,
collect_default=True
),
Expand All @@ -165,6 +173,11 @@ def _make_providers(ctx, java_info, transitive_files=depset(order="default")):
return struct(
kt=kotlin_info,
providers=[java_info,default_info,kotlin_info],
instrumented_files = struct(
extensions = ['.kt'],
source_attributes = ['srcs'],
dependency_attributes = ['deps', 'runtime_deps'],
)
)

def _compile_action (ctx, rule_kind):
Expand Down
51 changes: 28 additions & 23 deletions kotlin/internal/rules.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -76,44 +76,49 @@ def kt_jvm_import_impl(ctx):
return struct(kt = kotlin_info, providers= [default_info, java_info, kotlin_info])

def kt_jvm_library_impl(ctx):
return compile.make_providers(ctx, compile.compile_action(ctx, "kt_jvm_library"))

def kt_jvm_binary_impl(ctx):
java_info = compile.compile_action(ctx, "kt_jvm_binary")
utils.actions.write_launcher(
ctx,
java_info.transitive_runtime_jars,
ctx.attr.main_class,
ctx.attr.jvm_flags
)
java_info = compile.compile_action(ctx, "kt_jvm_library")
return compile.make_providers(
ctx,
java_info,
depset(
order = "default",
transitive=[java_info.transitive_runtime_jars],
direct=[ctx.executable._java]
)
)

def kt_jvm_junit_test_impl(ctx):
java_info = compile.compile_action(ctx, "kt_jvm_test")
def _kt_jvm_runnable_impl(ctx, rule_kind, launcher_jvm_flags=[]):
java_info = compile.compile_action(ctx, rule_kind)

transitive_runtime_jars = java_info.transitive_runtime_jars + ctx.files._bazel_test_runner
launcherJvmFlags = ["-ea", "-Dbazel.test_suite=%s"% ctx.attr.test_class]
transitive_runtime_jars = java_info.transitive_runtime_jars
if rule_kind == "kt_jvm_test":
transitive_runtime_jars += ctx.files._bazel_test_runner
if ctx.configuration.coverage_enabled or ctx.attr.internal_coverage_enabled:
transitive_runtime_jars += ctx.files._jacocorunner

utils.actions.write_launcher(
extra_runfiles = utils.actions.write_launcher(
ctx,
transitive_runtime_jars,
main_class = ctx.attr.main_class,
jvm_flags = launcherJvmFlags + ctx.attr.jvm_flags,
jvm_flags = launcher_jvm_flags + ctx.attr.jvm_flags,
)
transitive_files = depset(
order = "default",
transitive=[transitive_runtime_jars],
direct=[ctx.executable._java],
)
return compile.make_providers(
ctx,
java_info,
depset(
order = "default",
transitive=[transitive_runtime_jars],
direct=[ctx.executable._java]
)
)
transitive_files,
extra_runfiles,
)

def kt_jvm_binary_impl(ctx):
return _kt_jvm_runnable_impl(ctx, "kt_jvm_binary")

def kt_jvm_junit_test_impl(ctx):
return _kt_jvm_runnable_impl(
ctx,
"kt_jvm_test",
launcher_jvm_flags = ["-ea", "-Dbazel.test_suite=%s" % ctx.attr.test_class],
)
47 changes: 39 additions & 8 deletions kotlin/internal/utils.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -182,20 +182,51 @@ def _write_launcher_action(ctx, rjars, main_class, jvm_flags, args="", wrapper_p
jvm_flags = " ".join([ctx.expand_location(f, ctx.attr.data) for f in jvm_flags])
template = ctx.attr._java_stub_template.files.to_list()[0]

workspace_prefix = ctx.workspace_name + "/"
substitutions = {
"%classpath%": classpath,
"%javabin%": "JAVABIN=${RUNPATH}" + ctx.executable._java.short_path,
"%jvm_flags%": jvm_flags,
"%workspace_prefix%": workspace_prefix,
}

extra_runfiles = []
if ctx.configuration.coverage_enabled or ctx.attr.internal_coverage_enabled:
metadata = ctx.new_file("coverage_runtime_classpath/%s/runtime-classpath.txt" % ctx.attr.name)
extra_runfiles.append(metadata)
# We replace '../' to get a runtime-classpath.txt as close as possible to the one
# produced by java_binary.
metadata_entries = [rjar.short_path.replace("../", "external/") for rjar in rjars]
ctx.file_action(metadata, content="\n".join(metadata_entries))
substitutions += {
"%java_start_class%": "com.google.testing.coverage.JacocoCoverageRunner",
# %set_jacoco_main_class% and %set_jacoco_java_runfiles_root% are not
# taken into account, so we cram everything with %set_jacoco_metadata%.
"%set_jacoco_metadata%": "\n".join([
"export JACOCO_METADATA_JAR=${JAVA_RUNFILES}/" + workspace_prefix + metadata.short_path,
"export JACOCO_MAIN_CLASS=" + main_class,
"export JACOCO_JAVA_RUNFILES_ROOT=${JAVA_RUNFILES}/" + workspace_prefix,
]),
"%set_jacoco_main_class%": "",
"%set_jacoco_java_runfiles_root%": "",
}
else:
substitutions += {
"%java_start_class%": main_class,
"%set_jacoco_metadata%": "",
"%set_jacoco_main_class%": "",
"%set_jacoco_java_runfiles_root%": "",
}

ctx.actions.expand_template(
template = template,
output = ctx.outputs.executable,
substitutions = {
"%classpath%": classpath,
"%java_start_class%": main_class,
"%javabin%": "JAVABIN=${RUNPATH}" + ctx.executable._java.short_path,
"%jvm_flags%": jvm_flags,
"%set_jacoco_metadata%": "",
"%workspace_prefix%": ctx.workspace_name + "/",
},
substitutions = substitutions,
is_executable = True,
)

return extra_runfiles

# EXPORT #######################################################################################################################################################
utils = struct(
actions = struct(
Expand Down
Loading

1 comment on commit 41c42db

@hsyed
Copy link
Contributor

@hsyed hsyed commented on 41c42db May 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool, I looked at the work before. I need some way of verifying it in a real workspace. Afaik the intellij plugin doesnt work for bazel (only blaze).

Please sign in to comment.