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:
Dan Albert
2015-03-04 16:03:43 -08:00
parent 490d4a2aa6
commit 5d8e4e3581
4 changed files with 173 additions and 107 deletions

View 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

View 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

View File

@@ -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))

View File

@@ -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()