Home | History | Annotate | Download | only in tests
      1 #!/usr/bin/env python3
      2 #
      3 # Copyright (C) 2017 The Android Open Source Project
      4 #
      5 # Licensed under the Apache License, Version 2.0 (the "License");
      6 # you may not use this file except in compliance with the License.
      7 # You may obtain a copy of the License at
      8 #
      9 #      http://www.apache.org/licenses/LICENSE-2.0
     10 #
     11 # Unless required by applicable law or agreed to in writing, software
     12 # distributed under the License is distributed on an "AS IS" BASIS,
     13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14 # See the License for the specific language governing permissions and
     15 # limitations under the License.
     16 
     17 import argparse
     18 import collections
     19 import difflib
     20 import os
     21 import subprocess
     22 import sys
     23 import tempfile
     24 
     25 """Test vndk vtable dumper"""
     26 
     27 NDK_VERSION = 'r11'
     28 API_LEVEL = 'android-24'
     29 
     30 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
     31 VNDK_VTABLE_DUMPER = 'vndk-vtable-dumper'
     32 
     33 def get_dirnames(path, n):
     34     """Get directory, n directories before path"""
     35     for i in range(n):
     36         path = os.path.dirname(path)
     37     return path
     38 
     39 
     40 def get_prebuilts_host():
     41     """Get the host dir for prebuilts"""
     42     if sys.platform.startswith('linux'):
     43         return 'linux-x86'
     44     if sys.platform.startswith('darwin'):
     45         return 'darwin-x86'
     46     raise NotImplementedError('unknown platform')
     47 
     48 
     49 def get_prebuilts_gcc(android_build_top, arch, gcc_version):
     50     """Get the path to gcc for the current platform"""
     51     return os.path.join(android_build_top, 'prebuilts', 'gcc',
     52                         get_prebuilts_host(), arch, gcc_version)
     53 
     54 def get_prebuilts_clang(android_build_top):
     55     """Get the path to prebuilt gcc for the current platform"""
     56     return os.path.join(android_build_top, 'prebuilts', 'clang', 'host',
     57                         get_prebuilts_host(), 'clang-stable')
     58 
     59 def get_prebuilts_ndk(android_build_top, subdirs):
     60     """Get the path to prebuilt ndk  for the current platform and API level"""
     61     return os.path.join(android_build_top, 'prebuilts', 'ndk', NDK_VERSION,
     62                         'platforms', API_LEVEL, *subdirs)
     63 
     64 def run_cmd(cmd, verbose=False):
     65     """Run the command given and print the command if verbose is True"""
     66     if verbose:
     67         print('RUN:', ' '.join(cmd), file=sys.stderr)
     68     subprocess.check_call(cmd)
     69 
     70 
     71 def run_output(cmd, verbose=False):
     72     """Run the command given and print output of the command"""
     73     if verbose:
     74         print('RUN:', ' '.join(cmd), file=sys.stderr)
     75     return subprocess.check_output(cmd, universal_newlines=True)
     76 
     77 
     78 def run_vtable_dump(path, verbose=False):
     79     """Run vndk vtable dumper"""
     80     return run_output([VNDK_VTABLE_DUMPER, path], verbose)
     81 
     82 
     83 class Target(object):
     84     """Class representing a target: for eg: x86, arm64 etc"""
     85     def __init__(self, name, triple, cflags, ldflags, gcc_toolchain_dir,
     86                  clang_dir, ndk_include, ndk_lib):
     87         """Parameterized Constructor"""
     88         self.name = name
     89         self.target_triple = triple
     90         self.target_cflags = cflags
     91         self.target_ldflags = ldflags
     92 
     93         self.gcc_toolchain_dir = gcc_toolchain_dir
     94         self.clang_dir = clang_dir
     95         self.ndk_include = ndk_include
     96         self.ndk_lib = ndk_lib
     97 
     98     def compile(self, obj_file, src_file, cflags, verbose=False):
     99         """Compiles the given source files and produces a .o at obj_file"""
    100         clangpp = os.path.join(self.clang_dir, 'bin', 'clang++')
    101 
    102         cmd = [clangpp, '-o', obj_file, '-c', src_file]
    103         cmd.extend(['-fPIE', '-fPIC', '-fno-rtti', '-std=c++11'])
    104         cmd.extend(['-gcc-toolchain', self.gcc_toolchain_dir])
    105         cmd.extend(['-target', self.target_triple])
    106         cmd.extend(['-isystem', self.ndk_include])
    107         cmd.extend(cflags)
    108         cmd.extend(self.target_cflags)
    109         run_cmd(cmd, verbose)
    110 
    111     def link(self, out_file, obj_files, ldflags, verbose=False):
    112         """Link the given obj files to form a shared library"""
    113         crtbegin = os.path.join(self.ndk_lib, 'crtbegin_so.o')
    114         crtend = os.path.join(self.ndk_lib, 'crtend_so.o')
    115         clangpp = os.path.join(self.clang_dir, 'bin', 'clang++')
    116 
    117         cmd = [clangpp, '-o', out_file]
    118         cmd.extend(['-fPIE', '-fPIC', '-fno-rtti', '-Wl,--no-undefined', '-nostdlib'])
    119         cmd.append('-L' + self.ndk_lib)
    120         cmd.extend(['-gcc-toolchain', self.gcc_toolchain_dir])
    121         cmd.extend(['-target', self.target_triple])
    122         cmd.append(crtbegin)
    123         cmd.extend(obj_files)
    124         cmd.append(crtend)
    125         cmd.extend(ldflags)
    126         cmd.extend(self.target_ldflags)
    127         run_cmd(cmd, verbose)
    128 
    129 
    130 def create_targets(top):
    131     """Create multiple targets objects, one for each architecture supported"""
    132     return [
    133         Target('arm', 'arm-linux-androideabi', [],[],
    134                get_prebuilts_gcc(top, 'arm', 'arm-linux-androideabi-4.9'),
    135                get_prebuilts_clang(top),
    136                get_prebuilts_ndk(top, ['arch-arm', 'usr', 'include']),
    137                get_prebuilts_ndk(top, ['arch-arm', 'usr', 'lib'])),
    138 
    139         Target('arm64', 'aarch64-linux-android', [], [],
    140                get_prebuilts_gcc(top, 'aarch64', 'aarch64-linux-android-4.9'),
    141                get_prebuilts_clang(top),
    142                get_prebuilts_ndk(top, ['arch-arm64', 'usr', 'include']),
    143                get_prebuilts_ndk(top, ['arch-arm64', 'usr', 'lib'])),
    144 
    145         Target('mips', 'mipsel-linux-android', [], [],
    146                get_prebuilts_gcc(top, 'mips', 'mips64el-linux-android-4.9'),
    147                get_prebuilts_clang(top),
    148                get_prebuilts_ndk(top, ['arch-mips', 'usr', 'include']),
    149                get_prebuilts_ndk(top, ['arch-mips', 'usr', 'lib'])),
    150 
    151         Target('mips64', 'mips64el-linux-android',
    152                ['-march=mips64el', '-mcpu=mips64r6'],
    153                ['-march=mips64el', '-mcpu=mips64r6'],
    154                get_prebuilts_gcc(top, 'mips', 'mips64el-linux-android-4.9'),
    155                get_prebuilts_clang(top),
    156                get_prebuilts_ndk(top, ['arch-mips64', 'usr', 'include']),
    157                get_prebuilts_ndk(top, ['arch-mips64', 'usr', 'lib64'])),
    158 
    159         Target('x86', 'x86_64-linux-android', ['-m32'], ['-m32'],
    160                get_prebuilts_gcc(top, 'x86', 'x86_64-linux-android-4.9'),
    161                get_prebuilts_clang(top),
    162                get_prebuilts_ndk(top, ['arch-x86', 'usr', 'include']),
    163                get_prebuilts_ndk(top, ['arch-x86', 'usr', 'lib'])),
    164 
    165         Target('x86_64', 'x86_64-linux-android', ['-m64'], ['-m64'],
    166                get_prebuilts_gcc(top, 'x86', 'x86_64-linux-android-4.9'),
    167                get_prebuilts_clang(top),
    168                get_prebuilts_ndk(top, ['arch-x86_64', 'usr', 'include']),
    169                get_prebuilts_ndk(top, ['arch-x86_64', 'usr', 'lib64'])),
    170     ]
    171 
    172 
    173 class TestRunner(object):
    174     """Class to run the test"""
    175     def __init__(self, expected_dir, test_dir, verbose):
    176         """Parameterized constructor"""
    177         self.expected_dir = expected_dir
    178         self.test_dir = test_dir
    179         self.verbose = verbose
    180         self.num_errors = 0
    181 
    182     def check_output(self, expected_file_path, actual):
    183         """Compare the output of the test run and the expected output"""
    184         actual = actual.splitlines(True)
    185         with open(expected_file_path, 'r') as f:
    186             expected = f.readlines()
    187         if actual == expected:
    188             return
    189         for line in difflib.context_diff(expected, actual,
    190                                          fromfile=expected_file_path,
    191                                          tofile='actual'):
    192             sys.stderr.write(line)
    193         self.num_errors += 1
    194 
    195     def run_test_for_target(self, target):
    196         """Run the test for a specific target"""
    197         print('Testing target', target.name, '...', file=sys.stderr)
    198 
    199         expected_dir = os.path.join(self.expected_dir, target.name)
    200 
    201         # Create test directory for this target.
    202         test_dir = os.path.join(self.test_dir, target.name)
    203         os.makedirs(test_dir, exist_ok=True)
    204 
    205         # Compile and test "libtest.so".
    206         src_file = os.path.join(SCRIPT_DIR, 'test1.cpp')
    207         obj_file = os.path.join(test_dir, 'test.o')
    208         target.compile(obj_file, src_file, [], self.verbose)
    209 
    210         out_file = os.path.join(test_dir, 'libtest.so')
    211         target.link(out_file, [obj_file],
    212                     ['-shared', '-lc', '-lgcc', '-lstdc++'],
    213                     self.verbose)
    214         self.check_output(os.path.join(expected_dir, 'libtest.so.txt'),
    215                           run_vtable_dump(out_file, self.verbose))
    216 
    217     def run_test(self, targets):
    218         """Run test fo all targets"""
    219         for target in targets:
    220             self.run_test_for_target(target)
    221 
    222 
    223 def main():
    224     """ Set up and run test"""
    225     # Parse command line arguments.
    226     parser = argparse.ArgumentParser()
    227     parser.add_argument('--verbose', '-v', action='store_true')
    228     parser.add_argument('--android-build-top', help='path to android build top')
    229     parser.add_argument('--test-dir',
    230                         help='directory for temporary files')
    231     parser.add_argument('--expected-dir', help='directory with expected output')
    232     args = parser.parse_args()
    233 
    234     # Find ${ANDROID_BUILD_TOP}.
    235     if args.android_build_top:
    236         android_build_top = args.android_build_top
    237     else:
    238         android_build_top = get_dirnames(SCRIPT_DIR, 5)
    239 
    240     # Find expected output directory.
    241     if args.expected_dir:
    242         expected_dir = args.expected_dir
    243     else:
    244         expected_dir = os.path.join(SCRIPT_DIR, 'expected')
    245 
    246     # Load compilation targets.
    247     targets = create_targets(android_build_top)
    248 
    249     # Run tests.
    250     if args.test_dir:
    251         os.makedirs(args.test_dir, exist_ok=True)
    252         runner = TestRunner(expected_dir, args.test_dir, args.verbose)
    253         runner.run_test(targets)
    254     else:
    255         with tempfile.TemporaryDirectory() as test_dir:
    256             runner = TestRunner(expected_dir, test_dir, args.verbose)
    257             runner.run_test(targets)
    258 
    259     if runner.num_errors:
    260         print('FAILED:', runner.num_errors, 'test(s) failed', file=sys.stderr)
    261     else:
    262         print('SUCCESS', file=sys.stderr)
    263 
    264     return 1 if runner.num_errors else 0
    265 
    266 if __name__ == '__main__':
    267     sys.exit(main())
    268