#!/usr/bin/env python # # Copyright (C) 2015 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # """Runs the libc++ tests against the platform libc++.""" from __future__ import print_function import argparse import logging import os import posixpath import sys THIS_DIR = os.path.dirname(os.path.realpath(__file__)) ANDROID_DIR = os.path.realpath(os.path.join(THIS_DIR, '../..')) def logger(): """Returns the logger for the module.""" return logging.getLogger(__name__) def call(cmd, *args, **kwargs): """subprocess.call with logging.""" import subprocess logger().info('call %s', ' '.join(cmd)) return subprocess.call(cmd, *args, **kwargs) def check_call(cmd, *args, **kwargs): """subprocess.check_call with logging.""" import subprocess logger().info('check_call %s', ' '.join(cmd)) return subprocess.check_call(cmd, *args, **kwargs) def check_output(cmd, *args, **kwargs): """subprocess.check_output with logging.""" import subprocess logger().info('check_output %s', ' '.join(cmd)) return subprocess.check_output(cmd, *args, **kwargs) class ArgParser(argparse.ArgumentParser): """Parses command line arguments.""" def __init__(self): super(ArgParser, self).__init__() self.add_argument('--bitness', choices=(32, 64), type=int, default=32) self.add_argument('--host', action='store_true') def extract_build_cmds(commands, exe_name): """Extracts build command information from `ninja -t commands` output. Args: commands: String containing the output of `ninja -t commands` for the libcxx_test_template. exe_name: The basename of the built executable. Returns: Tuple of (compiler, compiler_flags, linker_flags). """ cc = None cflags = None ldflags = None template_name = 'external/libcxx/libcxx_test_template.cpp' for cmd in commands.splitlines(): cmd_args = cmd.split() if cc is None and template_name in cmd_args: for i, arg in enumerate(cmd_args): if arg == '-o': cmd_args[i + 1] = '%OUT%' elif arg == template_name: cmd_args[i] = '%SOURCE%' # Drop dependency tracking args since they can cause file # not found errors at test time. if arg == '-MD': cmd_args[i] = '' if arg == '-MF': cmd_args[i] = '' cmd_args[i + 1] = '' if cmd_args[0] == 'PWD=/proc/self/cwd': cmd_args = cmd_args[1:] if cmd_args[0].endswith('gomacc'): cmd_args = cmd_args[1:] cc = cmd_args[0] cflags = cmd_args[1:] if ldflags is None: is_ld = False for i, arg in enumerate(cmd_args): # Here we assume that the rspfile contains the path to the # object file and nothing else. if arg.startswith('@'): cmd_args[i] = '%SOURCE%' if arg == '-o' and cmd_args[i + 1].endswith(exe_name): cmd_args[i + 1] = '%OUT%' is_ld = True if is_ld: ldflags = cmd_args[1:] return cc, cflags, ldflags def get_build_cmds(bitness, host): """Use ninja -t commands to find the build commands for an executable.""" out_dir = os.getenv('OUT_DIR', os.path.join(ANDROID_DIR, 'out')) product_out = os.getenv('ANDROID_PRODUCT_OUT') if host: rel_out_dir = os.path.relpath( os.path.join(out_dir, 'soong/host/linux-x86/bin'), ANDROID_DIR) target = os.path.join(rel_out_dir, 'libcxx_test_template64') else: exe_name = 'libcxx_test_template' + str(bitness) rel_out_dir = os.path.relpath(product_out, ANDROID_DIR) target = os.path.join(rel_out_dir, 'system/bin', exe_name) # Generate $OUT_DIR/combined-$TARGET_PRODUCT.ninja and build the # template target's dependencies. check_call([ 'bash', os.path.join(ANDROID_DIR, 'build/soong/soong_ui.bash'), '--make-mode', target ]) ninja_path = os.path.join( out_dir, 'combined-' + os.getenv('TARGET_PRODUCT') + '.ninja') commands = check_output([ os.path.join(ANDROID_DIR, 'prebuilts/build-tools/linux-x86/bin/ninja'), '-C', ANDROID_DIR, '-f', ninja_path, '-t', 'commands', target ]) return extract_build_cmds(commands, os.path.basename(target)) def setup_test_directory(): """Prepares a device test directory for use by the shell user.""" stdfs_test_data = os.path.join( THIS_DIR, 'test/std/input.output/filesystems/Inputs/static_test_env') device_dir = '/data/local/tmp/libcxx' dynamic_dir = posixpath.join(device_dir, 'dynamic_test_env') check_call(['adb', 'shell', 'rm', '-rf', device_dir]) check_call(['adb', 'shell', 'mkdir', '-p', device_dir]) check_call(['adb', 'shell', 'mkdir', '-p', dynamic_dir]) check_call(['adb', 'push', '--sync', stdfs_test_data, device_dir]) check_call(['adb', 'shell', 'chown', '-R', 'shell:shell', device_dir]) def main(): """Program entry point.""" logging.basicConfig(level=logging.INFO) args, lit_args = ArgParser().parse_known_args() lit_path = os.path.join(ANDROID_DIR, 'external/llvm/utils/lit/lit.py') cc, cflags, ldflags = get_build_cmds(args.bitness, args.host) mode_str = 'host' if args.host else 'device' android_mode_arg = '--param=android_mode=' + mode_str cxx_under_test_arg = '--param=cxx_under_test=' + cc cxx_template_arg = '--param=cxx_template=' + ' '.join(cflags) link_template_arg = '--param=link_template=' + ' '.join(ldflags) site_cfg_path = os.path.join(THIS_DIR, 'test/lit.site.cfg') libcxx_site_cfg_arg = '--param=libcxx_site_config=' + site_cfg_path libcxxabi_site_cfg_arg = '--param=libcxxabi_site_config=' + site_cfg_path default_test_paths = [ os.path.join(THIS_DIR, 'test'), os.path.join(ANDROID_DIR, 'external/libcxxabi/test') ] have_filter_args = False for arg in lit_args: # If the argument is a valid path with default_test_paths, it is a test # filter. real_path = os.path.realpath(arg) if not any(real_path.startswith(path) for path in default_test_paths): continue if not os.path.exists(real_path): continue have_filter_args = True break # No need to keep scanning. if not args.host: setup_test_directory() lit_args = [ '-sv', android_mode_arg, cxx_under_test_arg, cxx_template_arg, link_template_arg, libcxx_site_cfg_arg, libcxxabi_site_cfg_arg ] + lit_args cmd = ['python', lit_path] + lit_args if not have_filter_args: cmd += default_test_paths sys.exit(call(cmd)) if __name__ == '__main__': main()