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++.""" 18from __future__ import print_function 19 20import argparse 21import logging 22import os 23import posixpath 24import sys 25 26THIS_DIR = os.path.dirname(os.path.realpath(__file__)) 27ANDROID_DIR = os.path.realpath(os.path.join(THIS_DIR, '../..')) 28 29 30def logger(): 31 """Returns the logger for the module.""" 32 return logging.getLogger(__name__) 33 34 35def 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 42def 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 49def 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 56class 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 65def 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 118def 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 146def 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 159def 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 206if __name__ == '__main__': 207 main() 208