1#!/usr/bin/env python3 2 3import os 4import shutil 5import subprocess 6import sys 7import tempfile 8import unittest 9 10import_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) 11import_path = os.path.abspath(os.path.join(import_path, 'utils')) 12sys.path.insert(1, import_path) 13 14from utils import run_abi_diff, run_header_abi_dumper 15from module import Module 16 17 18SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__)) 19INPUT_DIR = os.path.join(SCRIPT_DIR, 'input') 20EXPECTED_DIR = os.path.join(SCRIPT_DIR, 'expected') 21EXPORTED_HEADER_DIRS = (INPUT_DIR,) 22REF_DUMP_DIR = os.path.join(SCRIPT_DIR, 'reference_dumps') 23 24 25def make_and_copy_reference_dumps(module, reference_dump_dir=REF_DUMP_DIR): 26 dump_dir = os.path.join(reference_dump_dir, module.arch) 27 os.makedirs(dump_dir, exist_ok=True) 28 dump_path = os.path.join(dump_dir, module.get_dump_name()) 29 module.make_dump(dump_path) 30 return dump_path 31 32 33def _read_output_content(dump_path): 34 with open(dump_path, 'r') as f: 35 return f.read() 36 37 38class HeaderCheckerTest(unittest.TestCase): 39 @classmethod 40 def setUpClass(cls): 41 cls.maxDiff = None 42 43 def setUp(self): 44 self.tmp_dir = None 45 46 def tearDown(self): 47 if self.tmp_dir: 48 self.tmp_dir.cleanup() 49 self.tmp_dir = None 50 51 def get_tmp_dir(self): 52 if not self.tmp_dir: 53 self.tmp_dir = tempfile.TemporaryDirectory() 54 return self.tmp_dir.name 55 56 def run_and_compare(self, input_path, expected_path, cflags=[]): 57 with open(expected_path, 'r') as f: 58 expected_output = f.read() 59 with tempfile.NamedTemporaryFile(dir=self.get_tmp_dir(), 60 delete=False) as f: 61 output_path = f.name 62 run_header_abi_dumper(input_path, output_path, cflags, 63 EXPORTED_HEADER_DIRS) 64 actual_output = _read_output_content(output_path) 65 self.assertEqual(actual_output, expected_output) 66 67 def run_and_compare_name(self, name, cflags=[]): 68 input_path = os.path.join(INPUT_DIR, name) 69 expected_path = os.path.join(EXPECTED_DIR, name) 70 self.run_and_compare(input_path, expected_path, cflags) 71 72 def run_and_compare_name_cpp(self, name, cflags=[]): 73 self.run_and_compare_name(name, cflags + ['-x', 'c++', '-std=c++11']) 74 75 def run_and_compare_name_c_cpp(self, name, cflags=[]): 76 self.run_and_compare_name(name, cflags) 77 self.run_and_compare_name_cpp(name, cflags) 78 79 def run_and_compare_abi_diff(self, old_dump, new_dump, lib, arch, 80 expected_return_code, flags=[]): 81 actual_output = run_abi_diff(old_dump, new_dump, arch, lib, flags) 82 self.assertEqual(actual_output, expected_return_code) 83 84 def prepare_and_run_abi_diff(self, old_ref_dump_path, new_ref_dump_path, 85 target_arch, expected_return_code, flags=[]): 86 self.run_and_compare_abi_diff(old_ref_dump_path, new_ref_dump_path, 87 'test', target_arch, 88 expected_return_code, flags) 89 90 def get_or_create_ref_dump(self, module, create): 91 if create: 92 return make_and_copy_reference_dumps(module, self.get_tmp_dir()) 93 return os.path.join(REF_DUMP_DIR, module.arch, module.get_dump_name()) 94 95 def prepare_and_run_abi_diff_all_archs(self, old_lib, new_lib, 96 expected_return_code, flags=[], 97 create_old=False, create_new=True): 98 old_modules = Module.get_test_modules_by_name(old_lib) 99 new_modules = Module.get_test_modules_by_name(new_lib) 100 self.assertEqual(len(old_modules), len(new_modules)) 101 102 for old_module, new_module in zip(old_modules, new_modules): 103 self.assertEqual(old_module.arch, new_module.arch) 104 old_ref_dump_path = self.get_or_create_ref_dump(old_module, 105 create_old) 106 new_ref_dump_path = self.get_or_create_ref_dump(new_module, 107 create_new) 108 self.prepare_and_run_abi_diff( 109 old_ref_dump_path, new_ref_dump_path, new_module.arch, 110 expected_return_code, flags) 111 112 def prepare_and_absolute_diff_all_archs(self, old_lib, new_lib): 113 old_modules = Module.get_test_modules_by_name(old_lib) 114 new_modules = Module.get_test_modules_by_name(new_lib) 115 self.assertEqual(len(old_modules), len(new_modules)) 116 117 for old_module, new_module in zip(old_modules, new_modules): 118 self.assertEqual(old_module.arch, new_module.arch) 119 old_ref_dump_path = self.get_or_create_ref_dump(old_module, False) 120 new_ref_dump_path = self.get_or_create_ref_dump(new_module, True) 121 self.assertEqual(_read_output_content(old_ref_dump_path), 122 _read_output_content(new_ref_dump_path)) 123 124 def test_example1_cpp(self): 125 self.run_and_compare_name_cpp('example1.cpp') 126 127 def test_example1_h(self): 128 self.run_and_compare_name_cpp('example1.h') 129 130 def test_example2_h(self): 131 self.run_and_compare_name_cpp('example2.h') 132 133 def test_example3_h(self): 134 self.run_and_compare_name_cpp('example3.h') 135 136 def test_undeclared_types_h(self): 137 self.prepare_and_absolute_diff_all_archs( 138 'undeclared_types.h', 'undeclared_types.h') 139 140 def test_known_issues_h(self): 141 self.prepare_and_absolute_diff_all_archs( 142 'known_issues.h', 'known_issues.h') 143 144 def test_libc_and_cpp(self): 145 self.prepare_and_run_abi_diff_all_archs( 146 "libc_and_cpp", "libc_and_cpp", 0) 147 148 def test_libc_and_cpp_and_libc_and_cpp_with_unused_struct(self): 149 self.prepare_and_run_abi_diff_all_archs( 150 "libc_and_cpp", "libc_and_cpp_with_unused_struct", 0) 151 152 def test_libc_and_cpp_and_libc_and_cpp_with_unused_struct_allow(self): 153 self.prepare_and_run_abi_diff_all_archs( 154 "libc_and_cpp", "libc_and_cpp_with_unused_struct", 0, 155 ["-allow-unreferenced-changes"]) 156 157 def test_libc_and_cpp_and_libc_and_cpp_with_unused_struct_check_all(self): 158 self.prepare_and_run_abi_diff_all_archs( 159 "libc_and_cpp", "libc_and_cpp_with_unused_struct", 1, 160 ['-check-all-apis']) 161 162 def test_libc_and_cpp_with_unused_struct_and_libc_and_cpp_with_unused_cstruct( 163 self): 164 self.prepare_and_run_abi_diff_all_archs( 165 "libc_and_cpp_with_unused_struct", 166 "libc_and_cpp_with_unused_cstruct", 0, 167 ['-check-all-apis', '-allow-unreferenced-changes']) 168 169 def test_libc_and_cpp_and_libc_and_cpp_with_unused_struct_check_all_advice( 170 self): 171 self.prepare_and_run_abi_diff_all_archs( 172 "libc_and_cpp", "libc_and_cpp_with_unused_struct", 0, 173 ['-check-all-apis', '-advice-only']) 174 175 def test_libc_and_cpp_opaque_pointer_diff(self): 176 self.prepare_and_run_abi_diff_all_archs( 177 "libc_and_cpp_with_opaque_ptr_a", 178 "libc_and_cpp_with_opaque_ptr_b", 8, 179 ['-consider-opaque-types-different'], True, True) 180 181 def test_libgolden_cpp_return_type_diff(self): 182 self.prepare_and_run_abi_diff_all_archs( 183 "libgolden_cpp", "libgolden_cpp_return_type_diff", 8) 184 185 def test_libgolden_cpp_add_odr(self): 186 self.prepare_and_run_abi_diff_all_archs( 187 "libgolden_cpp", "libgolden_cpp_odr", 0, 188 ['-check-all-apis', '-allow-unreferenced-changes']) 189 190 def test_libgolden_cpp_add_function(self): 191 self.prepare_and_run_abi_diff_all_archs( 192 "libgolden_cpp", "libgolden_cpp_add_function", 4) 193 194 def test_libgolden_cpp_add_function_allow_extension(self): 195 self.prepare_and_run_abi_diff_all_archs( 196 "libgolden_cpp", "libgolden_cpp_add_function", 0, 197 ['-allow-extensions']) 198 199 def test_libgolden_cpp_add_function_and_elf_symbol(self): 200 self.prepare_and_run_abi_diff_all_archs( 201 "libgolden_cpp", "libgolden_cpp_add_function_and_unexported_elf", 202 4) 203 204 def test_libgolden_cpp_fabricated_function_ast_removed_diff(self): 205 self.prepare_and_run_abi_diff_all_archs( 206 "libgolden_cpp_add_function_sybmol_only", 207 "libgolden_cpp_add_function", 0, [], False, False) 208 209 def test_libgolden_cpp_change_function_access(self): 210 self.prepare_and_run_abi_diff_all_archs( 211 "libgolden_cpp", "libgolden_cpp_change_function_access", 8) 212 213 def test_libgolden_cpp_add_global_variable(self): 214 self.prepare_and_run_abi_diff_all_archs( 215 "libgolden_cpp", "libgolden_cpp_add_global_variable", 4) 216 217 def test_libgolden_cpp_change_global_var_access(self): 218 self.prepare_and_run_abi_diff_all_archs( 219 "libgolden_cpp_add_global_variable", 220 "libgolden_cpp_add_global_variable_private", 8) 221 222 def test_libgolden_cpp_parameter_type_diff(self): 223 self.prepare_and_run_abi_diff_all_archs( 224 "libgolden_cpp", "libgolden_cpp_parameter_type_diff", 8) 225 226 def test_libgolden_cpp_with_vtable_diff(self): 227 self.prepare_and_run_abi_diff_all_archs( 228 "libgolden_cpp", "libgolden_cpp_vtable_diff", 8) 229 230 def test_libgolden_cpp_member_diff_advice_only(self): 231 self.prepare_and_run_abi_diff_all_archs( 232 "libgolden_cpp", "libgolden_cpp_member_diff", 0, ['-advice-only']) 233 234 def test_libgolden_cpp_member_diff(self): 235 self.prepare_and_run_abi_diff_all_archs( 236 "libgolden_cpp", "libgolden_cpp_member_diff", 8) 237 238 def test_libgolden_cpp_change_member_access(self): 239 self.prepare_and_run_abi_diff_all_archs( 240 "libgolden_cpp", "libgolden_cpp_change_member_access", 8) 241 242 def test_libgolden_cpp_enum_extended(self): 243 self.prepare_and_run_abi_diff_all_archs( 244 "libgolden_cpp", "libgolden_cpp_enum_extended", 4) 245 246 def test_libgolden_cpp_enum_diff(self): 247 self.prepare_and_run_abi_diff_all_archs( 248 "libgolden_cpp", "libgolden_cpp_enum_diff", 8) 249 250 def test_libgolden_cpp_member_fake_diff(self): 251 self.prepare_and_run_abi_diff_all_archs( 252 "libgolden_cpp", "libgolden_cpp_member_fake_diff", 0) 253 254 def test_libgolden_cpp_member_integral_type_diff(self): 255 self.prepare_and_run_abi_diff_all_archs( 256 "libgolden_cpp", "libgolden_cpp_member_integral_type_diff", 8) 257 258 def test_libgolden_cpp_member_cv_diff(self): 259 self.prepare_and_run_abi_diff_all_archs( 260 "libgolden_cpp", "libgolden_cpp_member_cv_diff", 8) 261 262 def test_libgolden_cpp_unreferenced_elf_symbol_removed(self): 263 self.prepare_and_run_abi_diff_all_archs( 264 "libgolden_cpp", "libgolden_cpp_unreferenced_elf_symbol_removed", 265 16) 266 267 def test_libreproducability(self): 268 self.prepare_and_absolute_diff_all_archs( 269 "libreproducability", "libreproducability") 270 271 def test_libgolden_cpp_member_name_changed(self): 272 self.prepare_and_run_abi_diff_all_archs( 273 "libgolden_cpp", "libgolden_cpp_member_name_changed", 0) 274 275 def test_libgolden_cpp_member_function_pointer_changed(self): 276 self.prepare_and_run_abi_diff_all_archs( 277 "libgolden_cpp_function_pointer", 278 "libgolden_cpp_function_pointer_parameter_added", 8, [], 279 True, True) 280 281 def test_libgolden_cpp_internal_struct_access_upgraded(self): 282 self.prepare_and_run_abi_diff_all_archs( 283 "libgolden_cpp_internal_private_struct", 284 "libgolden_cpp_internal_public_struct", 0, [], True, True) 285 286 def test_libgolden_cpp_internal_struct_access_downgraded(self): 287 self.prepare_and_run_abi_diff_all_archs( 288 "libgolden_cpp_internal_public_struct", 289 "libgolden_cpp_internal_private_struct", 8, [], True, True) 290 291 def test_libgolden_cpp_inheritance_type_changed(self): 292 self.prepare_and_run_abi_diff_all_archs( 293 "libgolden_cpp", "libgolden_cpp_inheritance_type_changed", 8, [], 294 True, True) 295 296 def test_libpure_virtual_function(self): 297 self.prepare_and_absolute_diff_all_archs( 298 "libpure_virtual_function", "libpure_virtual_function") 299 300 def test_libc_and_cpp_in_json(self): 301 self.prepare_and_absolute_diff_all_archs( 302 "libgolden_cpp_json", "libgolden_cpp_json") 303 304 def test_libc_and_cpp_in_protobuf_and_json(self): 305 self.prepare_and_run_abi_diff_all_archs( 306 "libgolden_cpp", "libgolden_cpp_json", 0, 307 ["-input-format-old", "ProtobufTextFormat", 308 "-input-format-new", "Json"]) 309 310 def test_opaque_type_self_diff(self): 311 lsdump = os.path.join( 312 SCRIPT_DIR, "abi_dumps", "opaque_ptr_types.lsdump") 313 self.run_and_compare_abi_diff( 314 lsdump, lsdump, "libexample", "arm64", 0, 315 ["-input-format-old", "Json", "-input-format-new", "Json", 316 "-consider-opaque-types-different"]) 317 318 def test_allow_adding_removing_weak_symbols(self): 319 module_old = Module.get_test_modules_by_name("libweak_symbols_old")[0] 320 module_new = Module.get_test_modules_by_name("libweak_symbols_new")[0] 321 lsdump_old = self.get_or_create_ref_dump(module_old, False) 322 lsdump_new = self.get_or_create_ref_dump(module_new, False) 323 324 options = ["-input-format-old", "Json", "-input-format-new", "Json"] 325 326 # If `-allow-adding-removing-weak-symbols` is not specified, removing a 327 # weak symbol must be treated as an incompatible change. 328 self.run_and_compare_abi_diff( 329 lsdump_old, lsdump_new, "libweak_symbols", "arm64", 8, options) 330 331 # If `-allow-adding-removing-weak-symbols` is specified, removing a 332 # weak symbol must be fine and mustn't be a fatal error. 333 self.run_and_compare_abi_diff( 334 lsdump_old, lsdump_new, "libweak_symbols", "arm64", 0, 335 options + ["-allow-adding-removing-weak-symbols"]) 336 337 def test_linker_shared_object_file_and_version_script(self): 338 base_dir = os.path.join( 339 SCRIPT_DIR, 'integration', 'version_script_example') 340 341 cases = [ 342 'libversion_script_example', 343 'libversion_script_example_no_mytag', 344 'libversion_script_example_no_private', 345 ] 346 347 for module_name in cases: 348 module = Module.get_test_modules_by_name(module_name)[0] 349 example_lsdump_old = self.get_or_create_ref_dump(module, False) 350 example_lsdump_new = self.get_or_create_ref_dump(module, True) 351 self.run_and_compare_abi_diff( 352 example_lsdump_old, example_lsdump_new, 353 module_name, "arm64", 0, 354 ["-input-format-old", "Json", "-input-format-new", "Json"]) 355 356 def test_no_source(self): 357 self.prepare_and_run_abi_diff_all_archs( 358 "libempty", "libempty", 0, 359 ["-input-format-old", "Json", "-input-format-new", "Json"]) 360 361 def test_golden_anonymous_enum(self): 362 self.prepare_and_absolute_diff_all_archs( 363 "libgolden_anonymous_enum", "libgolden_anonymous_enum") 364 365 def test_swap_anonymous_enum(self): 366 self.prepare_and_run_abi_diff_all_archs( 367 "libgolden_anonymous_enum", "libswap_anonymous_enum", 0, 368 ["-input-format-old", "Json", "-input-format-new", "Json", 369 "-check-all-apis"]) 370 371 def test_swap_anonymous_enum_field(self): 372 self.prepare_and_run_abi_diff_all_archs( 373 "libgolden_anonymous_enum", "libswap_anonymous_enum_field", 0, 374 ["-input-format-old", "Json", "-input-format-new", "Json", 375 "-check-all-apis"]) 376 377 def test_anonymous_enum_odr(self): 378 self.prepare_and_absolute_diff_all_archs( 379 "libanonymous_enum_odr", "libanonymous_enum_odr") 380 381 def test_libifunc(self): 382 self.prepare_and_absolute_diff_all_archs( 383 "libifunc", "libifunc") 384 385 def test_merge_multi_definitions(self): 386 self.prepare_and_absolute_diff_all_archs( 387 "libmerge_multi_definitions", "libmerge_multi_definitions") 388 389 def test_print_resource_dir(self): 390 dumper_path = shutil.which("header-abi-dumper") 391 self.assertIsNotNone(dumper_path) 392 dumper_path = os.path.realpath(dumper_path) 393 common_dir = os.path.dirname(os.path.dirname(dumper_path)) 394 resource_dir = subprocess.check_output( 395 ["header-abi-dumper", "-print-resource-dir"], text=True, 396 stderr=subprocess.DEVNULL).strip() 397 self.assertEqual(os.path.dirname(resource_dir), 398 os.path.join(common_dir, "lib64", "clang")) 399 self.assertRegex(os.path.basename(resource_dir), r"^[\d.]+$") 400 401 402if __name__ == '__main__': 403 unittest.main() 404