unplugged-system/build/bazel/rules/abi/abi_dump_test.bzl

678 lines
20 KiB
Python

# Copyright (C) 2022 The Android Open Source Project
#
# 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.
load("@bazel_skylib//lib:paths.bzl", "paths")
load("@bazel_skylib//lib:sets.bzl", "sets")
load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts")
load("//build/bazel/rules/cc:cc_library_shared.bzl", "cc_library_shared")
load("//build/bazel/rules/cc:cc_library_static.bzl", "cc_library_static")
load("//build/bazel/rules/test_common:args.bzl", "get_arg_value", "get_arg_values")
load(":abi_dump.bzl", "abi_dump", "find_abi_config")
ABI_LINKER = "prebuilts/clang-tools/linux-x86/bin/header-abi-linker"
ABI_DIFF = "prebuilts/clang-tools/linux-x86/bin/header-abi-diff"
# cxa_demangle.cpp is added as part of the stl in cc_library_shared, so it's dump
# file is always created.
CXA_DEMANGLE = "external/libcxxabi/external/libcxxabi/src/libc++demangle.cxa_demangle.cpp.sdump"
REF_DUMPS_HOME = "build/bazel/rules/abi/abi-dumps"
ARCH = "x86_64"
BITNESS = 64
CONFIG_SETTING_COVERAGE = {
"//command_line_option:collect_code_coverage": True,
}
CONFIG_SETTING_SKIP_ABI_CHECK = {
"@//build/bazel/flags/cc/abi:skip_abi_checks": True,
}
CONFIG_SETTING_IN_APEX = {
"@//build/bazel/rules/apex:within_apex": True,
}
def _abi_linker_action_test_impl(ctx):
env = analysistest.begin(ctx)
bin_home = analysistest.target_bin_dir_path(env)
bazel_out_base = paths.join(bin_home, ctx.label.package)
actions = analysistest.target_actions(env)
link_actions = [a for a in actions if a.mnemonic == "AbiLink"]
asserts.true(
env,
len(link_actions) == 1,
"Abi link action not found: %s" % link_actions,
)
action = link_actions[0]
stripped_so = paths.join(bazel_out_base, "lib" + ctx.attr.lib_name + "_stripped.so")
symbol_file = paths.join(ctx.label.package, ctx.attr.symbol_file)
asserts.set_equals(
env,
expected = sets.make(
[paths.join(bazel_out_base, ctx.label.package, file + ".sdump") for file in ctx.attr.dumps] + [
ABI_LINKER,
paths.join(bin_home, CXA_DEMANGLE),
stripped_so,
symbol_file,
],
),
actual = sets.make([
file.path
for file in action.inputs.to_list()
]),
)
lsdump_file = paths.join(bazel_out_base, ctx.attr.lib_name + ".so.lsdump")
asserts.set_equals(
env,
expected = sets.make([lsdump_file]),
actual = sets.make([
file.path
for file in action.outputs.to_list()
]),
)
argv = action.argv
_test_arg_set_correctly(env, argv, "--root-dir", ".")
_test_arg_set_correctly(env, argv, "-o", lsdump_file)
_test_arg_set_correctly(env, argv, "-so", stripped_so)
_test_arg_set_correctly(env, argv, "-arch", ARCH)
_test_arg_set_correctly(env, argv, "-v", symbol_file)
_test_arg_set_multi_values_correctly(env, argv, "--exclude-symbol-version", ctx.attr.exclude_symbol_versions)
_test_arg_set_multi_values_correctly(env, argv, "--exclude-symbol-tag", ctx.attr.exclude_symbol_tags)
_test_arg_set_multi_values_correctly(
env,
argv,
"-I",
[paths.join(bazel_out_base, file) for file in ctx.attr.export_includes] +
[paths.join(ctx.label.package, file) for file in ctx.attr.export_includes] +
ctx.attr.export_absolute_includes +
[paths.join(bin_home, file) for file in ctx.attr.export_absolute_includes],
)
sdump_files = []
args = " ".join(argv).split(" ")
args_len = len(args)
# The .sdump files are at the end of the args, the abi linker binary is always at index 0.
for i in reversed(range(args_len)):
if ".sdump" in args[i]:
sdump_files.append(args[i])
else:
break
asserts.set_equals(
env,
expected = sets.make(
[paths.join(bazel_out_base, ctx.label.package, file + ".sdump") for file in ctx.attr.dumps] + [
paths.join(bin_home, CXA_DEMANGLE),
],
),
actual = sets.make(sdump_files),
)
return analysistest.end(env)
__abi_linker_action_test = analysistest.make(
impl = _abi_linker_action_test_impl,
attrs = {
"dumps": attr.string_list(),
"lib_name": attr.string(),
"symbol_file": attr.string(),
"exclude_symbol_versions": attr.string_list(),
"exclude_symbol_tags": attr.string_list(),
"export_includes": attr.string_list(),
"export_absolute_includes": attr.string_list(),
"_platform_utils": attr.label(default = Label("//build/bazel/platforms:platform_utils")),
},
)
def _abi_linker_action_test(**kwargs):
__abi_linker_action_test(
target_compatible_with = [
"//build/bazel/platforms/arch:x86_64",
"//build/bazel/platforms/os:android",
],
**kwargs
)
def _test_abi_linker_action():
name = "abi_linker_action"
static_dep_a = name + "_static_dep_a"
static_dep_b = name + "_static_dep_b"
test_name = name + "_test"
cc_library_static(
name = static_dep_a,
srcs = ["static_a.cpp"],
srcs_c = ["static_a.c"],
export_includes = ["export_includes_static_a"],
export_absolute_includes = ["export_absolute_includes_static_a"],
export_system_includes = ["export_system_includes_static_a"],
local_includes = ["local_includes_static_a"],
absolute_includes = ["absolute_includes_static_a"],
tags = ["manual"],
)
cc_library_static(
name = static_dep_b,
srcs = ["static_b.cpp"],
srcs_c = ["static_b.c"],
deps = [":" + static_dep_a],
export_includes = ["export_includes_static_b"],
export_absolute_includes = ["export_absolute_includes_static_b"],
export_system_includes = ["export_system_includes_static_b"],
local_includes = ["local_includes_static_b"],
absolute_includes = ["absolute_includes_static_b"],
tags = ["manual"],
)
symbol_file = "shared_a.map.txt"
exclude_symbol_versions = ["30", "31"]
exclude_symbol_tags = ["func_1", "func_2"]
cc_library_shared(
name = name,
srcs = ["shared.cpp"],
srcs_c = ["shared.c"],
deps = [":" + static_dep_b],
export_includes = ["export_includes_shared"],
export_absolute_includes = ["export_absolute_includes_shared"],
export_system_includes = ["export_system_includes_shared"],
local_includes = ["local_includes_shared"],
absolute_includes = ["absolute_includes_shared"],
stubs_symbol_file = name + ".map.txt",
abi_checker_symbol_file = symbol_file,
abi_checker_exclude_symbol_versions = exclude_symbol_versions,
abi_checker_exclude_symbol_tags = exclude_symbol_tags,
tags = ["manual"],
)
_abi_linker_action_test(
name = test_name,
target_under_test = name + "_abi_dump",
dumps = [
static_dep_a + ".static_a.cpp",
static_dep_b + ".static_b.cpp",
name + "__internal_root.shared.cpp",
static_dep_a + ".static_a.c",
static_dep_b + ".static_b.c",
name + "__internal_root.shared.c",
],
lib_name = name,
symbol_file = symbol_file,
exclude_symbol_versions = exclude_symbol_versions,
exclude_symbol_tags = exclude_symbol_tags,
export_includes = [
"export_includes_shared",
"export_includes_static_a",
"export_includes_static_b",
],
export_absolute_includes = [
"export_absolute_includes_shared",
"export_absolute_includes_static_a",
"export_absolute_includes_static_b",
],
)
return test_name
def _abi_linker_action_run_test_impl(ctx):
env = analysistest.begin(ctx)
actions = analysistest.target_actions(env)
link_actions = [a for a in actions if a.mnemonic == "AbiLink"]
asserts.true(
env,
len(link_actions) == 1,
"Abi link action not found: %s" % link_actions,
)
return analysistest.end(env)
__abi_linker_action_run_test = analysistest.make(
impl = _abi_linker_action_run_test_impl,
)
def _abi_linker_action_run_test(**kwargs):
__abi_linker_action_run_test(
target_compatible_with = [
"//build/bazel/platforms/arch:x86_64",
"//build/bazel/platforms/os:android",
],
**kwargs
)
def _test_abi_linker_action_run_for_enabled():
name = "abi_linker_action_run_for_enabled"
test_name = name + "_test"
cc_library_shared(
name = name,
abi_checker_enabled = True,
tags = ["manual"],
)
_abi_linker_action_run_test(
name = test_name,
target_under_test = name + "_abi_dump",
)
return test_name
def _abi_linker_action_not_run_test_impl(ctx):
env = analysistest.begin(ctx)
actions = analysistest.target_actions(env)
link_actions = [a for a in actions if a.mnemonic == "AbiLink"]
asserts.true(
env,
len(link_actions) == 0,
"Abi link action found: %s" % link_actions,
)
return analysistest.end(env)
__abi_linker_action_not_run_test = analysistest.make(
impl = _abi_linker_action_not_run_test_impl,
)
def _abi_linker_action_not_run_test(**kwargs):
__abi_linker_action_not_run_test(
target_compatible_with = [
"//build/bazel/platforms/arch:x86_64",
"//build/bazel/platforms/os:android",
],
**kwargs
)
__abi_linker_action_not_run_for_no_device_test = analysistest.make(
impl = _abi_linker_action_not_run_test_impl,
)
def _abi_linker_action_not_run_for_no_device_test(**kwargs):
__abi_linker_action_not_run_for_no_device_test(
target_compatible_with = [
"//build/bazel/platforms/arch:x86_64",
"//build/bazel/platforms/os:linux",
],
**kwargs
)
__abi_linker_action_not_run_for_coverage_test = analysistest.make(
impl = _abi_linker_action_not_run_test_impl,
config_settings = CONFIG_SETTING_COVERAGE,
)
def _abi_linker_action_not_run_for_coverage_test(**kwargs):
__abi_linker_action_not_run_for_coverage_test(
target_compatible_with = [
"//build/bazel/platforms/arch:x86_64",
"//build/bazel/platforms/os:android",
],
**kwargs
)
__abi_linker_action_not_run_if_skipped_test = analysistest.make(
impl = _abi_linker_action_not_run_test_impl,
config_settings = CONFIG_SETTING_SKIP_ABI_CHECK,
)
def _abi_linker_action_not_run_if_skipped_test(**kwargs):
__abi_linker_action_not_run_if_skipped_test(
target_compatible_with = [
"//build/bazel/platforms/arch:x86_64",
"//build/bazel/platforms/os:android",
],
**kwargs
)
__abi_linker_action_not_run_apex_no_stubs_test = analysistest.make(
impl = _abi_linker_action_not_run_test_impl,
config_settings = CONFIG_SETTING_IN_APEX,
)
def _abi_linker_action_not_run_apex_no_stubs_test(**kwargs):
__abi_linker_action_not_run_apex_no_stubs_test(
target_compatible_with = [
"//build/bazel/platforms/arch:x86_64",
"//build/bazel/platforms/os:android",
],
**kwargs
)
def _test_abi_linker_action_not_run_for_default():
name = "abi_linker_action_not_run_for_default"
test_name = name + "_test"
cc_library_shared(
name = name,
tags = ["manual"],
)
_abi_linker_action_not_run_test(
name = test_name,
target_under_test = name + "_abi_dump",
)
return test_name
def _test_abi_linker_action_not_run_for_disabled():
name = "abi_linker_action_not_run_for_disabled"
test_name = name + "_test"
cc_library_shared(
name = name,
stubs_symbol_file = name + ".map.txt",
abi_checker_enabled = False,
tags = ["manual"],
)
_abi_linker_action_not_run_test(
name = test_name,
target_under_test = name + "_abi_dump",
)
return test_name
def _test_abi_linker_action_not_run_for_no_device():
name = "abi_linker_action_not_run_for_no_device"
test_name = name + "_test"
cc_library_shared(
name = name,
abi_checker_enabled = True,
tags = ["manual"],
)
_abi_linker_action_not_run_for_no_device_test(
name = test_name,
target_under_test = name + "_abi_dump",
)
return test_name
def _test_abi_linker_action_not_run_if_skipped():
name = "abi_linker_action_not_run_if_skipped"
test_name = name + "_test"
cc_library_shared(
name = name,
abi_checker_enabled = True,
tags = ["manual"],
)
_abi_linker_action_not_run_if_skipped_test(
name = test_name,
target_under_test = name + "_abi_dump",
)
return test_name
def _test_abi_linker_action_not_run_for_coverage_enabled():
name = "abi_linker_action_not_run_for_coverage_enabled"
test_name = name + "_test"
cc_library_shared(
name = name,
abi_checker_enabled = True,
features = ["coverage"],
# Coverage will add an extra lib to all the shared libs, we try to avoid
# that by clearing the system_dynamic_deps and stl.
system_dynamic_deps = [],
stl = "none",
tags = ["manual"],
)
_abi_linker_action_not_run_for_coverage_test(
name = test_name,
target_under_test = name + "_abi_dump",
)
return test_name
def _test_abi_linker_action_not_run_for_apex_no_stubs():
name = "abi_linker_action_not_run_for_apex_no_stubs"
test_name = name + "_test"
cc_library_shared(
name = name,
abi_checker_enabled = True,
tags = ["manual"],
)
_abi_linker_action_not_run_apex_no_stubs_test(
name = test_name,
target_under_test = name + "_abi_dump",
)
return test_name
def _abi_diff_action_test_impl(ctx):
env = analysistest.begin(ctx)
actions = analysistest.target_actions(env)
diff_actions = [a for a in actions if a.mnemonic == "AbiDiff"]
asserts.true(
env,
len(diff_actions) == 2,
"There should be two abi diff actions: %s" % diff_actions,
)
prev_version, version = find_abi_config(ctx)
_verify_abi_diff_action(ctx, env, diff_actions[0], prev_version, True)
_verify_abi_diff_action(ctx, env, diff_actions[1], version, False)
return analysistest.end(env)
def _verify_abi_diff_action(ctx, env, action, version, is_prev_version):
bin_home = analysistest.target_bin_dir_path(env)
bazel_out_base = paths.join(bin_home, ctx.label.package)
lsdump_file = paths.join(bazel_out_base, ctx.attr.lib_name + ".so.lsdump")
ref_dump = paths.join(
REF_DUMPS_HOME,
"platform",
str(version),
str(BITNESS),
ARCH,
"source-based",
ctx.attr.lib_name + ".so.lsdump",
)
asserts.set_equals(
env,
expected = sets.make([
lsdump_file,
ABI_DIFF,
ref_dump,
]),
actual = sets.make([
file.path
for file in action.inputs.to_list()
]),
)
if is_prev_version:
diff_file = paths.join(bazel_out_base, ".".join([ctx.attr.lib_name, "so", str(version), "abidiff"]))
else:
diff_file = paths.join(bazel_out_base, ".".join([ctx.attr.lib_name, "so", "abidiff"]))
asserts.set_equals(
env,
expected = sets.make([diff_file]),
actual = sets.make([
file.path
for file in action.outputs.to_list()
]),
)
argv = action.argv
_test_arg_set_correctly(env, argv, "-o", diff_file)
_test_arg_set_correctly(env, argv, "-old", ref_dump)
_test_arg_set_correctly(env, argv, "-new", lsdump_file)
_test_arg_set_correctly(env, argv, "-lib", ctx.attr.lib_name)
_test_arg_set_correctly(env, argv, "-arch", ARCH)
_test_arg_exists(env, argv, "-allow-unreferenced-changes")
_test_arg_exists(env, argv, "-allow-unreferenced-elf-symbol-changes")
_test_arg_exists(env, argv, "-allow-extensions")
if is_prev_version:
_test_arg_set_correctly(env, argv, "-target-version", str(version + 1))
else:
_test_arg_set_correctly(env, argv, "-target-version", "current")
__abi_diff_action_test = analysistest.make(
impl = _abi_diff_action_test_impl,
attrs = {
"lib_name": attr.string(),
"_platform_utils": attr.label(default = Label("//build/bazel/platforms:platform_utils")),
},
)
def _abi_diff_action_test(**kwargs):
__abi_diff_action_test(
target_compatible_with = [
"//build/bazel/platforms/arch:x86_64",
"//build/bazel/platforms/os:android",
],
**kwargs
)
def _test_abi_diff_action():
name = "abi_diff_action"
test_name = name + "_test"
cc_library_shared(
name = name,
srcs = ["shared.cpp"],
tags = ["manual"],
)
lib_name = "lib" + name
abi_dump_name = name + "_abi_dump_new"
abi_dump(
name = abi_dump_name,
shared = name + "_stripped",
root = name + "__internal_root",
soname = lib_name + ".so",
enabled = True,
abi_ref_dumps_platform = "//build/bazel/rules/abi/abi-dumps/platform:bp2build_all_srcs",
ref_dumps_home = "build/bazel/rules/abi/abi-dumps",
tags = ["manual"],
)
_abi_diff_action_test(
name = test_name,
target_under_test = abi_dump_name,
lib_name = lib_name,
)
return test_name
def _abi_diff_action_not_run_test_impl(ctx):
env = analysistest.begin(ctx)
actions = analysistest.target_actions(env)
diff_actions = [a for a in actions if a.mnemonic == "AbiDiff"]
asserts.true(
env,
len(diff_actions) == 0,
"Abi diff action found: %s" % diff_actions,
)
return analysistest.end(env)
__abi_diff_action_not_run_test = analysistest.make(
impl = _abi_diff_action_not_run_test_impl,
)
def _abi_diff_action_not_run_test(**kwargs):
__abi_diff_action_not_run_test(
target_compatible_with = [
"//build/bazel/platforms/arch:x86_64",
"//build/bazel/platforms/os:android",
],
**kwargs
)
def _test_abi_diff_action_not_run_if_no_ref_dump_found():
name = "abi_diff_action_not_run_if_no_ref_dump_found"
test_name = name + "_test"
cc_library_shared(
name = name,
srcs = ["shared.cpp"],
tags = ["manual"],
)
lib_name = "lib" + name
abi_dump_name = name + "_abi_dump_new"
abi_dump(
name = abi_dump_name,
shared = name + "_stripped",
root = name + "__internal_root",
soname = lib_name + ".so",
enabled = True,
ref_dumps_home = "build/bazel/rules/abi/abi-dumps",
tags = ["manual"],
)
_abi_diff_action_not_run_test(
name = test_name,
target_under_test = abi_dump_name,
)
return test_name
def _test_arg_set_correctly(env, argv, arg_name, expected):
arg = get_arg_value(argv, arg_name)
asserts.true(
env,
arg == expected,
"%s is not set correctly: expected %s, actual %s" % (arg_name, expected, arg),
)
def _test_arg_set_multi_values_correctly(env, argv, arg_name, expected):
args = get_arg_values(argv, arg_name)
asserts.set_equals(
env,
expected = sets.make(expected),
actual = sets.make(args),
)
def _test_arg_exists(env, argv, arg_name):
asserts.true(
env,
arg_name in argv,
"arg %s is not found" % arg_name,
)
def abi_dump_test_suite(name):
native.test_suite(
name = name,
tests = [
_test_abi_linker_action(),
_test_abi_linker_action_not_run_for_default(),
_test_abi_linker_action_not_run_for_disabled(),
_test_abi_linker_action_run_for_enabled(),
_test_abi_linker_action_not_run_for_no_device(),
_test_abi_linker_action_not_run_for_coverage_enabled(),
_test_abi_linker_action_not_run_if_skipped(),
_test_abi_linker_action_not_run_for_apex_no_stubs(),
_test_abi_diff_action(),
_test_abi_diff_action_not_run_if_no_ref_dump_found(),
],
)