# Copyright 2022 Google LLC. 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. """Kotlin kt_jvm_library rule tests.""" load("//kotlin:jvm_library.bzl", "kt_jvm_library") load("//tests/analysis:util.bzl", "ONLY_FOR_ANALYSIS_TEST_TAGS", "create_file", "get_action", "get_arg") load("@bazel_skylib//lib:sets.bzl", "sets") load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts") load(":assert_failure_test.bzl", "assert_failure_test") load("//:visibility.bzl", "RULES_KOTLIN") _DEFAULT_LIST = ["__default__"] def _test_impl(ctx): env = analysistest.begin(ctx) actions = analysistest.target_actions(env) actual = ctx.attr.target_under_test expected = ctx.attr.expected asserts.true( env, JavaInfo in actual, "kt_jvm_library did not produce JavaInfo provider.", ) asserts.true( env, ProguardSpecProvider in actual, "Expected a ProguardSpecProvider provider.", ) if "data" in expected: expected_data = expected["data"] actual_data = _extract_data_runfiles(actual) asserts.new_set_equals( env, sets.make(expected_data), sets.make(actual_data), """ FAIL: kt_jvm_library did not produce the expected data dependencies. EXPECTED: %s ACTUAL: %s """ % (expected_data, actual_data), ) expected_exports = [] for target in ctx.attr.expected_exports: asserts.equals( env, 1, len(target[JavaInfo].full_compile_jars.to_list()), "Not a single compile-time Jar: %s" % target.label, ) expected_exports.extend(target[JavaInfo].full_compile_jars.to_list()) actual_exports = actual[JavaInfo].full_compile_jars.to_list() # TODO: fail if there are *un*expected exports, maybe by making sure # that the actual exports are exactly the expected ones plus the Jar(s) # produced by this JavaInfo. for expected_export in expected_exports: asserts.true( env, expected_export in actual_exports, """ kt_jvm_library did not export %s actual: %s """ % (expected_export, actual_exports), ) asserts.equals( env, ctx.attr.expected_exported_processor_classes, actual[JavaInfo].plugins.processor_classes.to_list(), ) kt_2_java_compile = get_action(actions, "Kt2JavaCompile") if kt_2_java_compile: asserts.true( env, kt_2_java_compile.outputs.to_list()[0].basename.endswith(".jar"), "Expected first output to be a JAR (this affects the param file name).", ) if ctx.attr.expected_friend_jar_names != _DEFAULT_LIST: friend_paths_arg = get_arg(kt_2_java_compile, "-Xfriend-paths=") friend_jar_names = [p.rsplit("/", 1)[1] for p in friend_paths_arg.split(",")] if friend_paths_arg else [] asserts.set_equals(env, sets.make(ctx.attr.expected_friend_jar_names), sets.make(friend_jar_names)) asserts.equals( env, ctx.attr.expect_neverlink, len(actual[JavaInfo].transitive_runtime_jars.to_list()) == 0, "Mismatch: Expected transitive_runtime_jars iff (neverlink == False)", ) return analysistest.end(env) _test = analysistest.make( impl = _test_impl, attrs = dict( expected = attr.string_list_dict(), expected_exports = attr.label_list(), expected_exported_processor_classes = attr.string_list( doc = "Annotation processors reported as to be run on depending targets", ), expected_processor_classes = attr.string_list( doc = "Annotation processors reported as run on the given target", ), expected_friend_jar_names = attr.string_list( doc = "Names of all -Xfriend-paths= JARs", default = _DEFAULT_LIST, ), expect_processor_classpath = attr.bool(), expect_neverlink = attr.bool(), ), ) jvm_library_test = _test def _coverage_test_impl(ctx): env = analysistest.begin(ctx) target_under_test = analysistest.target_under_test(env) instrumented_files_info = target_under_test[InstrumentedFilesInfo] instrumented_files = instrumented_files_info.instrumented_files.to_list() asserts.equals( env, ctx.attr.expected_instrumented_file_basenames, [file.basename for file in instrumented_files], ) return analysistest.end(env) _coverage_test = analysistest.make( impl = _coverage_test_impl, attrs = { "expected_instrumented_file_basenames": attr.string_list(), }, config_settings = { "//command_line_option:collect_code_coverage": "1", "//command_line_option:instrument_test_targets": "1", "//command_line_option:instrumentation_filter": "+tests/analysis[:/]", }, ) def _extract_data_runfiles(target): return [f.basename for f in target[DefaultInfo].data_runfiles.files.to_list()] def _test_kt_jvm_library_with_proguard_specs(): test_name = "kt_jvm_library_with_proguard_specs_test" create_file( name = test_name + "/Salutations.kt", content = """ package test fun greeting(): String = "Hello World!" """, ) create_file( name = test_name + "/salutations.pgcfg", content = """ -keep class * { *** greeting(); } """, ) kt_jvm_library( name = test_name + "_tut", srcs = [ test_name + "/Salutations.kt", ], proguard_specs = [ test_name + "/salutations.pgcfg", ], ) _test( name = test_name, target_under_test = test_name + "_tut", ) return test_name def _test_kt_jvm_library_with_resources(): test_name = "kt_jvm_library_with_resources_test" create_file( name = test_name + "/Salutations.kt", content = """ package test fun greeting(): String = "Hello World!" """, ) create_file( name = test_name + "/salutations.txt", content = """ Hi! """, ) kt_jvm_library( name = test_name + "_tut", srcs = [ test_name + "/Salutations.kt", "testinputs/Foo.java", ], resources = [ test_name + "/salutations.txt", ], ) _test( name = test_name, target_under_test = test_name + "_tut", ) return test_name def _test_kt_jvm_library_with_plugin(): test_name = "kt_jvm_library_with_plugin_test" create_file( name = test_name + "/Salutations.kt", content = """ package test fun greeting(): String = "Hello World!" """, ) kt_jvm_library( name = test_name + "_tut", srcs = [ test_name + "/Salutations.kt", ], # Need a working plugin so it can run for the test. plugins = ["//bazel:auto_value_plugin"], ) _test( name = test_name, target_under_test = test_name + "_tut", expected_processor_classes = ["com.google.auto.value.processor.AutoValueProcessor"], expect_processor_classpath = True, ) return test_name def _test_kt_jvm_library_no_kt_srcs_with_plugin(): test_name = "kt_jvm_library_no_kt_srcs_with_plugin_test" native.java_plugin( name = "%s_plugin" % test_name, processor_class = test_name, srcs = ["testinputs/Foo.java"], # induce processor_classpath ) kt_jvm_library( name = test_name + "_tut", srcs = ["testinputs/Bar.java"], plugins = [":%s_plugin" % test_name], tags = ONLY_FOR_ANALYSIS_TEST_TAGS, ) _test( name = test_name, target_under_test = test_name + "_tut", expected_processor_classes = [test_name], expect_processor_classpath = True, ) return test_name def _test_kt_jvm_library_with_non_processor_plugin(): test_name = "kt_jvm_library_with_non_processor_plugin_test" create_file( name = test_name + "/Salutations.kt", content = """ package test fun greeting(): String = "Hello World!" """, ) native.java_plugin( # no processor_class name = "%s_plugin" % test_name, srcs = ["testinputs/Foo.java"], ) kt_jvm_library( name = test_name + "_tut", srcs = [ test_name + "/Salutations.kt", ], plugins = [":%s_plugin" % test_name], ) _test( name = test_name, target_under_test = test_name + "_tut", expected_processor_classes = [], # no processor class so no processing expect_processor_classpath = True, # expect java_plugin's Jar ) return test_name def _test_kt_jvm_library_with_exported_plugin(): test_name = "kt_jvm_library_with_exported_plugin_test" create_file( name = test_name + "/Salutations.kt", content = """ package test fun greeting(): String = "Hello World!" """, ) native.java_plugin( name = "%s_plugin" % test_name, processor_class = test_name, ) kt_jvm_library( name = test_name + "_tut", srcs = [ test_name + "/Salutations.kt", ], exported_plugins = [":%s_plugin" % test_name], ) _test( name = test_name, target_under_test = test_name + "_tut", expected_exported_processor_classes = [test_name], expected_processor_classes = [], # exported plugin should *not* run on _tut itself ) return test_name def _test_kt_jvm_library_dep_on_exported_plugin(): test_name = "kt_jvm_library_dep_on_exported_plugin_test" create_file( name = test_name + "/Salutations.kt", content = """ package test fun greeting(): String = "Hello World!" """, ) native.java_plugin( name = "%s_plugin" % test_name, processor_class = test_name, srcs = ["testinputs/Foo.java"], # induce processor_classpath ) kt_jvm_library( name = "%s_exports_plugin" % test_name, srcs = [test_name + "/Salutations.kt"], exported_plugins = [":%s_plugin" % test_name], ) kt_jvm_library( name = test_name + "_tut", srcs = [ test_name + "/Salutations.kt", ], deps = [":%s_exports_plugin" % test_name], tags = ONLY_FOR_ANALYSIS_TEST_TAGS, ) _test( name = test_name, target_under_test = test_name + "_tut", expected_processor_classes = [test_name], expect_processor_classpath = True, ) return test_name def _test_kt_jvm_library_java_dep_on_exported_plugin(): test_name = "kt_jvm_library_java_dep_on_exported_plugin_test" create_file( name = test_name + "/Salutations.kt", content = """ package test fun greeting(): String = "Hello World!" """, ) native.java_plugin( name = "%s_plugin" % test_name, processor_class = test_name, srcs = ["testinputs/Foo.java"], # induce processor_classpath ) native.java_library( name = "%s_exports_plugin" % test_name, exported_plugins = [":%s_plugin" % test_name], ) kt_jvm_library( name = test_name + "_tut", srcs = [ test_name + "/Salutations.kt", ], deps = [":%s_exports_plugin" % test_name], tags = ONLY_FOR_ANALYSIS_TEST_TAGS, ) _test( name = test_name, target_under_test = test_name + "_tut", expected_processor_classes = [test_name], expect_processor_classpath = True, ) return test_name def _test_kt_jvm_library_with_exports(): test_name = "kt_jvm_library_with_exports_test" create_file( name = test_name + "/Salutations.kt", content = """ package test fun greeting(): String = "Hello World!" """, ) kt_jvm_library( name = test_name + "_exp", srcs = [test_name + "/Salutations.kt"], ) native.java_library( name = test_name + "_javaexp", srcs = ["testinputs/Foo.java"], # need file here so we get a Jar ) kt_jvm_library( name = test_name + "_tut", srcs = [ test_name + "/Salutations.kt", ], exports = [ ":%s_exp" % test_name, ":%s_javaexp" % test_name, ], ) _test( name = test_name, target_under_test = test_name + "_tut", expected_exports = [ ":%s_exp" % test_name, ":%s_javaexp" % test_name, ], ) return test_name def _test_kt_jvm_library_with_export_that_exports_plugin(): test_name = "kt_jvm_library_with_export_that_exports_plugin_test" create_file( name = test_name + "/Salutations.kt", content = """ package test fun greeting(): String = "Hello World!" """, ) native.java_plugin( name = "%s_plugin" % test_name, processor_class = test_name, srcs = ["testinputs/Foo.java"], # induce processor_classpath ) kt_jvm_library( name = "%s_exports_plugin" % test_name, exported_plugins = [":%s_plugin" % test_name], srcs = [test_name + "/Salutations.kt"], ) kt_jvm_library( name = test_name + "_tut", srcs = [ test_name + "/Salutations.kt", ], exports = [":%s_exports_plugin" % test_name], ) _test( name = test_name, target_under_test = test_name + "_tut", expected_exports = [":%s_exports_plugin" % test_name], expected_exported_processor_classes = [test_name], ) return test_name def _test_kt_jvm_library_with_java_export_that_exports_plugin(): test_name = "kt_jvm_library_with_java_export_that_exports_plugin_test" create_file( name = test_name + "/Salutations.kt", content = """ package test fun greeting(): String = "Hello World!" """, ) native.java_plugin( name = "%s_plugin" % test_name, processor_class = test_name, srcs = ["testinputs/Foo.java"], # induce processor_classpath ) native.java_library( name = "%s_exports_plugin" % test_name, exported_plugins = [":%s_plugin" % test_name], ) kt_jvm_library( name = test_name + "_tut", srcs = [ test_name + "/Salutations.kt", ], exports = [":%s_exports_plugin" % test_name], ) _test( name = test_name, target_under_test = test_name + "_tut", expected_exports = [], # _exports_plugin has no compile/runtime Jars expected_exported_processor_classes = [test_name], ) return test_name def _test_forbidden_nano_dep(): test_name = "kt_jvm_library_forbidden_nano_test" kt_jvm_library( name = test_name + "_tut", srcs = [test_name + "/Ignored.kt"], deps = [test_name + "_fake_nano_proto_lib"], tags = [ "manual", "nobuilder", ], ) native.java_library( name = test_name + "_fake_nano_proto_lib", srcs = [], tags = ["nano_proto_library"], ) assert_failure_test( name = test_name, target_under_test = test_name + "_tut", msg_contains = test_name + "_fake_nano_proto_lib : nano_proto_library", ) return test_name def _test_forbidden_nano_export(): test_name = "kt_jvm_library_forbidden_nano_export_test" kt_jvm_library( name = test_name + "_tut", srcs = [test_name + "/Ignored.kt"], deps = [test_name + "_export"], tags = [ "manual", "nobuilder", ], ) native.java_library( name = test_name + "_export", exports = [test_name + "_fake_nano_proto_lib"], ) native.java_library( name = test_name + "_fake_nano_proto_lib", srcs = [], tags = ["nano_proto_library"], ) assert_failure_test( name = test_name, target_under_test = test_name + "_tut", msg_contains = test_name + "_fake_nano_proto_lib : nano_proto_library", ) return test_name def _test_kt_jvm_library_with_no_sources(): test_name = "kt_jvm_library_with_no_sources_test" kt_jvm_library( name = test_name + "_tut", tags = [ "manual", "nobuilder", ], ) tut_label = str(Label("//tests/analysis:kt_jvm_library_with_no_sources_test_tut")) assert_failure_test( name = test_name, target_under_test = test_name + "_tut", msg_contains = "One of {srcs, common_srcs, exports, exported_plugins} of target " + tut_label + " must be non empty", ) return test_name def _test_kt_jvm_library_coverage(): test_name = "kt_jvm_library_coverage" kt_jvm_library( name = test_name + "_tut", srcs = ["testinputs/Srcs.kt"], common_srcs = ["testinputs/CommonSrcs.kt"], deps = [":{}_deps".format(test_name)], runtime_deps = [":{}_runtime_deps".format(test_name)], data = [":{}_data".format(test_name)], resources = [":{}_resources".format(test_name)], testonly = True, ) native.java_library( name = test_name + "_deps", srcs = ["testinputs/Deps.java"], testonly = True, ) native.java_library( name = test_name + "_runtime_deps", srcs = ["testinputs/RuntimeDeps.java"], testonly = True, ) native.java_binary( name = test_name + "_data", main_class = "Data", srcs = ["testinputs/Data.java"], testonly = True, ) native.java_binary( name = test_name + "_resources", main_class = "Resources", srcs = ["testinputs/Resources.java"], testonly = True, ) _coverage_test( name = test_name, target_under_test = test_name + "_tut", expected_instrumented_file_basenames = [ "Data.java", "Deps.java", "Resources.java", "RuntimeDeps.java", "Srcs.kt", "CommonSrcs.kt", ], ) return test_name def test_suite(name): native.test_suite( name = name, tests = [ _test_forbidden_nano_dep(), _test_forbidden_nano_export(), _test_kt_jvm_library_dep_on_exported_plugin(), _test_kt_jvm_library_java_dep_on_exported_plugin(), _test_kt_jvm_library_no_kt_srcs_with_plugin(), _test_kt_jvm_library_with_export_that_exports_plugin(), _test_kt_jvm_library_with_exported_plugin(), _test_kt_jvm_library_with_exports(), _test_kt_jvm_library_with_java_export_that_exports_plugin(), _test_kt_jvm_library_with_no_sources(), _test_kt_jvm_library_with_non_processor_plugin(), _test_kt_jvm_library_with_plugin(), _test_kt_jvm_library_with_proguard_specs(), _test_kt_jvm_library_with_resources(), _test_kt_jvm_library_coverage(), ], )