Home | History | Annotate | Download | only in libcxx
      1 #!/usr/bin/env python
      2 #
      3 # Copyright (C) 2015 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 """Runs the libc++ tests against the platform libc++."""
     18 from __future__ import print_function
     19 
     20 import argparse
     21 import logging
     22 import os
     23 import posixpath
     24 import sys
     25 
     26 THIS_DIR = os.path.dirname(os.path.realpath(__file__))
     27 ANDROID_DIR = os.path.realpath(os.path.join(THIS_DIR, '../..'))
     28 
     29 
     30 def logger():
     31     """Returns the logger for the module."""
     32     return logging.getLogger(__name__)
     33 
     34 
     35 def call(cmd, *args, **kwargs):
     36     """subprocess.call with logging."""
     37     import subprocess
     38     logger().info('call %s', ' '.join(cmd))
     39     return subprocess.call(cmd, *args, **kwargs)
     40 
     41 
     42 def check_call(cmd, *args, **kwargs):
     43     """subprocess.check_call with logging."""
     44     import subprocess
     45     logger().info('check_call %s', ' '.join(cmd))
     46     return subprocess.check_call(cmd, *args, **kwargs)
     47 
     48 
     49 def check_output(cmd, *args, **kwargs):
     50     """subprocess.check_output with logging."""
     51     import subprocess
     52     logger().info('check_output %s', ' '.join(cmd))
     53     return subprocess.check_output(cmd, *args, **kwargs)
     54 
     55 
     56 class ArgParser(argparse.ArgumentParser):
     57     """Parses command line arguments."""
     58 
     59     def __init__(self):
     60         super(ArgParser, self).__init__()
     61         self.add_argument('--bitness', choices=(32, 64), type=int, default=32)
     62         self.add_argument('--host', action='store_true')
     63 
     64 
     65 def extract_build_cmds(commands, exe_name):
     66     """Extracts build command information from `ninja -t commands` output.
     67 
     68     Args:
     69         commands: String containing the output of `ninja -t commands` for the
     70             libcxx_test_template.
     71         exe_name: The basename of the built executable.
     72 
     73     Returns:
     74         Tuple of (compiler, compiler_flags, linker_flags).
     75     """
     76     cc = None
     77     cflags = None
     78     ldflags = None
     79     template_name = 'external/libcxx/libcxx_test_template.cpp'
     80 
     81     for cmd in commands.splitlines():
     82         cmd_args = cmd.split()
     83         if cc is None and template_name in cmd_args:
     84             for i, arg in enumerate(cmd_args):
     85                 if arg == '-o':
     86                     cmd_args[i + 1] = '%OUT%'
     87                 elif arg == template_name:
     88                     cmd_args[i] = '%SOURCE%'
     89                 # Drop dependency tracking args since they can cause file
     90                 # not found errors at test time.
     91                 if arg == '-MD':
     92                     cmd_args[i] = ''
     93                 if arg == '-MF':
     94                     cmd_args[i] = ''
     95                     cmd_args[i + 1] = ''
     96             if cmd_args[0] == 'PWD=/proc/self/cwd':
     97                 cmd_args = cmd_args[1:]
     98             if cmd_args[0].endswith('gomacc'):
     99                 cmd_args = cmd_args[1:]
    100             cc = cmd_args[0]
    101             cflags = cmd_args[1:]
    102         if ldflags is None:
    103             is_ld = False
    104             for i, arg in enumerate(cmd_args):
    105                 # Here we assume that the rspfile contains the path to the
    106                 # object file and nothing else.
    107                 if arg.startswith('@'):
    108                     cmd_args[i] = '%SOURCE%'
    109                 if arg == '-o' and cmd_args[i + 1].endswith(exe_name):
    110                     cmd_args[i + 1] = '%OUT%'
    111                     is_ld = True
    112             if is_ld:
    113                 ldflags = cmd_args[1:]
    114 
    115     return cc, cflags, ldflags
    116 
    117 
    118 def get_build_cmds(bitness, host):
    119     """Use ninja -t commands to find the build commands for an executable."""
    120     out_dir = os.getenv('OUT_DIR', os.path.join(ANDROID_DIR, 'out'))
    121     product_out = os.getenv('ANDROID_PRODUCT_OUT')
    122 
    123     if host:
    124         rel_out_dir = os.path.relpath(
    125             os.path.join(out_dir, 'soong/host/linux-x86/bin'), ANDROID_DIR)
    126         target = os.path.join(rel_out_dir, 'libcxx_test_template64')
    127     else:
    128         exe_name = 'libcxx_test_template' + str(bitness)
    129         rel_out_dir = os.path.relpath(product_out, ANDROID_DIR)
    130         target = os.path.join(rel_out_dir, 'system/bin', exe_name)
    131 
    132     # Generate $OUT_DIR/combined-$TARGET_PRODUCT.ninja and build the
    133     # template target's dependencies.
    134     check_call(['make', '-C', ANDROID_DIR, target])
    135 
    136     ninja_path = os.path.join(
    137         out_dir, 'combined-' + os.getenv('TARGET_PRODUCT') + '.ninja')
    138     commands = check_output([
    139         os.path.join(ANDROID_DIR, 'prebuilts/build-tools/linux-x86/bin/ninja'),
    140         '-C', ANDROID_DIR, '-f', ninja_path, '-t', 'commands', target
    141     ])
    142 
    143     return extract_build_cmds(commands, os.path.basename(target))
    144 
    145 
    146 def setup_test_directory():
    147     """Prepares a device test directory for use by the shell user."""
    148     stdfs_test_data = os.path.join(
    149         THIS_DIR, 'test/std/input.output/filesystems/Inputs/static_test_env')
    150     device_dir = '/data/local/tmp/libcxx'
    151     dynamic_dir = posixpath.join(device_dir, 'dynamic_test_env')
    152     check_call(['adb', 'shell', 'rm', '-rf', device_dir])
    153     check_call(['adb', 'shell', 'mkdir', '-p', device_dir])
    154     check_call(['adb', 'shell', 'mkdir', '-p', dynamic_dir])
    155     check_call(['adb', 'push', '--sync', stdfs_test_data, device_dir])
    156     check_call(['adb', 'shell', 'chown', '-R', 'shell:shell', device_dir])
    157 
    158 
    159 def main():
    160     """Program entry point."""
    161     logging.basicConfig(level=logging.INFO)
    162 
    163     args, lit_args = ArgParser().parse_known_args()
    164     lit_path = os.path.join(ANDROID_DIR, 'external/llvm/utils/lit/lit.py')
    165     cc, cflags, ldflags = get_build_cmds(args.bitness, args.host)
    166 
    167     mode_str = 'host' if args.host else 'device'
    168     android_mode_arg = '--param=android_mode=' + mode_str
    169     cxx_under_test_arg = '--param=cxx_under_test=' + cc
    170     cxx_template_arg = '--param=cxx_template=' + ' '.join(cflags)
    171     link_template_arg = '--param=link_template=' + ' '.join(ldflags)
    172     site_cfg_path = os.path.join(THIS_DIR, 'test/lit.site.cfg')
    173     libcxx_site_cfg_arg = '--param=libcxx_site_config=' + site_cfg_path
    174     libcxxabi_site_cfg_arg = '--param=libcxxabi_site_config=' + site_cfg_path
    175     default_test_paths = [
    176         os.path.join(THIS_DIR, 'test'),
    177         os.path.join(ANDROID_DIR, 'external/libcxxabi/test')
    178     ]
    179 
    180     have_filter_args = False
    181     for arg in lit_args:
    182         # If the argument is a valid path with default_test_paths, it is a test
    183         # filter.
    184         real_path = os.path.realpath(arg)
    185         if not any(real_path.startswith(path) for path in default_test_paths):
    186             continue
    187         if not os.path.exists(real_path):
    188             continue
    189 
    190         have_filter_args = True
    191         break  # No need to keep scanning.
    192 
    193     if not args.host:
    194         setup_test_directory()
    195 
    196     lit_args = [
    197         '-sv', android_mode_arg, cxx_under_test_arg, cxx_template_arg,
    198         link_template_arg, libcxx_site_cfg_arg, libcxxabi_site_cfg_arg
    199     ] + lit_args
    200     cmd = ['python', lit_path] + lit_args
    201     if not have_filter_args:
    202         cmd += default_test_paths
    203     sys.exit(call(cmd))
    204 
    205 
    206 if __name__ == '__main__':
    207     main()
    208