普通文本  |  208行  |  7.24 KB

#!/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(['make', '-C', ANDROID_DIR, 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()