Update Android's test configuration for new APIs.
The upstream test runner has changed enough that the way Android was shimmed into it was no longer compatible. This adapts our test runner to the new APIs for cross-compiling and remote execution. There's probably a fair amount of dead code in the Android test runners now (or at least some code that should be made dead). I'll clean it up in a later patch, but want to get us up and running for now. The NDK test runner will need to be updated as well. There aren't any continuous runs for that, so that will be fixed in a follow up as well. Change-Id: I1756f538aa6c7136ebd26d1e81c8299b87f0c6b2
This commit is contained in:
75
test/libcxx/android/compiler.py
Normal file
75
test/libcxx/android/compiler.py
Normal file
@@ -0,0 +1,75 @@
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import subprocess
|
||||
|
||||
import libcxx.compiler
|
||||
|
||||
|
||||
class AndroidCXXCompiler(libcxx.compiler.CXXCompiler):
|
||||
def __init__(self, cxx_under_test, cxx_template, link_template):
|
||||
super(AndroidCXXCompiler, self).__init__(cxx_under_test)
|
||||
self.cxx_template = cxx_template
|
||||
self.link_template = link_template
|
||||
self.build_top = os.getenv('ANDROID_BUILD_TOP')
|
||||
|
||||
def get_triple(self):
|
||||
if 'clang' in self.path:
|
||||
return self.get_clang_triple()
|
||||
else:
|
||||
return self.get_gcc_triple()
|
||||
|
||||
raise RuntimeError('Could not determine target triple.')
|
||||
|
||||
def get_clang_triple(self):
|
||||
match = re.search(r'-target\s+(\S+)', self.cxx_template)
|
||||
if match:
|
||||
return match.group(1)
|
||||
return None
|
||||
|
||||
def get_gcc_triple(self):
|
||||
proc = subprocess.Popen([self.path, '-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 compile(self, source_files, out=None, flags=None, env=None, cwd=None):
|
||||
flags = [] if flags is None else flags
|
||||
return super(AndroidCXXCompiler, self).compile(source_files, out, flags,
|
||||
env, self.build_top)
|
||||
|
||||
def link(self, source_files, out=None, flags=None, env=None, cwd=None):
|
||||
flags = [] if flags is None else flags
|
||||
return super(AndroidCXXCompiler, self).link(source_files, out, flags,
|
||||
env, self.build_top)
|
||||
|
||||
def compileCmd(self, source_files, out=None, flags=None):
|
||||
if out is None:
|
||||
raise RuntimeError('The Android compiler requires an out path.')
|
||||
|
||||
if isinstance(source_files, str):
|
||||
source_files = [source_files]
|
||||
cxx_args = self.cxx_template.replace('%OUT%', out)
|
||||
cxx_args = cxx_args.replace('%SOURCE%', ' '.join(source_files))
|
||||
return [self.path] + shlex.split(cxx_args)
|
||||
|
||||
def linkCmd(self, source_files, out=None, flags=None):
|
||||
if out is None:
|
||||
raise RuntimeError('The Android compiler requires an out path.')
|
||||
|
||||
if isinstance(source_files, str):
|
||||
source_files = [source_files]
|
||||
link_args = self.link_template.replace('%OUT%', out)
|
||||
link_args = link_args.replace('%SOURCE%', ' '.join(source_files))
|
||||
return [self.path] + shlex.split(link_args)
|
||||
|
||||
def _basicCmd(self, source_files, out, is_link=False, input_is_cxx=False):
|
||||
raise NotImplementedError()
|
||||
|
||||
def _initTypeAndVersion(self):
|
||||
pass
|
||||
55
test/libcxx/android/executors.py
Normal file
55
test/libcxx/android/executors.py
Normal file
@@ -0,0 +1,55 @@
|
||||
import time
|
||||
|
||||
import libcxx.test.executor
|
||||
|
||||
from libcxx.android import adb
|
||||
from lit.util import executeCommand # pylint: disable=import-error
|
||||
|
||||
|
||||
class AdbExecutor(libcxx.test.executor.RemoteExecutor):
|
||||
def __init__(self, serial=None):
|
||||
# TODO(danalbert): Should factor out the shared pieces of SSHExecutor
|
||||
# so we don't have this ugly parent constructor...
|
||||
super(AdbExecutor, self).__init__()
|
||||
self.serial = serial
|
||||
self.local_run = executeCommand
|
||||
|
||||
def _remote_temp(self, is_dir):
|
||||
dir_arg = '-d' if is_dir else ''
|
||||
cmd = 'mktemp -q {} /data/local/tmp/libcxx.XXXXXXXXXX'.format(dir_arg)
|
||||
temp_path, err, exitCode = self._execute_command_remote([cmd])
|
||||
temp_path = temp_path.strip()
|
||||
if exitCode != 0:
|
||||
raise RuntimeError(err)
|
||||
return temp_path
|
||||
|
||||
def _copy_in_file(self, src, dst): # pylint: disable=no-self-use
|
||||
adb.push(src, dst)
|
||||
|
||||
def _execute_command_remote(self, cmd, remote_work_dir='.', env=None):
|
||||
adb_cmd = ['adb', 'shell']
|
||||
if self.serial:
|
||||
adb_cmd.extend(['-s', self.serial])
|
||||
if env:
|
||||
env_cmd = ['env'] + ['%s=%s' % (k, v) for k, v in env.items()]
|
||||
else:
|
||||
env_cmd = []
|
||||
remote_cmd = ' '.join(env_cmd + cmd + ['; echo $?'])
|
||||
if remote_work_dir != '.':
|
||||
remote_cmd = 'cd {} && {}'.format(remote_work_dir, remote_cmd)
|
||||
adb_cmd.append(remote_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 = self.local_run(adb_cmd)
|
||||
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
|
||||
return out, err, exit_code
|
||||
@@ -1,19 +1,16 @@
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
import libcxx.test.config
|
||||
import libcxx.android.build
|
||||
import libcxx.android.compiler
|
||||
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()
|
||||
@@ -21,11 +18,19 @@ class Configuration(libcxx.test.config.Configuration):
|
||||
|
||||
self.configure_build_cmds()
|
||||
self.configure_cxx()
|
||||
self.configure_cxx_template()
|
||||
self.configure_link_template()
|
||||
self.configure_triple()
|
||||
self.configure_features()
|
||||
|
||||
def print_config_info(self):
|
||||
self.lit_config.note(
|
||||
'Using compiler: {}'.format(self.cxx.path))
|
||||
self.lit_config.note(
|
||||
'Using compile template: {}'.format(self.cxx.cxx_template))
|
||||
self.lit_config.note(
|
||||
'Using link template: {}'.format(self.cxx.link_template))
|
||||
self.lit_config.note('Using available_features: %s' %
|
||||
list(self.config.available_features))
|
||||
|
||||
def configure_build_cmds(self):
|
||||
os.chdir(self.config.android_root)
|
||||
self.build_cmds_dir = os.path.join(self.libcxx_src_root, 'buildcmds')
|
||||
@@ -36,64 +41,41 @@ class Configuration(libcxx.test.config.Configuration):
|
||||
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()
|
||||
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()
|
||||
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()
|
||||
link_template = open(link_template_file).read().strip()
|
||||
|
||||
self.cxx = libcxx.android.compiler.AndroidCXXCompiler(
|
||||
cxx_under_test, cxx_template, link_template)
|
||||
|
||||
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
|
||||
self.config.target_triple = self.cxx.get_triple()
|
||||
|
||||
def configure_features(self):
|
||||
self.config.available_features.add('long_tests')
|
||||
std_pattern = re.compile(r'-std=(c\+\+\d[0-9x-z])')
|
||||
match = std_pattern.search(self.cxx.cxx_template)
|
||||
if match:
|
||||
self.config.available_features.add(match.group(1))
|
||||
|
||||
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.cxx,
|
||||
self.libcxx_src_root,
|
||||
self.obj_root,
|
||||
self.cxx_template,
|
||||
self.link_template,
|
||||
self.libcxx_obj_root,
|
||||
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,
|
||||
self.cxx,
|
||||
self.libcxx_src_root,
|
||||
self.libcxx_obj_root,
|
||||
getattr(self.config, 'timeout', '60'))
|
||||
else:
|
||||
raise RuntimeError('Invalid android_mode: {}'.format(mode))
|
||||
|
||||
@@ -1,43 +1,25 @@
|
||||
import os
|
||||
import shlex
|
||||
import time
|
||||
|
||||
import lit.util # pylint: disable=import-error
|
||||
|
||||
from libcxx.android.executors import AdbExecutor
|
||||
from libcxx.test.executor import LocalExecutor, TimeoutExecutor
|
||||
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
|
||||
def __init__(self, cxx, libcxx_src_root, libcxx_obj_root, timeout,
|
||||
exec_env=None):
|
||||
self.cxx = cxx
|
||||
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
|
||||
self.executor = TimeoutExecutor(timeout, LocalExecutor())
|
||||
self.exec_env = {} if exec_env is None else exec_env
|
||||
|
||||
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):
|
||||
def _run(self, exec_path, _, 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
|
||||
@@ -56,19 +38,17 @@ class HostTestFormat(libcxx.test.format.LibcxxTestFormat):
|
||||
|
||||
|
||||
class TestFormat(HostTestFormat):
|
||||
def __init__(self, cxx_under_test, libcxx_src_root, libcxx_obj_root,
|
||||
cxx_template, link_template, device_dir, timeout,
|
||||
exec_env=None):
|
||||
def __init__(self, cxx, libcxx_src_root, libcxx_obj_root, device_dir,
|
||||
timeout, exec_env=None):
|
||||
HostTestFormat.__init__(
|
||||
self,
|
||||
cxx_under_test,
|
||||
cxx,
|
||||
libcxx_src_root,
|
||||
libcxx_obj_root,
|
||||
cxx_template,
|
||||
link_template,
|
||||
timeout)
|
||||
timeout,
|
||||
exec_env)
|
||||
self.device_dir = device_dir
|
||||
self.exec_env = {} if exec_env is None else exec_env
|
||||
self.executor = TimeoutExecutor(timeout, AdbExecutor())
|
||||
|
||||
def _working_directory(self, file_name):
|
||||
return os.path.join(self.device_dir, file_name)
|
||||
@@ -111,31 +91,5 @@ class TestFormat(HostTestFormat):
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def _run(self, exec_path, lit_config, in_dir=None):
|
||||
exec_file = os.path.basename(exec_path)
|
||||
env_string = ' '.join(['='.join([k, v]) for k, v in
|
||||
self.exec_env.items()])
|
||||
shell_cmd = 'cd {work_dir} && {env_string} {cmd}; echo $?'.format(
|
||||
work_dir=self._working_directory(exec_file),
|
||||
env_string=env_string,
|
||||
cmd=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 _run(self, exec_path, _, in_dir=None):
|
||||
raise NotImplementedError()
|
||||
|
||||
Reference in New Issue
Block a user