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 code coverage support #52

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
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