Skip to content

Commit

Permalink
Initial support for code coverage. (#505)
Browse files Browse the repository at this point in the history
* `kt_jvm_test` supports coverage collection.
* `kt_jvm_library` propagates transitive coverage information.

This provides coverage support of Java code in `java_library` + `android_library` targets through `kt_jvm_test.`

It also provides coverage support of Java code in `kt_jvm_library` when experimental_use_abi_jars is enabled.

TODO: Instrument Kotlin code for coverage.
  • Loading branch information
jongerrish authored Mar 18, 2021
1 parent fde5804 commit 0a2440b
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 4 deletions.
15 changes: 15 additions & 0 deletions kotlin/internal/jvm/compile.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -617,8 +617,17 @@ def kt_jvm_produce_jar_actions(ctx, rule_kind):
exports = [_java_info(d) for d in getattr(ctx.attr, "exports", [])],
neverlink = getattr(ctx.attr, "neverlink", False),
)

instrumented_files = coverage_common.instrumented_files_info(
ctx,
source_attributes = ["srcs"],
dependency_attributes = ["deps", "exports", "associates", "friends"],
extensions = ["kt", "java"],
)

return struct(
java = java_info,
instrumented_files = instrumented_files,
kt = _KtJvmInfo(
srcs = ctx.files.srcs,
module_name = associates.module_name,
Expand Down Expand Up @@ -891,4 +900,10 @@ def export_only_providers(ctx, actions, attr, outputs):
getattr(attr, "exports", []),
),
),
instrumented_files = coverage_common.instrumented_files_info(
ctx,
source_attributes = ["srcs"],
dependency_attributes = ["deps", "exports", "associates", "friends"],
extensions = ["kt", "java"],
)
)
71 changes: 68 additions & 3 deletions kotlin/internal/jvm/impl.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ load(
"//kotlin/internal:defs.bzl",
_KtCompilerPluginInfo = "KtCompilerPluginInfo",
_KtJvmInfo = "KtJvmInfo",
_TOOLCHAIN_TYPE = "TOOLCHAIN_TYPE",
)
load(
"//kotlin/internal/utils:utils.bzl",
Expand All @@ -34,6 +35,7 @@ def _make_providers(ctx, providers, transitive_files = depset(order = "default")
providers = [
providers.java,
providers.kt,
providers.instrumented_files,
DefaultInfo(
files = depset([ctx.outputs.jar, ctx.outputs.jdeps]),
runfiles = ctx.runfiles(
Expand All @@ -56,11 +58,69 @@ def _write_launcher_action(ctx, rjars, main_class, jvm_flags, args = "", wrapper
jvm_flags: The flags that should be passed to the jvm.
args: Args that should be passed to the Binary.
"""
classpath = ":".join(["${RUNPATH}%s" % (j.short_path) for j in rjars.to_list()])
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]
java_bin_path = ctx.attr._java_runtime[java_common.JavaRuntimeInfo].java_executable_exec_path

if ctx.configuration.coverage_enabled:
jacocorunner = ctx.toolchains[_TOOLCHAIN_TYPE].jacocorunner
classpath = ctx.configuration.host_path_separator.join(
["${RUNPATH}%s" % (j.short_path) for j in rjars.to_list() + jacocorunner.files.to_list()],
)
jacoco_metadata_file = ctx.actions.declare_file(
"%s.jacoco_metadata.txt" % ctx.attr.name,
sibling = ctx.outputs.executable,
)
ctx.actions.write(jacoco_metadata_file, "\n".join([
jar.short_path.replace("../", "external/")
for jar in rjars.to_list()
]))
ctx.actions.expand_template(
template = template,
output = ctx.outputs.executable,
substitutions = {
"%classpath%": classpath,
"%runfiles_manifest_only%": "",
"%java_start_class%": "com.google.testing.coverage.JacocoCoverageRunner",
"%javabin%": "JAVABIN=" + java_bin_path,
"%jvm_flags%": jvm_flags,
"%set_jacoco_metadata%": "export JACOCO_METADATA_JAR=\"$JAVA_RUNFILES/{}/{}\"".format(ctx.workspace_name, jacoco_metadata_file.short_path),
"%set_jacoco_main_class%": """export JACOCO_MAIN_CLASS={}""".format(main_class),
"%set_jacoco_java_runfiles_root%": """export JACOCO_JAVA_RUNFILES_ROOT=$JAVA_RUNFILES/{}/""".format(ctx.workspace_name),
"%set_java_coverage_new_implementation%": """export JAVA_COVERAGE_NEW_IMPLEMENTATION=YES""",
"%workspace_prefix%": ctx.workspace_name + "/",
"%test_runtime_classpath_file%": "export TEST_RUNTIME_CLASSPATH_FILE=${JAVA_RUNFILES}",
"%needs_runfiles%": "0" if paths.is_absolute(java_bin_path) else "1"
},
is_executable = True,
)
return [jacoco_metadata_file]

else:
classpath = ctx.configuration.host_path_separator.join(
["${RUNPATH}%s" % (j.short_path) for j in rjars.to_list()],
)
ctx.actions.expand_template(
template = template,
output = ctx.outputs.executable,
substitutions = {
"%classpath%": classpath,
"%runfiles_manifest_only%": "",
"%java_start_class%": main_class,
"%javabin%": "JAVABIN=" + java_bin_path,
"%jvm_flags%": jvm_flags,
"%set_jacoco_metadata%": "",
"%set_jacoco_main_class%": "",
"%set_jacoco_java_runfiles_root%": "",
"%set_java_coverage_new_implementation%": """export JAVA_COVERAGE_NEW_IMPLEMENTATION=NO""",
"%workspace_prefix%": ctx.workspace_name + "/",
"%test_runtime_classpath_file%": "export TEST_RUNTIME_CLASSPATH_FILE=${JAVA_RUNFILES}",
"%needs_runfiles%": "0" if paths.is_absolute(java_bin_path) else "1"
},
is_executable = True,
)
return []

ctx.actions.expand_template(
template = template,
output = ctx.outputs.executable,
Expand Down Expand Up @@ -211,6 +271,11 @@ def kt_jvm_junit_test_impl(ctx):
providers = _kt_jvm_produce_jar_actions(ctx, "kt_jvm_test")
runtime_jars = depset(ctx.files._bazel_test_runner, transitive = [providers.java.transitive_runtime_jars])

coverage_runfiles = []
if ctx.configuration.coverage_enabled:
jacocorunner = ctx.toolchains[_TOOLCHAIN_TYPE].jacocorunner
coverage_runfiles = jacocorunner.files.to_list()

test_class = ctx.attr.test_class

# If no test_class, do a best-effort attempt to infer one.
Expand All @@ -223,7 +288,7 @@ def kt_jvm_junit_test_impl(ctx):
test_class = elements[1].split(".")[0].replace("/", ".")
break

_write_launcher_action(
coverage_metadata = _write_launcher_action(
ctx,
runtime_jars,
main_class = ctx.attr.main_class,
Expand All @@ -238,7 +303,7 @@ def kt_jvm_junit_test_impl(ctx):
providers,
depset(
order = "default",
transitive = [runtime_jars],
transitive = [runtime_jars, depset(coverage_runfiles), depset(coverage_metadata)],
direct = ctx.files._java_runtime,
),
# adds common test variables, including TEST_WORKSPACE.
Expand Down
3 changes: 3 additions & 0 deletions kotlin/internal/jvm/jvm.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,9 @@ kt_jvm_test = rule(
default = "",
),
"main_class": attr.string(default = "com.google.testing.junit.runner.BazelTestRunner"),
"_lcov_merger": attr.label(
default = Label("@bazel_tools//tools/test/CoverageOutputGenerator/java/com/google/devtools/coverageoutputgenerator:Main"),
),
}),
executable = True,
outputs = _common_outputs,
Expand Down
8 changes: 7 additions & 1 deletion kotlin/internal/toolchains.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ def _kotlin_toolchain_impl(ctx):
kotlinc_options = ctx.attr.kotlinc_options[KotlincOptions] if ctx.attr.kotlinc_options else None,
empty_jar = ctx.file._empty_jar,
empty_jdeps = ctx.file._empty_jdeps,
jacocorunner = ctx.attr.jacocorunner
)

return [
Expand Down Expand Up @@ -248,6 +249,9 @@ _kt_toolchain = rule(
cfg = "target",
default = Label("//third_party:empty.jdeps"),
),
"jacocorunner": attr.label(
default = Label("@bazel_tools//tools/jdk:JacocoCoverage"),
),
},
implementation = _kotlin_toolchain_impl,
provides = [platform_common.ToolchainInfo],
Expand All @@ -268,7 +272,8 @@ def define_kt_toolchain(
experimental_reduce_classpath_mode = None,
experimental_multiplex_workers = None,
javac_options = None,
kotlinc_options = None):
kotlinc_options = None,
jacocorunner = None):
"""Define the Kotlin toolchain."""
impl_name = name + "_impl"

Expand Down Expand Up @@ -298,6 +303,7 @@ def define_kt_toolchain(
javac_options = javac_options,
kotlinc_options = kotlinc_options,
visibility = ["//visibility:public"],
jacocorunner = jacocorunner,
)
native.toolchain(
name = name,
Expand Down

0 comments on commit 0a2440b

Please sign in to comment.