1#!/usr/bin/env python3
2
3from __future__ import print_function
4
5import os
6import re
7import subprocess
8import sys
9import unittest
10
11from .compat import TemporaryDirectory, makedirs
12from .ndk_toolchain import create_targets
13
14
15SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
16VNDK_DEF_TOOL = os.path.join(SCRIPT_DIR, '..', 'vndk_definition_tool.py')
17
18INPUT_DIR = os.path.join(SCRIPT_DIR, 'testdata', 'test_elfdump', 'input')
19EXPECTED_DIR = os.path.join(SCRIPT_DIR, 'testdata', 'test_elfdump', 'expected')
20test_dir_base = None
21
22
23def run_elf_dump(path):
24    cmd = [sys.executable, VNDK_DEF_TOOL, 'elfdump', path]
25    return subprocess.check_output(cmd, universal_newlines=True)
26
27
28class ELFDumpTest(unittest.TestCase):
29    @classmethod
30    def setUpClass(cls):
31        cls.targets = create_targets()
32
33        if test_dir_base:
34            cls.test_dir_base = test_dir_base
35        else:
36            cls.tmp_dir = TemporaryDirectory()
37            cls.test_dir_base = cls.tmp_dir.name
38
39        cls._build_fixtures(cls.target_name)
40
41
42    @classmethod
43    def tearDownClass(cls):
44        if not test_dir_base:
45            cls.tmp_dir.cleanup()
46
47
48    @classmethod
49    def _build_fixtures(cls, target_name):
50        target = cls.targets[target_name]
51
52        cls.expected_dir = os.path.join(EXPECTED_DIR, target_name)
53        cls.test_dir = os.path.join(cls.test_dir_base, target_name)
54
55        makedirs(cls.test_dir, exist_ok=True)
56
57        # Compile main.o.
58        src_file = os.path.join(INPUT_DIR, 'main.c')
59        obj_file = os.path.join(cls.test_dir, 'main.o')
60        target.compile(obj_file, src_file, [])
61
62        # Link main.out.
63        out_file = os.path.join(cls.test_dir, 'main.out')
64        target.link(out_file, [obj_file], ['-ldl', '-lc', '-lstdc++'])
65
66        # Compile test.o.
67        src_file = os.path.join(INPUT_DIR, 'test.c')
68        obj_file = os.path.join(cls.test_dir, 'test.o')
69        target.compile(obj_file, src_file, [])
70
71        # Link libtest.so.
72        out_file = os.path.join(cls.test_dir, 'libtest.so')
73        target.link(out_file, [obj_file], ['-shared', '-lc'])
74
75        # Link libtest-rpath.so.
76        out_file = os.path.join(cls.test_dir, 'libtest-rpath.so')
77        target.link(out_file, [obj_file],
78                    ['-shared', '-lc', '-Wl,-rpath,$ORIGIN/../lib',
79                     '-Wl,--disable-new-dtags'])
80
81        # Link libtest-rpath-multi.so.
82        out_file = os.path.join(cls.test_dir, 'libtest-rpath-multi.so')
83        target.link(out_file, [obj_file],
84                    ['-shared', '-lc', '-Wl,-rpath,/system/lib:/vendor/lib',
85                     '-Wl,--disable-new-dtags'])
86
87        # Link libtest-runpath.so.
88        out_file = os.path.join(cls.test_dir, 'libtest-runpath.so')
89        target.link(out_file, [obj_file],
90                    ['-shared', '-lc', '-Wl,-rpath,$ORIGIN/../lib',
91                     '-Wl,--enable-new-dtags'])
92
93        # Link libtest-runpath-multi.so.
94        out_file = os.path.join(cls.test_dir, 'libtest-runpath-multi.so')
95        target.link(out_file, [obj_file],
96                    ['-shared', '-lc', '-Wl,-rpath,/system/lib:/vendor/lib',
97                     '-Wl,--enable-new-dtags'])
98
99
100    def _remove_size_lines(self, lines):
101        """Remove file size information because they may vary."""
102        prefixes = (
103            'FILE_SIZE\t',
104            'RO_SEG_FILE_SIZE\t',
105            'RO_SEG_MEM_SIZE\t',
106            'RW_SEG_FILE_SIZE\t',
107            'RW_SEG_MEM_SIZE\t',
108        )
109        patt = re.compile('|'.join('(?:' + re.escape(x) +')' for x in prefixes))
110        return [line for line in lines if not patt.match(line)]
111
112
113    def _assert_equal_to_file(self, expected_file_name, actual):
114        actual = actual.splitlines(True)
115        expected_file_path = os.path.join(self.expected_dir, expected_file_name)
116        with open(expected_file_path, 'r') as f:
117            expected = f.readlines()
118        self.assertEqual(self._remove_size_lines(expected),
119                         self._remove_size_lines(actual))
120
121
122    def _test_main_out(self):
123        out_file = os.path.join(self.test_dir, 'main.out')
124        self._assert_equal_to_file('main.out.txt', run_elf_dump(out_file))
125
126
127    def _test_libtest(self, expected_file_name, lib_name):
128        lib_file = os.path.join(self.test_dir, lib_name)
129        self._assert_equal_to_file(expected_file_name, run_elf_dump(lib_file))
130
131
132def create_target_test(target_name):
133    def test_main(self):
134        self._test_main_out()
135
136    def test_libtest(self):
137        self._test_libtest('libtest.so.txt', 'libtest.so')
138
139    def test_libtest_rpath(self):
140        self._test_libtest('libtest-rpath.so.txt', 'libtest-rpath.so')
141
142    def test_libtest_rpath_multi(self):
143        self._test_libtest('libtest-rpath-multi.so.txt',
144                           'libtest-rpath-multi.so')
145
146    def test_libtest_runpath(self):
147        self._test_libtest('libtest-runpath.so.txt', 'libtest-runpath.so')
148
149    def test_libtest_runpath_multi(self):
150        self._test_libtest('libtest-runpath-multi.so.txt',
151                           'libtest-runpath-multi.so')
152
153    class_name = 'ELFDumpTest_' + target_name
154    globals()[class_name] = type(
155        class_name, (ELFDumpTest,),
156        dict(test_main=test_main,
157             test_libtest=test_libtest,
158             test_libtest_rpath=test_libtest_rpath,
159             test_libtest_rpath_multi=test_libtest_rpath_multi,
160             test_libtest_runpath=test_libtest_runpath,
161             test_libtest_runpath_multi=test_libtest_runpath_multi,
162             target_name=target_name))
163
164
165for target in ('arm', 'arm64', 'mips', 'mips64', 'x86', 'x86_64'):
166    create_target_test(target)
167