diff --git a/test/libcxx/android/__init__.py b/test/libcxx/android/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/test/libcxx/android/adb.py b/test/libcxx/android/adb.py new file mode 100644 index 000000000..43bd2b52d --- /dev/null +++ b/test/libcxx/android/adb.py @@ -0,0 +1,24 @@ +import lit.util # pylint: disable=import-error + + +class AdbError(RuntimeError): + def __init__(self, cmd, out, err, exit_code): + super(AdbError, self).__init__(err) + self.cmd = cmd + self.out = out + self.err = err + self.exit_code = exit_code + + +def mkdir(path): + cmd = ['adb', 'shell', 'mkdir', path] + out, err, exit_code = lit.util.executeCommand(cmd) + if exit_code != 0: + raise AdbError(cmd, out, err, exit_code) + + +def push(src, dst): + cmd = ['adb', 'push', src, dst] + out, err, exit_code = lit.util.executeCommand(cmd) + if exit_code != 0: + raise AdbError(cmd, out, err, exit_code) diff --git a/test/libcxx/android/build.py b/test/libcxx/android/build.py new file mode 100644 index 000000000..56c7649f2 --- /dev/null +++ b/test/libcxx/android/build.py @@ -0,0 +1,13 @@ +import os +import subprocess + + +def mm(path, android_build_top): + env = os.environ + env['ONE_SHOT_MAKEFILE'] = os.path.join(path, 'Android.mk') + + cmd = [ + 'make', '-C', android_build_top, '-f', 'build/core/main.mk', + 'all_modules', '-B' + ] + return not subprocess.Popen(cmd, stdout=None, stderr=None, env=env).wait() diff --git a/test/libcxx/android/test/__init__.py b/test/libcxx/android/test/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/test/libcxx/android/test/config.py b/test/libcxx/android/test/config.py new file mode 100644 index 000000000..e696399f2 --- /dev/null +++ b/test/libcxx/android/test/config.py @@ -0,0 +1,99 @@ +import os +import re +import subprocess + +import libcxx.test.config +import libcxx.android.build +import libcxx.android.test.format + + +class Configuration(libcxx.test.config.Configuration): + def __init__(self, lit_config, config): + super(Configuration, self).__init__(lit_config, config) + self.cxx_under_test = None + self.build_cmds_dir = None + self.cxx_template = None + self.link_template = None + + def configure(self): + self.configure_src_root() + self.configure_obj_root() + + self.configure_build_cmds() + self.configure_cxx() + self.configure_cxx_template() + self.configure_link_template() + self.configure_triple() + self.configure_features() + + def configure_build_cmds(self): + os.chdir(self.config.android_root) + self.build_cmds_dir = os.path.join(self.src_root, 'buildcmds') + if not libcxx.android.build.mm(self.build_cmds_dir, + self.config.android_root): + raise RuntimeError('Could not generate build commands.') + + def configure_cxx(self): + cxx_under_test_file = os.path.join(self.build_cmds_dir, + 'cxx_under_test') + self.cxx_under_test = open(cxx_under_test_file).read().strip() + + def configure_cxx_template(self): + cxx_template_file = os.path.join(self.build_cmds_dir, 'cxx.cmds') + self.cxx_template = open(cxx_template_file).read().strip() + + def configure_link_template(self): + link_template_file = os.path.join(self.build_cmds_dir, 'link.cmds') + self.link_template = open(link_template_file).read().strip() + + def configure_triple(self): + if 'clang' in self.cxx_under_test: + triple = self.configure_clang_triple() + else: + triple = self.configure_gcc_triple() + + if not triple: + raise RuntimeError('Could not determine target triple.') + self.config.target_triple = triple + + def configure_clang_triple(self): + match = re.search(r'-target\s+(\S+)', self.cxx_template) + if match: + return match.group(1) + return None + + def configure_gcc_triple(self): + proc = subprocess.Popen([self.cxx_under_test, '-v'], + stderr=subprocess.PIPE) + _, stderr = proc.communicate() + for line in stderr.split('\n'): + print 'Checking {}'.format(line) + match = re.search(r'^Target: (.+)$', line) + if match: + return match.group(1) + return None + + def configure_features(self): + self.config.available_features.add('long_tests') + + def get_test_format(self): + mode = self.lit_config.params.get('android_mode', 'device') + if mode == 'device': + return libcxx.android.test.format.TestFormat( + self.cxx_under_test, + self.src_root, + self.obj_root, + self.cxx_template, + self.link_template, + getattr(self.config, 'device_dir', '/data/local/tmp/'), + getattr(self.config, 'timeout', '60')) + elif mode == 'host': + return libcxx.android.test.format.HostTestFormat( + self.cxx_under_test, + self.src_root, + self.obj_root, + self.cxx_template, + self.link_template, + getattr(self.config, 'timeout', '60')) + else: + raise RuntimeError('Invalid android_mode: {}'.format(mode)) diff --git a/test/libcxx/android/test/format.py b/test/libcxx/android/test/format.py new file mode 100644 index 000000000..5897c2e1c --- /dev/null +++ b/test/libcxx/android/test/format.py @@ -0,0 +1,133 @@ +import os +import shlex +import time + +import lit.util # pylint: disable=import-error + +import libcxx.test.format +import libcxx.android.adb as adb + + +class HostTestFormat(libcxx.test.format.LibcxxTestFormat): + # pylint: disable=super-init-not-called + def __init__(self, cxx_under_test, libcxx_src_root, libcxx_obj_root, + cxx_template, link_template, timeout): + self.cxx_under_test = cxx_under_test + self.libcxx_src_root = libcxx_src_root + self.libcxx_obj_root = libcxx_obj_root + self.cxx_template = cxx_template + self.link_template = link_template + self.timeout = timeout + self.use_verify_for_fail = False + + def _compile(self, output_path, source_path, use_verify=False): + if use_verify: + raise NotImplementedError( + 'AndroidConfiguration does not support use_verify mode.') + cxx_args = self.cxx_template.replace('%OUT%', output_path) + cxx_args = cxx_args.replace('%SOURCE%', source_path) + cmd = [self.cxx_under_test] + shlex.split(cxx_args) + out, err, exit_code = lit.util.executeCommand(cmd) + return cmd, out, err, exit_code + + def _link(self, exec_path, object_path): + link_args = self.link_template.replace('%OUT%', exec_path) + link_args = link_args.replace('%SOURCE%', object_path) + cmd = [self.cxx_under_test] + shlex.split(link_args) + out, err, exit_code = lit.util.executeCommand(cmd) + return cmd, out, err, exit_code + + def _run(self, exec_path, lit_config, in_dir=None): + cmd = [exec_path] + # We need to use LD_LIBRARY_PATH because the build system's rpath is + # relative, which won't work since we're running from /tmp. We can + # either scan `cxx_under_test`/`link_template` to determine whether + # we're 32-bit or 64-bit, scan testconfig.mk, or just add both + # directories and let the linker sort it out. I'm choosing the lazy + # option. + outdir = os.getenv('ANDROID_HOST_OUT') + libpath = os.pathsep.join([ + os.path.join(outdir, 'lib'), + os.path.join(outdir, 'lib64'), + ]) + out, err, rc = lit.util.executeCommand( + cmd, cwd=in_dir, env={'LD_LIBRARY_PATH': libpath}) + return self._make_report(cmd, out, err, rc) + + +class TestFormat(HostTestFormat): + def __init__(self, cxx_under_test, libcxx_src_root, libcxx_obj_root, + cxx_template, link_template, device_dir, timeout): + HostTestFormat.__init__( + self, + cxx_under_test, + libcxx_src_root, + libcxx_obj_root, + cxx_template, + link_template, + timeout) + self.device_dir = device_dir + + def _working_directory(self, file_name): + return os.path.join(self.device_dir, file_name) + + def _wd_path(self, test_name, file_name): + return os.path.join(self._working_directory(test_name), file_name) + + def _build(self, exec_path, source_path, compile_only=False, + use_verify=False): + # pylint: disable=protected-access + cmd, report, rc = libcxx.test.format.LibcxxTestFormat._build( + self, exec_path, source_path, compile_only, use_verify) + if rc != 0: + return cmd, report, rc + + try: + exec_file = os.path.basename(exec_path) + + adb.mkdir(self._working_directory(exec_file)) + adb.push(exec_path, self._wd_path(exec_file, exec_file)) + + # Push any .dat files in the same directory as the source to the + # working directory. + src_dir = os.path.dirname(source_path) + data_files = [f for f in os.listdir(src_dir) if f.endswith('.dat')] + for data_file in data_files: + df_path = os.path.join(src_dir, data_file) + df_dev_path = self._wd_path(exec_file, data_file) + adb.push(df_path, df_dev_path) + return cmd, report, rc + except adb.AdbError as ex: + return self._make_report(ex.cmd, ex.out, ex.err, ex.exit_code) + + def _clean(self, exec_path): + exec_file = os.path.basename(exec_path) + cmd = ['adb', 'shell', 'rm', '-rf', self._working_directory(exec_file)] + lit.util.executeCommand(cmd) + os.remove(exec_path) + + def _run(self, exec_path, lit_config, in_dir=None): + exec_file = os.path.basename(exec_path) + shell_cmd = 'cd {} && {}; echo $?'.format( + self._working_directory(exec_file), + self._wd_path(exec_file, exec_file)) + cmd = ['timeout', self.timeout, 'adb', 'shell', shell_cmd] + + # Tests will commonly fail with ETXTBSY. Possibly related to this bug: + # https://code.google.com/p/android/issues/detail?id=65857. Work around + # it by just waiting a second and then retrying. + for _ in range(10): + out, err, exit_code = lit.util.executeCommand(cmd) + if exit_code == 0: + if 'Text file busy' in out: + time.sleep(1) + else: + out = out.strip().split('\r\n') + status_line = out[-1:][0] + out = '\n'.join(out[:-1]) + exit_code = int(status_line) + break + else: + err += '\nTimed out after {} seconds'.format(self.timeout) + break + return self._make_report(cmd, out, err, exit_code) diff --git a/test/lit.cfg b/test/lit.cfg index d7f54570b..6f536386e 100644 --- a/test/lit.cfg +++ b/test/lit.cfg @@ -6,251 +6,6 @@ import site site.addsitedir(os.path.dirname(__file__)) -class AndroidHostLibcxxTestFormat(LibcxxTestFormat): - def __init__(self, cxx_under_test, libcxx_src_root, libcxx_obj_root, - cxx_template, link_template, timeout): - self.cxx_under_test = cxx_under_test - self.libcxx_src_root = libcxx_src_root - self.libcxx_obj_root = libcxx_obj_root - self.cxx_template = cxx_template - self.link_template = link_template - self.timeout = timeout - self.use_verify_for_fail = False - - def _compile(self, output_path, source_path, use_verify=False): - if use_verify: - raise NotImplementedError( - 'AndroidConfiguration does not support use_verify mode.') - cxx_args = self.cxx_template.replace('%OUT%', output_path) - cxx_args = cxx_args.replace('%SOURCE%', source_path) - cmd = [self.cxx_under_test] + shlex.split(cxx_args) - out, err, exit_code = lit.util.executeCommand(cmd) - return cmd, out, err, exit_code - - def _link(self, exec_path, object_path): - link_args = self.link_template.replace('%OUT%', exec_path) - link_args = link_args.replace('%SOURCE%', object_path) - cmd = [self.cxx_under_test] + shlex.split(link_args) - out, err, exit_code = lit.util.executeCommand(cmd) - return cmd, out, err, exit_code - - def _run(self, exec_path, lit_config, in_dir=None): - cmd = [exec_path] - # We need to use LD_LIBRARY_PATH because the build system's rpath is - # relative, which won't work since we're running from /tmp. We can - # either scan `cxx_under_test`/`link_template` to determine whether - # we're 32-bit or 64-bit, scan testconfig.mk, or just add both - # directories and let the linker sort it out. I'm choosing the lazy - # option. - outdir = os.getenv('ANDROID_HOST_OUT') - libpath = os.pathsep.join([ - os.path.join(outdir, 'lib'), - os.path.join(outdir, 'lib64'), - ]) - out, err, rc = lit.util.executeCommand( - cmd, cwd=in_dir, env={'LD_LIBRARY_PATH': libpath}) - return self._make_report(cmd, out, err, rc) - - -class AdbError(RuntimeError): - def __init__(self, cmd, out, err, exit_code): - self.cmd = cmd - self.out = out - self.err = err - self.exit_code = exit_code - - -class AndroidLibcxxTestFormat(AndroidHostLibcxxTestFormat): - def __init__(self, cxx_under_test, libcxx_src_root, libcxx_obj_root, - cxx_template, link_template, device_dir, timeout): - AndroidHostLibcxxTestFormat.__init__( - self, - cxx_under_test, - libcxx_src_root, - libcxx_obj_root, - cxx_template, - link_template, - timeout) - self.device_dir = device_dir - - def _working_directory(self, file_name): - return os.path.join(self.device_dir, file_name) - - def _wd_path(self, test_name, file_name): - return os.path.join(self._working_directory(test_name), file_name) - - def _adb_mkdir(self, path): - cmd = ['adb', 'shell', 'mkdir', path] - out, err, exit_code = lit.util.executeCommand(cmd) - if exit_code != 0: - raise AdbError(cmd, out, err, exit_code) - - def _adb_push(self, src, dst): - cmd = ['adb', 'push', src, dst] - out, err, exit_code = lit.util.executeCommand(cmd) - if exit_code != 0: - raise AdbError(cmd, out, err, exit_code) - - def _build(self, exec_path, source_path, compile_only=False, - use_verify=False): - cmd, report, rc = LibcxxTestFormat._build( - self, exec_path, source_path, compile_only, use_verify) - if rc != 0: - return cmd, report, rc - - try: - exec_file = os.path.basename(exec_path) - - self._adb_mkdir(self._working_directory(exec_file)) - self._adb_push(exec_path, self._wd_path(exec_file, exec_file)) - - # Push any .dat files in the same directory as the source to the - # working directory. - src_dir = os.path.dirname(source_path) - data_files = [f for f in os.listdir(src_dir) if f.endswith('.dat')] - for data_file in data_files: - df_path = os.path.join(src_dir, data_file) - df_dev_path = self._wd_path(exec_file, data_file) - self._adb_push(df_path, df_dev_path) - return cmd, report, rc - except AdbError as ex: - return self._make_report(ex.cmd, ex.out, ex.err, ex.exit_code) - - def _clean(self, exec_path): - exec_file = os.path.basename(exec_path) - cmd = ['adb', 'shell', 'rm', '-rf', self._working_directory(exec_file)] - lit.util.executeCommand(cmd) - os.remove(exec_path) - - def _run(self, exec_path, lit_config, in_dir=None): - exec_file = os.path.basename(exec_path) - shell_cmd = 'cd {} && {}; echo $?'.format( - self._working_directory(exec_file), - self._wd_path(exec_file, exec_file)) - cmd = ['timeout', self.timeout, 'adb', 'shell', shell_cmd] - - # Tests will commonly fail with ETXTBSY. Possibly related to this bug: - # https://code.google.com/p/android/issues/detail?id=65857. Work around - # it by just waiting a second and then retrying. - for _ in range(10): - out, err, exit_code = lit.util.executeCommand(cmd) - if exit_code == 0: - if 'Text file busy' in out: - time.sleep(1) - else: - out = out.strip().split('\r\n') - status_line = out[-1:][0] - out = '\n'.join(out[:-1]) - exit_code = int(status_line) - break - else: - err += '\nTimed out after {} seconds'.format(self.timeout) - break - return self._make_report(cmd, out, err, exit_code) - - -def mm(path, android_build_top): - env = os.environ - env['ONE_SHOT_MAKEFILE'] = os.path.join(path, 'Android.mk') - - cmd = [ - 'make', '-C', android_build_top, '-f', 'build/core/main.mk', - 'all_modules', '-B' - ] - return not subprocess.Popen(cmd, stdout=None, stderr=None, env=env).wait() - - -class AndroidConfiguration(Configuration): - def __init__(self, lit_config, config): - super(AndroidConfiguration, self).__init__(lit_config, config) - self.cxx_under_test = None - self.build_cmds_dir = None - self.cxx_template = None - self.link_template = None - - def configure(self): - self.configure_src_root() - self.configure_obj_root() - - self.configure_build_cmds() - self.configure_cxx() - self.configure_cxx_template() - self.configure_link_template() - self.configure_triple() - self.configure_features() - - def configure_build_cmds(self): - os.chdir(self.config.android_root) - self.build_cmds_dir = os.path.join(self.src_root, 'buildcmds') - if not mm(self.build_cmds_dir, self.config.android_root): - raise RuntimeError('Could not generate build commands.') - - def configure_cxx(self): - cxx_under_test_file = os.path.join(self.build_cmds_dir, - 'cxx_under_test') - self.cxx_under_test = open(cxx_under_test_file).read().strip() - - def configure_cxx_template(self): - cxx_template_file = os.path.join(self.build_cmds_dir, 'cxx.cmds') - self.cxx_template = open(cxx_template_file).read().strip() - - def configure_link_template(self): - link_template_file = os.path.join(self.build_cmds_dir, 'link.cmds') - self.link_template = open(link_template_file).read().strip() - - def configure_triple(self): - if 'clang' in self.cxx_under_test: - triple = self.configure_clang_triple() - else: - triple = self.configure_gcc_triple() - - if not triple: - raise RuntimeError('Could not determine target triple.') - self.config.target_triple = triple - - def configure_clang_triple(self): - match = re.search(r'-target\s+(\S+)', self.cxx_template) - if match: - return match.group(1) - return None - - def configure_gcc_triple(self): - proc = subprocess.Popen([self.cxx_under_test, '-v'], - stderr=subprocess.PIPE) - _, stderr = proc.communicate() - for line in stderr.split('\n'): - print 'Checking {}'.format(line) - match = re.search(r'^Target: (.+)$', line) - if match: - return match.group(1) - return None - - def configure_features(self): - self.config.available_features.add('long_tests') - - def get_test_format(self): - mode = self.lit_config.params.get('android_mode', 'device') - if mode == 'device': - return AndroidLibcxxTestFormat( - self.cxx_under_test, - self.src_root, - self.obj_root, - self.cxx_template, - self.link_template, - getattr(self.config, 'device_dir', '/data/local/tmp/'), - getattr(self.config, 'timeout', '60')) - elif mode == 'host': - return AndroidHostLibcxxTestFormat( - self.cxx_under_test, - self.src_root, - self.obj_root, - self.cxx_template, - self.link_template, - getattr(self.config, 'timeout', '60')) - else: - raise RuntimeError('Invalid android_mode: {}'.format(mode)) - - # Tell pylint that we know config and lit_config exist somewhere. if 'PYLINT_IMPORT' in os.environ: config = object() diff --git a/test/lit.site.cfg b/test/lit.site.cfg index 27cb5abfa..c5c929b9c 100644 --- a/test/lit.site.cfg +++ b/test/lit.site.cfg @@ -1,6 +1,6 @@ import os -config.configuration_variant = 'Android' +config.configuration_variant = 'libcxx.android' config.android_root = os.getenv('ANDROID_BUILD_TOP') config.libcxx_src_root = os.path.join(config.android_root, 'external/libcxx') config.libcxx_obj_root = os.getenv('ANDROID_PRODUCT_OUT')