xref: /aosp_15_r20/external/pigweed/pw_symbolizer/py/llvm_symbolizer_test.py (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1# Copyright 2021 The Pigweed Authors
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may not
4# use this file except in compliance with the License. You may obtain a copy of
5# the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations under
13# the License.
14"""Tests for pw_symbolizer's llvm-symbolizer based symbolization."""
15
16import os
17import shutil
18import subprocess
19import tempfile
20import unittest
21import json
22from pathlib import Path
23import pw_symbolizer
24
25_MODULE_PY_DIR = Path(__file__).parent.resolve()
26_CPP_TEST_FILE_NAME = 'symbolizer_test.cc'
27
28_COMPILER = 'clang++'
29
30
31class TestSymbolizer(unittest.TestCase):
32    """Unit tests for binary symbolization."""
33
34    def _test_symbolization_results(self, expected_symbols, symbolizer):
35        for expected_symbol in expected_symbols:
36            result = symbolizer.symbolize(expected_symbol['Address'])
37            self.assertEqual(result.name, expected_symbol['Expected'])
38            self.assertEqual(result.address, expected_symbol['Address'])
39
40            # Objects sometimes don't have a file/line number for some
41            # reason.
42            if not expected_symbol['IsObj']:
43                self.assertEqual(result.file, _CPP_TEST_FILE_NAME)
44                self.assertEqual(result.line, expected_symbol['Line'])
45
46    def _parameterized_test_symbolization(self, **llvm_symbolizer_kwargs):
47        """Tests that the symbolizer can symbolize addresses properly."""
48        self.assertTrue('PW_PIGWEED_CIPD_INSTALL_DIR' in os.environ)
49        sysroot = Path(os.environ['PW_PIGWEED_CIPD_INSTALL_DIR']).joinpath(
50            "clang_sysroot"
51        )
52        with tempfile.TemporaryDirectory() as exe_dir:
53            exe_file = Path(exe_dir) / 'print_expected_symbols'
54
55            # Compiles a binary that prints symbol addresses and expected
56            # results as JSON.
57            cmd = [
58                _COMPILER,
59                _CPP_TEST_FILE_NAME,
60                '-gfull',
61                f'-ffile-prefix-map={_MODULE_PY_DIR}=',
62                '--sysroot=%s' % sysroot,
63                '-std=c++17',
64                '-fno-pic',
65                '-fno-pie',
66                '-no-pie',
67                '-o',
68                exe_file,
69            ]
70
71            process = subprocess.run(
72                cmd,
73                stdout=subprocess.PIPE,
74                stderr=subprocess.STDOUT,
75                cwd=_MODULE_PY_DIR,
76            )
77            self.assertEqual(process.returncode, 0)
78
79            process = subprocess.run(
80                [exe_file], stdout=subprocess.PIPE, stderr=subprocess.STDOUT
81            )
82            self.assertEqual(process.returncode, 0)
83
84            expected_symbols = [
85                json.loads(line)
86                for line in process.stdout.decode().splitlines()
87            ]
88
89            with self.subTest("non-legacy"):
90                symbolizer = pw_symbolizer.LlvmSymbolizer(
91                    exe_file, **llvm_symbolizer_kwargs
92                )
93                self._test_symbolization_results(expected_symbols, symbolizer)
94                symbolizer.close()
95
96            with self.subTest("backwards-compability"):
97                # Test backwards compatibility with older versions of
98                # llvm-symbolizer.
99                symbolizer = pw_symbolizer.LlvmSymbolizer(
100                    exe_file, force_legacy=True, **llvm_symbolizer_kwargs
101                )
102                self._test_symbolization_results(expected_symbols, symbolizer)
103                symbolizer.close()
104
105    def test_symbolization_default_binary(self):
106        self._parameterized_test_symbolization()
107
108    def test_symbolization_specified_binary(self):
109        location = Path(
110            subprocess.run(
111                ['which', 'llvm-symbolizer'], check=True, stdout=subprocess.PIPE
112            )
113            .stdout.decode()
114            .strip()
115        )
116        with tempfile.TemporaryDirectory() as copy_dir:
117            copy_location = Path(copy_dir) / "copy-llvm-symbolizer"
118            shutil.copy(location, copy_location)
119            self._parameterized_test_symbolization(
120                llvm_symbolizer_binary=copy_location
121            )
122
123
124if __name__ == '__main__':
125    unittest.main()
126