• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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