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([ 135 'bash', 136 os.path.join(ANDROID_DIR, 'build/soong/soong_ui.bash'), '--make-mode', 137 target 138 ]) 139 140 ninja_path = os.path.join( 141 out_dir, 'combined-' + os.getenv('TARGET_PRODUCT') + '.ninja') 142 commands = check_output([ 143 os.path.join(ANDROID_DIR, 'prebuilts/build-tools/linux-x86/bin/ninja'), 144 '-C', ANDROID_DIR, '-f', ninja_path, '-t', 'commands', target 145 ]) 146 147 return extract_build_cmds(commands, os.path.basename(target)) 148 149 150def setup_test_directory(): 151 """Prepares a device test directory for use by the shell user.""" 152 stdfs_test_data = os.path.join( 153 THIS_DIR, 'test/std/input.output/filesystems/Inputs/static_test_env') 154 device_dir = '/data/local/tmp/libcxx' 155 dynamic_dir = posixpath.join(device_dir, 'dynamic_test_env') 156 check_call(['adb', 'shell', 'rm', '-rf', device_dir]) 157 check_call(['adb', 'shell', 'mkdir', '-p', device_dir]) 158 check_call(['adb', 'shell', 'mkdir', '-p', dynamic_dir]) 159 check_call(['adb', 'push', '--sync', stdfs_test_data, device_dir]) 160 check_call(['adb', 'shell', 'chown', '-R', 'shell:shell', device_dir]) 161 162 163def main(): 164 """Program entry point.""" 165 logging.basicConfig(level=logging.INFO) 166 167 args, lit_args = ArgParser().parse_known_args() 168 lit_path = os.path.join(ANDROID_DIR, 'external/llvm/utils/lit/lit.py') 169 cc, cflags, ldflags = get_build_cmds(args.bitness, args.host) 170 171 mode_str = 'host' if args.host else 'device' 172 android_mode_arg = '--param=android_mode=' + mode_str 173 cxx_under_test_arg = '--param=cxx_under_test=' + cc 174 cxx_template_arg = '--param=cxx_template=' + ' '.join(cflags) 175 link_template_arg = '--param=link_template=' + ' '.join(ldflags) 176 site_cfg_path = os.path.join(THIS_DIR, 'test/lit.site.cfg') 177 libcxx_site_cfg_arg = '--param=libcxx_site_config=' + site_cfg_path 178 libcxxabi_site_cfg_arg = '--param=libcxxabi_site_config=' + site_cfg_path 179 default_test_paths = [ 180 os.path.join(THIS_DIR, 'test'), 181 os.path.join(ANDROID_DIR, 'external/libcxxabi/test') 182 ] 183 184 have_filter_args = False 185 for arg in lit_args: 186 # If the argument is a valid path with default_test_paths, it is a test 187 # filter. 188 real_path = os.path.realpath(arg) 189 if not any(real_path.startswith(path) for path in default_test_paths): 190 continue 191 if not os.path.exists(real_path): 192 continue 193 194 have_filter_args = True 195 break # No need to keep scanning. 196 197 if not args.host: 198 setup_test_directory() 199 200 lit_args = [ 201 '-sv', android_mode_arg, cxx_under_test_arg, cxx_template_arg, 202 link_template_arg, libcxx_site_cfg_arg, libcxxabi_site_cfg_arg 203 ] + lit_args 204 cmd = ['python', lit_path] + lit_args 205 if not have_filter_args: 206 cmd += default_test_paths 207 sys.exit(call(cmd)) 208 209 210if __name__ == '__main__': 211 main() 212