add support for code coverage
diff --git a/kotlin/builder/BUILD b/kotlin/builder/BUILD
index 7a104a5..0c8703b 100644
--- a/kotlin/builder/BUILD
+++ b/kotlin/builder/BUILD
@@ -21,6 +21,12 @@
"@com_github_jetbrains_kotlin//:kotlin-script-runtime",
]
+ASM_DEPS = [
+ "@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",
+]
+
# Common depset for the builder.
COMMON_DEPS = [
"//kotlin/builder/proto:deps",
@@ -38,6 +44,7 @@
"@com_github_jetbrains_kotlin//:kotlin-stdlib",
"@com_github_jetbrains_kotlin//:kotlin-stdlib-jdk7",
"@com_github_jetbrains_kotlin//:kotlin-stdlib-jdk8",
+ "@io_bazel_rules_kotlin_org_jacoco_org_jacoco_core//jar",
]
# The compiler library, this is co-located in the kotlin compiler classloader.
@@ -73,8 +80,9 @@
name = "builder",
main_class = "io.bazel.kotlin.builder.KotlinBuilder",
visibility = ["//visibility:public"],
- runtime_deps = [
+ runtime_deps = ASM_DEPS + [
":builder_lib",
+ "@com_github_jetbrains_kotlin//:kotlin-reflect",
],
data = [
":compiler_lib.jar"
@@ -115,6 +123,7 @@
"//third_party/jvm/com/google/truth",
"//third_party/jvm/junit",
],
+ runtime_deps = ASM_DEPS
)
java_test(
diff --git a/kotlin/builder/integrationtests/KotlinBuilderActionTests.java b/kotlin/builder/integrationtests/KotlinBuilderActionTests.java
index a26c1a1..bc08407 100644
--- a/kotlin/builder/integrationtests/KotlinBuilderActionTests.java
+++ b/kotlin/builder/integrationtests/KotlinBuilderActionTests.java
@@ -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;
@@ -11,4 +12,14 @@
assertFileExists(DirectoryType.CLASSES, "something/AClass.class");
assertFileDoesNotExist(outputs().getJar());
}
+
+ @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().getJar());
+ }
}
diff --git a/kotlin/builder/integrationtests/KotlinBuilderTestCase.java b/kotlin/builder/integrationtests/KotlinBuilderTestCase.java
index f1cc005..015310c 100644
--- a/kotlin/builder/integrationtests/KotlinBuilderTestCase.java
+++ b/kotlin/builder/integrationtests/KotlinBuilderTestCase.java
@@ -29,6 +29,10 @@
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());
@@ -159,6 +163,11 @@
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();
}
diff --git a/kotlin/builder/integrationtests/KotlinBuilderTests.java b/kotlin/builder/integrationtests/KotlinBuilderTests.java
index cb5aa62..053abe0 100644
--- a/kotlin/builder/integrationtests/KotlinBuilderTests.java
+++ b/kotlin/builder/integrationtests/KotlinBuilderTests.java
@@ -21,6 +21,7 @@
addSource("AClass.kt", "package something;" + "class AClass{}");
runCompileTask();
assertFileExists(DirectoryType.CLASSES, "something/AClass.class");
+ assertFileDoesNotExist(DirectoryType.CLASSES, "something/AClass.class.uninstrumented");
}
@Test
@@ -33,6 +34,15 @@
assertFileExists(outputs().getJar());
}
+ @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() {
KotlinModel.BuilderCommand command = builderCommand();
for (DirectoryType directoryType : DirectoryType.values()) {
diff --git a/kotlin/builder/proto/BUILD b/kotlin/builder/proto/BUILD
index 8bc369b..4682b52 100644
--- a/kotlin/builder/proto/BUILD
+++ b/kotlin/builder/proto/BUILD
@@ -35,7 +35,7 @@
# name="%s_java_proto" % lib,
# deps=["%s_proto" % lib],
# ) for lib in _PROTO_LIBS]
-#
+
java_import(
name = "deps",
diff --git a/kotlin/builder/proto/jars/libkotlin_model_proto-speed.jar b/kotlin/builder/proto/jars/libkotlin_model_proto-speed.jar
index 943c343..d04c1b2 100755
--- a/kotlin/builder/proto/jars/libkotlin_model_proto-speed.jar
+++ b/kotlin/builder/proto/jars/libkotlin_model_proto-speed.jar
Binary files differ
diff --git a/kotlin/builder/proto/kotlin_model.proto b/kotlin/builder/proto/kotlin_model.proto
index d1e4ced..22760fd 100644
--- a/kotlin/builder/proto/kotlin_model.proto
+++ b/kotlin/builder/proto/kotlin_model.proto
@@ -87,6 +87,8 @@
// Jars that the kotlin compiler will allow package private access to.
repeated string friend_paths = 10;
+
+ string post_processor = 11;
}
// Directories used by the builder.
diff --git a/kotlin/builder/src/io/bazel/kotlin/builder/BuildCommandBuilder.kt b/kotlin/builder/src/io/bazel/kotlin/builder/BuildCommandBuilder.kt
index 54302c1..335cd02 100644
--- a/kotlin/builder/src/io/bazel/kotlin/builder/BuildCommandBuilder.kt
+++ b/kotlin/builder/src/io/bazel/kotlin/builder/BuildCommandBuilder.kt
@@ -123,6 +123,7 @@
}
passthroughFlags = argMap.optionalSingle("--kotlin_passthrough_flags")
addAllFriendPaths(argMap.mandatory("--kotlin_friend_paths"))
+ 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")
diff --git a/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/KotlinJvmCompilationExecutor.kt b/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/KotlinJvmCompilationExecutor.kt
index db1b42d..8f1f7d0 100644
--- a/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/KotlinJvmCompilationExecutor.kt
+++ b/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/KotlinJvmCompilationExecutor.kt
@@ -26,6 +26,7 @@
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
@@ -45,7 +46,8 @@
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()
@@ -53,6 +55,11 @@
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)
}
diff --git a/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/JacocoProcessor.kt b/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/JacocoProcessor.kt
new file mode 100644
index 0000000..abd0c9f
--- /dev/null
+++ b/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/JacocoProcessor.kt
@@ -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.directories.classes)
+ 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
+ }
+ })
+ }
+}
diff --git a/kotlin/internal/compile.bzl b/kotlin/internal/compile.bzl
index e08429a..94b68ea 100644
--- a/kotlin/internal/compile.bzl
+++ b/kotlin/internal/compile.bzl
@@ -68,6 +68,10 @@
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))
@@ -144,7 +148,7 @@
transitive_runtime_jars = my_transitive_runtime_jars
)
-def _make_providers(ctx, java_info, module_name, transitive_files=depset(order="default")):
+def _make_providers(ctx, java_info, module_name, transitive_files=depset(order="default"), extra_runfiles=[]):
kotlin_info=kt.info.KtInfo(
srcs=ctx.files.srcs,
module_name = module_name,
@@ -159,9 +163,13 @@
),
)
+ 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
),
@@ -170,6 +178,11 @@
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, module_name, friend_paths=depset(), src_jars=[]):
diff --git a/kotlin/internal/rules.bzl b/kotlin/internal/rules.bzl
index 79ee8b1..95e441d 100644
--- a/kotlin/internal/rules.bzl
+++ b/kotlin/internal/rules.bzl
@@ -77,21 +77,7 @@
def kt_jvm_library_impl(ctx):
module_name=utils.derive_module_name(ctx)
- return compile.make_providers(
- ctx,
- compile.compile_action(ctx, "kt_jvm_library", module_name),
- module_name,
- )
-
-def kt_jvm_binary_impl(ctx):
- module_name=utils.derive_module_name(ctx)
- java_info = compile.compile_action(ctx, "kt_jvm_binary", module_name)
- 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", module_name)
return compile.make_providers(
ctx,
java_info,
@@ -99,8 +85,42 @@
depset(
order = "default",
transitive=[java_info.transitive_runtime_jars],
- direct=[ctx.executable._java]
- ),
+ )
+ )
+
+def _kt_jvm_runnable_impl(ctx, rule_kind, module_name, launcher_jvm_flags=[], friend_paths=depset()):
+ java_info = compile.compile_action(ctx, rule_kind, module_name, friend_paths)
+
+ 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
+
+ extra_runfiles = utils.actions.write_launcher(
+ ctx,
+ transitive_runtime_jars,
+ main_class = ctx.attr.main_class,
+ 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,
+ module_name,
+ transitive_files,
+ extra_runfiles,
+ )
+
+def kt_jvm_binary_impl(ctx):
+ return _kt_jvm_runnable_impl(
+ ctx,
+ "kt_jvm_binary",
+ utils.derive_module_name(ctx)
)
def kt_jvm_junit_test_impl(ctx):
@@ -117,24 +137,10 @@
friend_paths += [j.path for j in friends[0][JavaInfo].compile_jars]
module_name = friends[0][kt.info.KtInfo].module_name
- java_info = compile.compile_action(ctx, "kt_jvm_test", module_name,friend_paths)
-
- transitive_runtime_jars = java_info.transitive_runtime_jars + ctx.files._bazel_test_runner
- launcherJvmFlags = ["-ea", "-Dbazel.test_suite=%s"% ctx.attr.test_class]
-
- utils.actions.write_launcher(
+ return _kt_jvm_runnable_impl(
ctx,
- transitive_runtime_jars,
- main_class = ctx.attr.main_class,
- jvm_flags = launcherJvmFlags + ctx.attr.jvm_flags,
+ rule_kind = "kt_jvm_test",
+ module_name = module_name,
+ launcher_jvm_flags = ["-ea", "-Dbazel.test_suite=%s" % ctx.attr.test_class],
+ friend_paths=friend_paths
)
- return compile.make_providers(
- ctx,
- java_info,
- module_name,
- depset(
- order = "default",
- transitive=[transitive_runtime_jars],
- direct=[ctx.executable._java]
- ),
- )
\ No newline at end of file
diff --git a/kotlin/internal/utils.bzl b/kotlin/internal/utils.bzl
index fd34a66..724e558 100644
--- a/kotlin/internal/utils.bzl
+++ b/kotlin/internal/utils.bzl
@@ -214,20 +214,51 @@
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(
diff --git a/kotlin/kotlin.bzl b/kotlin/kotlin.bzl
index 2ecd9a5..79c6fee 100644
--- a/kotlin/kotlin.bzl
+++ b/kotlin/kotlin.bzl
@@ -176,12 +176,28 @@
aspects = [_kt_jvm_plugin_aspect],
),
"module_name": attr.string(),
+ "internal_coverage_instrumented": attr.bool(
+ default = False,
+ doc = "visible for testing",
+ ),
}.items())
_runnable_common_attr = dict(_common_attr.items() + {
"jvm_flags": attr.string_list(
default = [],
),
+ "_jacocorunner": attr.label(
+ default = Label("@bazel_tools//tools/jdk:JacocoCoverage"),
+ ),
+ "_lcov_merger": attr.label(
+ default = Label("@bazel_tools//tools/test:LcovMerger"),
+ executable = True,
+ cfg = "target",
+ ),
+ "internal_coverage_enabled": attr.bool(
+ default = False,
+ doc = "visible for testing",
+ ),
}.items())
########################################################################################################################
diff --git a/tests/integrationtests/jvm/BUILD b/tests/integrationtests/jvm/BUILD
index 70bd38a..f86d868 100644
--- a/tests/integrationtests/jvm/BUILD
+++ b/tests/integrationtests/jvm/BUILD
@@ -33,12 +33,22 @@
data = [ "//examples/dagger:coffee_app"]
)
+kt_it_assertion_test(
+ name = "coverage_tests",
+ cases = "//tests/integrationtests/jvm/coverage:cases",
+ test_class="io.bazel.kotlin.testing.jvm.JvmCoverageFunctionalTests",
+ deps = [
+ "//tests/integrationtests/jvm/coverage:cases",
+ ],
+)
+
test_suite(
name = "jvm",
tests = [
":basic_tests",
":kapt_tests",
":example_tests",
+ ":coverage_tests",
"//tests/integrationtests/jvm/basic:friends_tests"
]
)
diff --git a/tests/integrationtests/jvm/JvmCoverageFunctionalTests.kt b/tests/integrationtests/jvm/JvmCoverageFunctionalTests.kt
new file mode 100644
index 0000000..1459117
--- /dev/null
+++ b/tests/integrationtests/jvm/JvmCoverageFunctionalTests.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.testing.jvm
+
+import com.google.common.truth.Truth.assertWithMessage
+import io.bazel.kotlin.testing.AssertionTestCase
+import org.junit.Test
+import java.nio.file.Files
+import java.nio.file.Paths
+import java.util.stream.Collectors
+
+class JvmCoverageFunctionalTests : AssertionTestCase("tests/integrationtests/jvm/coverage") {
+
+ @Test
+ fun testCoverage() {
+ val coverageOutputDir = Files.createTempDirectory(
+ Paths.get(System.getenv("TEST_TMPDIR")),
+ "coverage")
+ assertExecutableRunfileSucceeds(
+ "foo_test",
+ description = "Code coverage should by default be collected for :foo but not :foo_test",
+ environment = mapOf(
+ "JAVA_COVERAGE_FILE" to coverageOutputDir.resolve("coverage.dat").toString(),
+ "RUNPATH" to Paths.get("").toAbsolutePath().toString() + "/"))
+ val coverageReports = Files.list(coverageOutputDir)
+ .filter { it.toString().endsWith(".dat") }
+ .collect(Collectors.toList())
+ assertWithMessage("expected one and only one coverage report").that(coverageReports).hasSize(1)
+ val coverageReportLines = Files.readAllLines(coverageReports[0])
+ assertWithMessage("unexpected coverage report").that(coverageReportLines).isEqualTo(listOf(
+ "SF:simple/Foo.kt",
+ "FN:4,simple/Foo::exampleA ()Ljava/lang/String;",
+ "FNDA:1,simple/Foo::exampleA ()Ljava/lang/String;",
+ "FN:5,simple/Foo::exampleB ()Ljava/lang/String;",
+ "FNDA:0,simple/Foo::exampleB ()Ljava/lang/String;",
+ "FN:3,simple/Foo::<init> ()V",
+ "FNDA:1,simple/Foo::<init> ()V",
+ "DA:3,3",
+ "DA:4,2",
+ "DA:5,0",
+ "end_of_record"))
+ }
+}
diff --git a/tests/integrationtests/jvm/coverage/BUILD b/tests/integrationtests/jvm/coverage/BUILD
new file mode 100644
index 0000000..b906987
--- /dev/null
+++ b/tests/integrationtests/jvm/coverage/BUILD
@@ -0,0 +1,28 @@
+load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_jvm_library", "kt_jvm_test")
+
+kt_jvm_library(
+ name = "foo",
+ srcs = ["Foo.kt"],
+ internal_coverage_instrumented = True,
+)
+
+kt_jvm_test(
+ name = "foo_test",
+ srcs = ["FooTest.kt"],
+ test_class = "simple.FooTest",
+ deps = [
+ ":foo",
+ "@io_bazel_rules_kotlin_junit_junit//jar",
+ ],
+ size = "small",
+ internal_coverage_enabled = True,
+)
+
+filegroup(
+ name = "cases",
+ srcs = [
+ ":foo_test",
+ ],
+ visibility=["//tests/integrationtests:__subpackages__"],
+ testonly = True,
+)
diff --git a/tests/integrationtests/jvm/coverage/Foo.kt b/tests/integrationtests/jvm/coverage/Foo.kt
new file mode 100644
index 0000000..d4246a7
--- /dev/null
+++ b/tests/integrationtests/jvm/coverage/Foo.kt
@@ -0,0 +1,6 @@
+package simple
+
+class Foo {
+ fun exampleA() = "A"
+ fun exampleB() = "B" // Not covered
+}
diff --git a/tests/integrationtests/jvm/coverage/FooTest.kt b/tests/integrationtests/jvm/coverage/FooTest.kt
new file mode 100644
index 0000000..44816a3
--- /dev/null
+++ b/tests/integrationtests/jvm/coverage/FooTest.kt
@@ -0,0 +1,11 @@
+package simple
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+class FooTest {
+ @Test
+ fun exampleA() {
+ assertEquals("A", Foo().exampleA())
+ }
+}
diff --git a/tests/rules/AssertionTestCase.kt b/tests/rules/AssertionTestCase.kt
index 0e44ea7..965bc93 100644
--- a/tests/rules/AssertionTestCase.kt
+++ b/tests/rules/AssertionTestCase.kt
@@ -82,10 +82,16 @@
}
abstract class BasicAssertionTestCase {
- protected fun assertExecutableRunfileSucceeds(executable: String, description: String? = null) {
+ protected fun assertExecutableRunfileSucceeds(
+ executable: String,
+ description: String? = null,
+ environment: Map<String, String> = emptyMap()
+ ) {
ProcessBuilder().command("bash", "-c", Paths.get(executable).fileName.toString())
.also { it.directory(executable.resolveDirectory()) }
- .start().let {
+ .also { it.environment().putAll(environment) }
+ .also { it.inheritIO() }
+ .start().also {
it.waitFor(5, TimeUnit.SECONDS)
assert(it.exitValue() == 0) {
throw TestCaseFailedException(description, RuntimeException("non-zero return code: ${it.exitValue()}"))
diff --git a/tests/rules/defs.bzl b/tests/rules/defs.bzl
index 95be157..577dd52 100644
--- a/tests/rules/defs.bzl
+++ b/tests/rules/defs.bzl
@@ -16,7 +16,8 @@
_TEST_COMMON_DEPS=[
"//tests/rules:assertion_test_case",
"//third_party/jvm/com/google/truth",
- "//third_party/jvm/junit:junit"
+ "//third_party/jvm/junit:junit",
+ "@com_github_jetbrains_kotlin//:kotlin-test",
]
def kt_it_assertion_test(name, test_class, cases=None, data = [], deps=[]):
diff --git a/third_party/dependencies.yaml b/third_party/dependencies.yaml
index c7d68ea..a08d35b 100644
--- a/third_party/dependencies.yaml
+++ b/third_party/dependencies.yaml
@@ -45,6 +45,15 @@
modules: ["", "compiler", "producers"]
lang: "java"
version: "2.16"
+ org.jacoco:
+ org.jacoco.core:
+ lang: "java"
+ version: "0.7.5.201505241946"
+ exclude: ["org.ow2.asm:asm-debug-all"]
+ org.ow2.asm:
+ asm-commons:
+ lang: "java"
+ version: "6.0"
org.jetbrains.kotlinx:
kotlinx-coroutines:
modules: ["core"]
diff --git a/third_party/jvm/org/jacoco/BUILD b/third_party/jvm/org/jacoco/BUILD
new file mode 100644
index 0000000..be35a9f
--- /dev/null
+++ b/third_party/jvm/org/jacoco/BUILD
@@ -0,0 +1,12 @@
+licenses(["notice"])
+java_library(
+ name = "org_jacoco_core",
+ exports = [
+ "//external:jar/io_bazel_rules_kotlin_org/jacoco/org_jacoco_core"
+ ],
+ visibility = [
+ "//visibility:public"
+ ]
+)
+
+
diff --git a/third_party/jvm/org/ow2/asm/BUILD b/third_party/jvm/org/ow2/asm/BUILD
new file mode 100644
index 0000000..d3c8dde
--- /dev/null
+++ b/third_party/jvm/org/ow2/asm/BUILD
@@ -0,0 +1,42 @@
+licenses(["notice"])
+java_library(
+ name = "asm",
+ exports = [
+ "//external:jar/io_bazel_rules_kotlin_org/ow2/asm/asm"
+ ],
+ visibility = [
+ "//visibility:public"
+ ]
+)
+
+
+
+java_library(
+ name = "asm_commons",
+ exports = [
+ "//external:jar/io_bazel_rules_kotlin_org/ow2/asm/asm_commons"
+ ],
+ runtime_deps = [
+ ":asm_tree"
+ ],
+ visibility = [
+ "//visibility:public"
+ ]
+)
+
+
+
+java_library(
+ name = "asm_tree",
+ exports = [
+ "//external:jar/io_bazel_rules_kotlin_org/ow2/asm/asm_tree"
+ ],
+ runtime_deps = [
+ ":asm"
+ ],
+ visibility = [
+ "//visibility:public"
+ ]
+)
+
+
diff --git a/third_party/jvm/workspace.bzl b/third_party/jvm/workspace.bzl
index dac3946..10df4ca 100644
--- a/third_party/jvm/workspace.bzl
+++ b/third_party/jvm/workspace.bzl
@@ -51,10 +51,14 @@
{"artifact": "org.checkerframework:checker-compat-qual:2.3.0", "lang": "java", "sha1": "69cb4fea55a9d89b8827d107f17c985cc1a76052", "repository": "https://repo.maven.apache.org/maven2/", "name": "io_bazel_rules_kotlin_org_checkerframework_checker_compat_qual", "actual": "@io_bazel_rules_kotlin_org_checkerframework_checker_compat_qual//jar", "bind": "jar/io_bazel_rules_kotlin_org/checkerframework/checker_compat_qual"},
{"artifact": "org.codehaus.mojo:animal-sniffer-annotations:1.14", "lang": "java", "sha1": "775b7e22fb10026eed3f86e8dc556dfafe35f2d5", "repository": "https://repo.maven.apache.org/maven2/", "name": "io_bazel_rules_kotlin_org_codehaus_mojo_animal_sniffer_annotations", "actual": "@io_bazel_rules_kotlin_org_codehaus_mojo_animal_sniffer_annotations//jar", "bind": "jar/io_bazel_rules_kotlin_org/codehaus/mojo/animal_sniffer_annotations"},
{"artifact": "org.hamcrest:hamcrest-core:1.3", "lang": "java", "sha1": "42a25dc3219429f0e5d060061f71acb49bf010a0", "repository": "https://repo.maven.apache.org/maven2/", "name": "io_bazel_rules_kotlin_org_hamcrest_hamcrest_core", "actual": "@io_bazel_rules_kotlin_org_hamcrest_hamcrest_core//jar", "bind": "jar/io_bazel_rules_kotlin_org/hamcrest/hamcrest_core"},
+ {"artifact": "org.jacoco:org.jacoco.core:0.7.5.201505241946", "lang": "java", "sha1": "1ea906dc5201d2a1bc0604f8650534d4bcaf4c95", "repository": "https://repo.maven.apache.org/maven2/", "name": "io_bazel_rules_kotlin_org_jacoco_org_jacoco_core", "actual": "@io_bazel_rules_kotlin_org_jacoco_org_jacoco_core//jar", "bind": "jar/io_bazel_rules_kotlin_org/jacoco/org_jacoco_core"},
{"artifact": "org.jetbrains.kotlin:kotlin-stdlib-common:1.2.41", "lang": "java", "sha1": "bf0bdac1048fd1c5c54362978dd7e06bd2230e78", "repository": "https://repo.maven.apache.org/maven2/", "name": "io_bazel_rules_kotlin_org_jetbrains_kotlin_kotlin_stdlib_common", "actual": "@io_bazel_rules_kotlin_org_jetbrains_kotlin_kotlin_stdlib_common//jar", "bind": "jar/io_bazel_rules_kotlin_org/jetbrains/kotlin/kotlin_stdlib_common"},
{"artifact": "org.jetbrains.kotlinx:atomicfu-common:0.10.1", "lang": "java", "sha1": "4eb87291dff597f2f5bac4876fae02ef23466a39", "repository": "https://repo.maven.apache.org/maven2/", "name": "io_bazel_rules_kotlin_org_jetbrains_kotlinx_atomicfu_common", "actual": "@io_bazel_rules_kotlin_org_jetbrains_kotlinx_atomicfu_common//jar", "bind": "jar/io_bazel_rules_kotlin_org/jetbrains/kotlinx/atomicfu_common"},
{"artifact": "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:0.23.1", "lang": "java", "sha1": "ee988a3e0a918579315ce6654f415b47fec39d36", "repository": "https://repo.maven.apache.org/maven2/", "name": "io_bazel_rules_kotlin_org_jetbrains_kotlinx_kotlinx_coroutines_core_common", "actual": "@io_bazel_rules_kotlin_org_jetbrains_kotlinx_kotlinx_coroutines_core_common//jar", "bind": "jar/io_bazel_rules_kotlin_org/jetbrains/kotlinx/kotlinx_coroutines_core_common"},
{"artifact": "org.jetbrains.kotlinx:kotlinx-coroutines-core:0.23.1", "lang": "java", "sha1": "fb67b623766f0b2d56697f0b8ed14450f285b8ed", "repository": "https://repo.maven.apache.org/maven2/", "name": "io_bazel_rules_kotlin_org_jetbrains_kotlinx_kotlinx_coroutines_core", "actual": "@io_bazel_rules_kotlin_org_jetbrains_kotlinx_kotlinx_coroutines_core//jar", "bind": "jar/io_bazel_rules_kotlin_org/jetbrains/kotlinx/kotlinx_coroutines_core"},
+ {"artifact": "org.ow2.asm:asm-commons:6.0", "lang": "java", "sha1": "f256fd215d8dd5a4fa2ab3201bf653de266ed4ec", "repository": "https://repo.maven.apache.org/maven2/", "name": "io_bazel_rules_kotlin_org_ow2_asm_asm_commons", "actual": "@io_bazel_rules_kotlin_org_ow2_asm_asm_commons//jar", "bind": "jar/io_bazel_rules_kotlin_org/ow2/asm/asm_commons"},
+ {"artifact": "org.ow2.asm:asm-tree:6.0", "lang": "java", "sha1": "a624f1a6e4e428dcd680a01bab2d4c56b35b18f0", "repository": "https://repo.maven.apache.org/maven2/", "name": "io_bazel_rules_kotlin_org_ow2_asm_asm_tree", "actual": "@io_bazel_rules_kotlin_org_ow2_asm_asm_tree//jar", "bind": "jar/io_bazel_rules_kotlin_org/ow2/asm/asm_tree"},
+ {"artifact": "org.ow2.asm:asm:6.0", "lang": "java", "sha1": "bc6fa6b19424bb9592fe43bbc20178f92d403105", "repository": "https://repo.maven.apache.org/maven2/", "name": "io_bazel_rules_kotlin_org_ow2_asm_asm", "actual": "@io_bazel_rules_kotlin_org_ow2_asm_asm//jar", "bind": "jar/io_bazel_rules_kotlin_org/ow2/asm/asm"},
]
def maven_dependencies(callback = declare_maven):