blob: c7ae00834aec4f7fade7ae11d2168f7bb22468b3 [file] [log] [blame]
#!/usr/bin/env python3
import os
import shutil
import stat
import subprocess
import sys
import tempfile
import unittest
import_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
import_path = os.path.abspath(os.path.join(import_path, 'utils'))
sys.path.insert(1, import_path)
from utils import (run_abi_diff, run_and_read_abi_diff, run_header_abi_dumper,
run_header_abi_linker)
from module import Module
SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__))
INPUT_DIR = os.path.join(SCRIPT_DIR, 'input')
EXPECTED_DIR = os.path.join(SCRIPT_DIR, 'expected')
EXPORTED_HEADER_DIRS = (INPUT_DIR,)
REF_DUMP_DIR = os.path.join(SCRIPT_DIR, 'reference_dumps')
def make_and_copy_dump(module, dump_dir):
dump_dir = os.path.join(dump_dir, module.arch)
os.makedirs(dump_dir, exist_ok=True)
dump_path = os.path.join(dump_dir, module.get_dump_name())
module.make_dump(dump_path)
return dump_path
def _read_output_content(dump_path):
with open(dump_path, 'r') as f:
return f.read()
class HeaderCheckerTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.maxDiff = None
def setUp(self):
self.tmp_dir = None
def tearDown(self):
if self.tmp_dir:
self.tmp_dir.cleanup()
self.tmp_dir = None
def get_tmp_dir(self):
if not self.tmp_dir:
self.tmp_dir = tempfile.TemporaryDirectory()
return self.tmp_dir.name
def run_and_compare(self, input_path, expected_path, cflags=[]):
with open(expected_path, 'r') as f:
expected_output = f.read()
with tempfile.NamedTemporaryFile(dir=self.get_tmp_dir(),
delete=False) as f:
output_path = f.name
run_header_abi_dumper(input_path, output_path, cflags,
EXPORTED_HEADER_DIRS)
actual_output = _read_output_content(output_path)
self.assertEqual(actual_output, expected_output)
def run_and_compare_name(self, name, cflags=[]):
input_path = os.path.join(INPUT_DIR, name)
expected_path = os.path.join(EXPECTED_DIR, name)
self.run_and_compare(input_path, expected_path, cflags)
def run_and_compare_name_cpp(self, name, cflags=[]):
self.run_and_compare_name(name, cflags + ['-x', 'c++', '-std=c++11'])
def run_and_compare_name_c_cpp(self, name, cflags=[]):
self.run_and_compare_name(name, cflags)
self.run_and_compare_name_cpp(name, cflags)
def run_and_compare_abi_diff(self, old_dump, new_dump, lib, arch,
expected_return_code, flags=[]):
return_code, output = run_and_read_abi_diff(
old_dump, new_dump, arch, lib, flags)
self.assertEqual(return_code, expected_return_code)
return output
def prepare_and_run_abi_diff(self, old_ref_dump_path, new_ref_dump_path,
target_arch, expected_return_code, flags=[]):
return self.run_and_compare_abi_diff(
old_ref_dump_path, new_ref_dump_path, 'test', target_arch,
expected_return_code, flags)
def get_or_create_dump(self, module, create):
if create:
return make_and_copy_dump(module, self.get_tmp_dir())
self.assertTrue(module.has_reference_dump,
f'Module {module.name} is not configured to generate '
f'reference dump.')
return os.path.join(REF_DUMP_DIR, module.arch, module.get_dump_name())
def prepare_and_run_abi_diff_all_archs(self, old_lib, new_lib,
expected_return_code, flags=[],
create_old=False, create_new=True):
old_modules = Module.get_test_modules_by_name(old_lib)
new_modules = Module.get_test_modules_by_name(new_lib)
self.assertEqual(len(old_modules), len(new_modules))
self.assertTrue(len(old_modules))
for old_module, new_module in zip(old_modules, new_modules):
self.assertEqual(old_module.arch, new_module.arch)
old_dump_path = self.get_or_create_dump(old_module, create_old)
new_dump_path = self.get_or_create_dump(new_module, create_new)
output = self.prepare_and_run_abi_diff(
old_dump_path, new_dump_path, new_module.arch,
expected_return_code, flags)
# Since most test cases are independent of architecture, verifying one
# of the reports is sufficient.
return output
def prepare_and_absolute_diff_all_archs(self, old_lib, new_lib):
old_modules = Module.get_test_modules_by_name(old_lib)
new_modules = Module.get_test_modules_by_name(new_lib)
self.assertEqual(len(old_modules), len(new_modules))
for old_module, new_module in zip(old_modules, new_modules):
self.assertEqual(old_module.arch, new_module.arch)
old_dump_path = self.get_or_create_dump(old_module, False)
new_dump_path = self.get_or_create_dump(new_module, True)
self.assertEqual(_read_output_content(old_dump_path),
_read_output_content(new_dump_path))
def test_example1_cpp(self):
self.run_and_compare_name_cpp('example1.cpp')
def test_example1_h(self):
self.run_and_compare_name_cpp('example1.h')
def test_example2_h(self):
self.run_and_compare_name_cpp('example2.h')
def test_example3_h(self):
self.run_and_compare_name_cpp('example3.h')
def test_libc_and_cpp(self):
self.prepare_and_run_abi_diff_all_archs(
"libc_and_cpp", "libc_and_cpp", 0)
def test_libc_and_cpp_and_libc_and_cpp_with_unused_struct(self):
self.prepare_and_run_abi_diff_all_archs(
"libc_and_cpp", "libc_and_cpp_with_unused_struct", 0)
def test_libc_and_cpp_and_libc_and_cpp_with_unused_struct_allow(self):
self.prepare_and_run_abi_diff_all_archs(
"libc_and_cpp", "libc_and_cpp_with_unused_struct", 0,
["-allow-unreferenced-changes"])
def test_libc_and_cpp_and_libc_and_cpp_with_unused_struct_check_all(self):
self.prepare_and_run_abi_diff_all_archs(
"libc_and_cpp", "libc_and_cpp_with_unused_struct", 1,
["-check-all-apis"])
self.prepare_and_run_abi_diff_all_archs(
"libc_and_cpp", "libc_and_cpp_with_unused_struct", 0,
["-check-all-apis",
"-ignore-linker-set-key", "_ZTI12UnusedStruct"])
def test_libc_and_cpp_with_unused_struct_and_libc_and_cpp_with_unused_cstruct(
self):
self.prepare_and_run_abi_diff_all_archs(
"libc_and_cpp_with_unused_struct",
"libc_and_cpp_with_unused_cstruct", 0,
['-check-all-apis', '-allow-unreferenced-changes'])
def test_libc_and_cpp_and_libc_and_cpp_with_unused_struct_check_all_advice(
self):
self.prepare_and_run_abi_diff_all_archs(
"libc_and_cpp", "libc_and_cpp_with_unused_struct", 0,
['-check-all-apis', '-advice-only'])
def test_libc_and_cpp_opaque_pointer_diff(self):
self.prepare_and_run_abi_diff_all_archs(
"libc_and_cpp_with_opaque_ptr_a",
"libc_and_cpp_with_opaque_ptr_b", 8,
['-consider-opaque-types-different'], True, True)
def test_libgolden_cpp_return_type_diff(self):
self.prepare_and_run_abi_diff_all_archs(
"libgolden_cpp", "libgolden_cpp_return_type_diff", 8)
self.prepare_and_run_abi_diff_all_archs(
"libgolden_cpp", "libgolden_cpp_return_type_diff", 0,
["-ignore-linker-set-key", "_ZN17HighVolumeSpeaker6ListenEv",
"-ignore-linker-set-key", "_ZN16LowVolumeSpeaker6ListenEv"])
def test_libgolden_cpp_add_odr(self):
self.prepare_and_run_abi_diff_all_archs(
"libgolden_cpp", "libgolden_cpp_odr", 0,
['-check-all-apis', '-allow-unreferenced-changes'])
def test_libgolden_cpp_add_function(self):
self.prepare_and_run_abi_diff_all_archs(
"libgolden_cpp", "libgolden_cpp_add_function", 6)
def test_libgolden_cpp_add_function_allow_extension(self):
self.prepare_and_run_abi_diff_all_archs(
"libgolden_cpp", "libgolden_cpp_add_function", 0,
['-allow-extensions'])
def test_libgolden_cpp_add_function_and_elf_symbol(self):
self.prepare_and_run_abi_diff_all_archs(
"libgolden_cpp", "libgolden_cpp_add_function_and_unexported_elf",
6)
def test_libgolden_cpp_fabricated_function_ast_removed_diff(self):
self.prepare_and_run_abi_diff_all_archs(
"libgolden_cpp_add_function_sybmol_only",
"libgolden_cpp_add_function", 4, [], False, False)
self.prepare_and_run_abi_diff_all_archs(
"libgolden_cpp_add_function_sybmol_only",
"libgolden_cpp_add_function", 0,
["-allow-adding-removing-referenced-apis"], False, False)
def test_libgolden_cpp_change_function_access(self):
self.prepare_and_run_abi_diff_all_archs(
"libgolden_cpp", "libgolden_cpp_change_function_access", 8)
def test_libgolden_cpp_add_global_variable(self):
self.prepare_and_run_abi_diff_all_archs(
"libgolden_cpp", "libgolden_cpp_add_global_variable", 6)
def test_libgolden_cpp_change_global_var_access(self):
self.prepare_and_run_abi_diff_all_archs(
"libgolden_cpp_add_global_variable",
"libgolden_cpp_add_global_variable_private", 8)
def test_libgolden_cpp_parameter_type_diff(self):
self.prepare_and_run_abi_diff_all_archs(
"libgolden_cpp", "libgolden_cpp_parameter_type_diff", 8)
def test_libgolden_cpp_with_vtable_diff(self):
self.prepare_and_run_abi_diff_all_archs(
"libgolden_cpp", "libgolden_cpp_vtable_diff", 8)
def test_libgolden_cpp_member_diff_advice_only(self):
self.prepare_and_run_abi_diff_all_archs(
"libgolden_cpp", "libgolden_cpp_member_diff", 0, ['-advice-only'])
def test_libgolden_cpp_member_diff(self):
self.prepare_and_run_abi_diff_all_archs(
"libgolden_cpp", "libgolden_cpp_member_diff", 8)
self.prepare_and_run_abi_diff_all_archs(
"libgolden_cpp", "libgolden_cpp_member_diff", 0,
["-ignore-linker-set-key", "_ZTI16LowVolumeSpeaker"])
def test_libgolden_cpp_change_member_access(self):
self.prepare_and_run_abi_diff_all_archs(
"libgolden_cpp", "libgolden_cpp_change_member_access", 8)
def test_libgolden_cpp_enum_extended(self):
self.prepare_and_run_abi_diff_all_archs(
"libgolden_cpp", "libgolden_cpp_enum_extended", 4)
def test_libgolden_cpp_enum_diff(self):
self.prepare_and_run_abi_diff_all_archs(
"libgolden_cpp", "libgolden_cpp_enum_diff", 8)
self.prepare_and_run_abi_diff_all_archs(
"libgolden_cpp", "libgolden_cpp_enum_diff", 0,
["-ignore-linker-set-key", "_ZTIN12SuperSpeaker6VolumeE"])
def test_libgolden_cpp_member_fake_diff(self):
self.prepare_and_run_abi_diff_all_archs(
"libgolden_cpp", "libgolden_cpp_member_fake_diff", 0)
def test_libgolden_cpp_member_integral_type_diff(self):
self.prepare_and_run_abi_diff_all_archs(
"libgolden_cpp", "libgolden_cpp_member_integral_type_diff", 8)
def test_libgolden_cpp_member_cv_diff(self):
self.prepare_and_run_abi_diff_all_archs(
"libgolden_cpp", "libgolden_cpp_member_cv_diff", 8)
def test_libgolden_cpp_unreferenced_elf_symbol_removed(self):
self.prepare_and_run_abi_diff_all_archs(
"libgolden_cpp", "libgolden_cpp_unreferenced_elf_symbol_removed",
16)
def test_libgolden_cpp_unreferenced_elf_symbol_added(self):
self.prepare_and_run_abi_diff_all_archs(
"libgolden_cpp_unreferenced_elf_symbol_removed", "libgolden_cpp",
2, create_old=True, create_new=False)
def test_libreproducability(self):
self.prepare_and_absolute_diff_all_archs(
"libreproducability", "libreproducability")
def test_libgolden_cpp_member_name_changed(self):
self.prepare_and_run_abi_diff_all_archs(
"libgolden_cpp", "libgolden_cpp_member_name_changed", 0)
def test_libgolden_cpp_member_function_pointer_changed(self):
self.prepare_and_run_abi_diff_all_archs(
"libgolden_cpp_function_pointer",
"libgolden_cpp_function_pointer_parameter_added", 8, [],
True, True)
def test_libgolden_cpp_internal_struct_access_upgraded(self):
self.prepare_and_run_abi_diff_all_archs(
"libgolden_cpp_internal_private_struct",
"libgolden_cpp_internal_public_struct", 4, [], True, True)
def test_libgolden_cpp_internal_struct_access_downgraded(self):
self.prepare_and_run_abi_diff_all_archs(
"libgolden_cpp_internal_public_struct",
"libgolden_cpp_internal_private_struct", 8, [], True, True)
def test_libgolden_cpp_inheritance_type_changed(self):
self.prepare_and_run_abi_diff_all_archs(
"libgolden_cpp", "libgolden_cpp_inheritance_type_changed", 8, [],
True, True)
def test_libpure_virtual_function(self):
self.prepare_and_absolute_diff_all_archs(
"libpure_virtual_function", "libpure_virtual_function")
def test_libc_and_cpp_in_json(self):
self.prepare_and_absolute_diff_all_archs(
"libgolden_cpp_json", "libgolden_cpp_json")
def test_libc_and_cpp_in_protobuf_and_json(self):
self.prepare_and_run_abi_diff_all_archs(
"libgolden_cpp", "libgolden_cpp_json", 0,
["-input-format-old", "ProtobufTextFormat",
"-input-format-new", "Json"])
def test_opaque_type_self_diff(self):
self.prepare_and_run_abi_diff_all_archs(
"libopaque_type", "libopaque_type", 0,
["-input-format-old", "Json", "-input-format-new", "Json",
"-consider-opaque-types-different"],
create_old=False, create_new=False)
def test_allow_adding_removing_weak_symbols(self):
module_old = Module.get_test_modules_by_name("libweak_symbols_old")[0]
module_new = Module.get_test_modules_by_name("libweak_symbols_new")[0]
lsdump_old = self.get_or_create_dump(module_old, False)
lsdump_new = self.get_or_create_dump(module_new, False)
options = ["-input-format-old", "Json", "-input-format-new", "Json"]
# If `-allow-adding-removing-weak-symbols` is not specified, removing a
# weak symbol must be treated as an incompatible change.
self.run_and_compare_abi_diff(
lsdump_old, lsdump_new, "libweak_symbols", "arm64", 8, options)
# If `-allow-adding-removing-weak-symbols` is specified, removing a
# weak symbol must be fine and mustn't be a fatal error.
self.run_and_compare_abi_diff(
lsdump_old, lsdump_new, "libweak_symbols", "arm64", 0,
options + ["-allow-adding-removing-weak-symbols"])
def test_linker_shared_object_file_and_version_script(self):
base_dir = os.path.join(
SCRIPT_DIR, 'integration', 'version_script_example')
cases = [
'libversion_script_example',
'libversion_script_example_no_mytag',
'libversion_script_example_no_private',
'libversion_script_example_api_level',
]
for module_name in cases:
module = Module.get_test_modules_by_name(module_name)[0]
example_lsdump_old = self.get_or_create_dump(module, False)
example_lsdump_new = self.get_or_create_dump(module, True)
self.run_and_compare_abi_diff(
example_lsdump_old, example_lsdump_new,
module_name, "arm64", 0,
["-input-format-old", "Json", "-input-format-new", "Json"])
def test_no_source(self):
self.prepare_and_run_abi_diff_all_archs(
"libempty", "libempty", 0,
["-input-format-old", "Json", "-input-format-new", "Json"])
def test_golden_anonymous_enum(self):
self.prepare_and_absolute_diff_all_archs(
"libgolden_anonymous_enum", "libgolden_anonymous_enum")
def test_swap_anonymous_enum(self):
self.prepare_and_run_abi_diff_all_archs(
"libgolden_anonymous_enum", "libswap_anonymous_enum", 0,
["-input-format-old", "Json", "-input-format-new", "Json",
"-check-all-apis"])
def test_swap_anonymous_enum_field(self):
self.prepare_and_run_abi_diff_all_archs(
"libgolden_anonymous_enum", "libswap_anonymous_enum_field", 0,
["-input-format-old", "Json", "-input-format-new", "Json",
"-check-all-apis"])
def test_anonymous_enum_odr(self):
self.prepare_and_absolute_diff_all_archs(
"libanonymous_enum_odr", "libanonymous_enum_odr")
def test_libifunc(self):
self.prepare_and_absolute_diff_all_archs(
"libifunc", "libifunc")
def test_merge_multi_definitions(self):
self.prepare_and_absolute_diff_all_archs(
"libmerge_multi_definitions", "libmerge_multi_definitions")
self.prepare_and_run_abi_diff_all_archs(
"libmerge_multi_definitions", "libdiff_multi_definitions", 0,
flags=["-input-format-new", "Json", "-input-format-old", "Json",
"-consider-opaque-types-different"],
create_old=False, create_new=False)
def test_print_resource_dir(self):
dumper_path = shutil.which("header-abi-dumper")
self.assertIsNotNone(dumper_path)
dumper_path = os.path.realpath(dumper_path)
common_dir = os.path.dirname(os.path.dirname(dumper_path))
resource_dir = subprocess.check_output(
["header-abi-dumper", "-print-resource-dir"], text=True,
stderr=subprocess.DEVNULL).strip()
self.assertIn(os.path.dirname(resource_dir),
(os.path.join(common_dir, "lib64", "clang"),
os.path.join(common_dir, "lib", "clang")))
self.assertRegex(os.path.basename(resource_dir), r"^[\d.]+$")
def test_struct_extensions(self):
output = self.prepare_and_run_abi_diff_all_archs(
"libstruct_extensions", "liballowed_struct_extensions", 4,
flags=["-input-format-new", "Json", "-input-format-old", "Json"],
create_old=False, create_new=False)
self.assertEqual(output.count("record_type_extension_diffs"), 6)
output = self.prepare_and_run_abi_diff_all_archs(
"liballowed_struct_extensions", "libstruct_extensions", 8,
flags=["-input-format-new", "Json", "-input-format-old", "Json"],
create_old=False, create_new=False)
self.assertEqual(output.count("record_type_diffs"), 6)
def test_param_size_diff(self):
self.prepare_and_run_abi_diff_all_archs(
"libpass_by_value", "libparam_size_diff", 8,
flags=["-input-format-new", "Json", "-input-format-old", "Json"],
create_old=False, create_new=False)
def test_return_size_diff(self):
self.prepare_and_run_abi_diff_all_archs(
"libpass_by_value", "libreturn_size_diff", 8,
flags=["-input-format-new", "Json", "-input-format-old", "Json"],
create_old=False, create_new=False)
def test_function_extensions(self):
diff = self.prepare_and_run_abi_diff_all_archs(
"libfunction_extensions", "liballowed_function_extensions", 4,
flags=["-input-format-new", "Json", "-input-format-old", "Json"],
create_old=False, create_new=False)
self.assertEqual(6, diff.count('function_extension_diffs'))
diff = self.prepare_and_run_abi_diff_all_archs(
"liballowed_function_extensions", "libfunction_extensions", 8,
flags=["-input-format-new", "Json", "-input-format-old", "Json"],
create_old=False, create_new=False)
# Adding and removing __restrict__ at the first level are extensions.
self.assertEqual(1, diff.count('function_extension_diffs'))
self.assertEqual(5, diff.count('function_diffs'))
def test_array_diff(self):
self.prepare_and_absolute_diff_all_archs("libarray", "libarray")
self.prepare_and_absolute_diff_all_archs(
"libarray_diff", "libarray_diff")
diff = self.prepare_and_run_abi_diff_all_archs(
"libarray", "libarray_diff", 8,
flags=["-input-format-new", "Json", "-input-format-old", "Json"],
create_old=False, create_new=False)
for type_id in ["StructMember", "Pointer", "DoublePointer"]:
self.assertNotIn(f'"{type_id}"', diff,
f'"{type_id}" should not be in the diff report.')
for type_id in ["Struct", "PointerToArray", "PointerTo2DArray",
"Reference", "Element"]:
self.assertIn(f'"{type_id}"', diff,
f'"{type_id}" should be in the diff report.')
def test_union_diff(self):
diff = self.prepare_and_run_abi_diff_all_archs(
"libunion", "libunion_diff", 8,
flags=["-input-format-new", "Json", "-input-format-old", "Json"],
create_old=False, create_new=True)
self.assertIn('"ChangeType"', diff)
self.assertIn('"ChangeTypeInStruct"', diff)
self.assertEqual(2, diff.count("fields_diff"))
self.assertNotIn("fields_added", diff)
self.assertNotIn("fields_removed", diff)
def test_enum_diff(self):
self.prepare_and_absolute_diff_all_archs("libenum", "libenum")
def test_io_error(self):
cpp_path = os.path.join(self.get_tmp_dir(), "test.cpp")
sdump_path = os.path.join(self.get_tmp_dir(), "test.sdump")
version_script_path = os.path.join(self.get_tmp_dir(), "map.txt")
lsdump_path = os.path.join(self.get_tmp_dir(), "test.lsdump")
abidiff_path = os.path.join(self.get_tmp_dir(), "test.abidiff")
read_only_path = os.path.join(self.get_tmp_dir(), "read_only.txt")
with open(cpp_path, "w") as cpp_file:
cpp_file.write("void func(int) {}")
with open(version_script_path, "w") as version_script_file:
pass
fd = os.open(read_only_path, flags=(os.O_CREAT | os.O_EXCL),
mode=(stat.S_IROTH | stat.S_IRGRP | stat.S_IRUSR))
self.assertGreaterEqual(fd, 0)
os.close(fd)
# Make sure that the commands are valid.
dumper_flags = ('-output-format', 'Json')
run_header_abi_dumper(cpp_path, sdump_path, flags=dumper_flags)
self.assertGreater(os.stat(sdump_path).st_size, 0)
linker_flags = ('-input-format', 'Json',
'-output-format', 'ProtobufTextFormat')
run_header_abi_linker([sdump_path], lsdump_path, version_script_path,
"current", "x86_64", linker_flags)
self.assertGreater(os.stat(lsdump_path).st_size, 0)
diff_flags = ('-input-format-old', 'ProtobufTextFormat',
'-input-format-new', 'ProtobufTextFormat')
return_code = run_abi_diff(lsdump_path, lsdump_path, abidiff_path,
'x86_64', 'libtest', diff_flags)
self.assertEqual(return_code, 0)
self.assertGreater(os.stat(abidiff_path).st_size, 0)
# Test with output error.
with self.assertRaises(subprocess.CalledProcessError) as assertion:
run_header_abi_dumper(cpp_path, read_only_path, flags=dumper_flags)
self.assertEqual(assertion.exception.returncode, 1)
with self.assertRaises(subprocess.CalledProcessError) as assertion:
run_header_abi_linker([sdump_path], read_only_path,
version_script_path, "current", "x86_64",
linker_flags)
self.assertEqual(assertion.exception.returncode, 255)
return_code = run_abi_diff(lsdump_path, lsdump_path, read_only_path,
'x86_64', 'libtest', diff_flags)
self.assertEqual(return_code, 1)
def test_bit_field_diff(self):
self.prepare_and_absolute_diff_all_archs(
"libbit_field", "libbit_field")
self.prepare_and_run_abi_diff_all_archs(
"libbit_field", "libbit_field_diff", 8,
flags=["-input-format-new", "Json", "-input-format-old", "Json"],
create_old=False, create_new=True)
def test_availability(self):
self.prepare_and_absolute_diff_all_archs(
"libavailability", "libavailability")
self.prepare_and_absolute_diff_all_archs(
"libavailability_35", "libavailability_35")
if __name__ == '__main__':
unittest.main()