android: Run tests by running run_tests.py on the build host

Bug: crashpad:30
Change-Id: Ie432c58c4a2505b6434861276512a5011fd285d4
Reviewed-on: https://chromium-review.googlesource.com/811891
Commit-Queue: Mark Mentovai <mark@chromium.org>
Reviewed-by: Scott Graham <scottmg@chromium.org>
This commit is contained in:
Mark Mentovai 2017-12-07 16:57:46 -05:00 committed by Commit Bot
parent 914b0d6755
commit 659420bc7d
2 changed files with 148 additions and 38 deletions

View File

@ -19,6 +19,8 @@ from __future__ import print_function
import os
import pipes
import posixpath
import re
import subprocess
import sys
import uuid
@ -28,18 +30,120 @@ CRASHPAD_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)),
IS_WINDOWS_HOST = sys.platform.startswith('win')
def _GetFuchsiaSDKRoot():
arch = 'mac-amd64' if sys.platform == 'darwin' else 'linux-amd64'
return os.path.join(CRASHPAD_DIR, 'third_party', 'fuchsia', 'sdk', arch)
def _BinaryDirTargetOS(binary_dir):
"""Returns the apparent target OS of binary_dir, or None if none appear to be
explicitly specified."""
def _BinaryDirLooksLikeFuchsiaBuild(binary_dir):
"""Checks whether the provided output directory targets Fuchsia."""
# Look for a GN “target_os”.
popen = subprocess.Popen(
['gn', 'args', binary_dir, '--list=target_os', '--short'],
shell=IS_WINDOWS_HOST, stdout=subprocess.PIPE, stderr=open(os.devnull))
value = popen.communicate()[0]
return popen.returncode == 0 and 'target_os = "fuchsia"' in value
if popen.returncode == 0:
match = re.match('target_os = "(.*)"$', value.decode('utf-8'))
if match:
return match.group(1)
# For GYP with Ninja, look for the appearance of “linux-android” in the path
# to ar. This path is configured by gyp_crashpad_android.py.
try:
with open(os.path.join(binary_dir, 'build.ninja')) as build_ninja_file:
build_ninja_content = build_ninja_file.read()
match = re.search('^ar = .+-linux-android(eabi)?-ar$',
build_ninja_content,
re.MULTILINE)
if match:
return 'android'
except FileNotFoundError:
# Ninja may not be in use. Assume the best.
pass
return None
def _RunOnAndroidTarget(binary_dir, test, android_device):
local_test_path = os.path.join(binary_dir, test)
MAYBE_UNSUPPORTED_TESTS = (
'crashpad_client_test',
'crashpad_handler_test',
'crashpad_minidump_test',
'crashpad_snapshot_test',
)
if not os.path.exists(local_test_path) and test in MAYBE_UNSUPPORTED_TESTS:
print(test, 'is not present and may not be supported, skipping')
return
device_temp_dir = subprocess.check_output(
['adb', '-s', android_device, 'shell',
'mktemp', '-d', '/data/local/tmp/%s.XXXXXXXX' % test],
shell=IS_WINDOWS_HOST).decode('utf-8').rstrip()
try:
# Specify test dependencies that must be pushed to the device. This could be
# determined automatically in a GN build, following the example used for
# Fuchsia. Since nothing like that exists for GYP, hard-code it for
# supported tests.
test_build_artifacts = [test]
test_data = ['test/test_paths_test_data_root.txt']
if test == 'crashpad_test_test':
test_build_artifacts.append(
'crashpad_test_test_multiprocess_exec_test_child')
elif test == 'crashpad_util_test':
test_data.append('util/net/testdata/')
def _adb(*args):
# Flush all of this scripts own buffered stdout output before running
# adb, which will likely produce its own output on stdout.
sys.stdout.flush()
adb_command = ['adb', '-s', android_device]
adb_command.extend(args)
subprocess.check_call(adb_command, shell=IS_WINDOWS_HOST)
# Establish the directory structure on the device.
device_out_dir = posixpath.join(device_temp_dir, 'out')
device_mkdirs = [device_out_dir]
for source_path in test_data:
# A trailing slash could reasonably mean to copy an entire directory, but
# will interfere with whats needed from the path split. All parent
# directories of any source_path need to be be represented in
# device_mkdirs, but its important that no source_path itself wind up in
# device_mkdirs, even if source_path names a directory, because that would
# cause the “adb push” of the directory below to behave incorrectly.
if source_path.endswith(posixpath.sep):
source_path = source_path[:-1]
device_source_path = posixpath.join(device_temp_dir, source_path)
device_mkdir = posixpath.split(device_source_path)[0]
if device_mkdir not in device_mkdirs:
device_mkdirs.append(device_mkdir)
adb_mkdir_command = ['shell', 'mkdir', '-p']
adb_mkdir_command.extend(device_mkdirs)
_adb(*adb_mkdir_command)
# Push the test binary and any other build output to the device.
adb_push_command = ['push']
for artifact in test_build_artifacts:
adb_push_command.append(os.path.join(binary_dir, artifact))
adb_push_command.append(device_out_dir)
_adb(*adb_push_command)
# Push test data to the device.
for source_path in test_data:
_adb('push',
os.path.join(CRASHPAD_DIR, source_path),
posixpath.join(device_temp_dir, source_path))
# Run the test on the device.
_adb('shell', 'env', 'CRASHPAD_TEST_DATA_ROOT=' + device_temp_dir,
posixpath.join(device_out_dir, test))
finally:
_adb('shell', 'rm', '-rf', device_temp_dir)
def _GetFuchsiaSDKRoot():
arch = 'mac-amd64' if sys.platform == 'darwin' else 'linux-amd64'
return os.path.join(CRASHPAD_DIR, 'third_party', 'fuchsia', 'sdk', arch)
def _GenerateFuchsiaRuntimeDepsFiles(binary_dir, tests):
@ -170,7 +274,9 @@ def main(args):
if os.path.isdir(binary_dir_32):
os.environ['CRASHPAD_TEST_32_BIT_OUTPUT'] = binary_dir_32
is_fuchsia = _BinaryDirLooksLikeFuchsiaBuild(binary_dir)
target_os = _BinaryDirTargetOS(binary_dir)
is_android = target_os == 'android'
is_fuchsia = target_os == 'fuchsia'
tests = [
'crashpad_client_test',
@ -181,7 +287,26 @@ def main(args):
'crashpad_util_test',
]
if is_fuchsia:
if is_android:
android_device = os.environ.get('ANDROID_DEVICE')
if not android_device:
adb_devices = subprocess.check_output(['adb', 'devices'],
shell=IS_WINDOWS_HOST)
devices = []
for line in adb_devices.splitlines():
line = line.decode('utf-8')
if (line == 'List of devices attached' or
re.match('^\* daemon .+ \*$', line) or
line == ''):
continue
(device, ignore) = line.split('\t')
devices.append(device)
if len(devices) != 1:
print("Please set ANDROID_DEVICE to your device's id", file=sys.stderr)
return 2
android_device = devices[0]
print('Using autodetected Android device:', android_device)
elif is_fuchsia:
zircon_nodename = os.environ.get('ZIRCON_NODENAME')
if not zircon_nodename:
netls = os.path.join(_GetFuchsiaSDKRoot(), 'tools', 'netls')
@ -212,7 +337,9 @@ def main(args):
subprocess.check_call(
[sys.executable, os.path.join(CRASHPAD_DIR, test), binary_dir])
else:
if is_fuchsia:
if is_android:
_RunOnAndroidTarget(binary_dir, test, android_device)
elif is_fuchsia:
_RunOnFuchsiaTarget(binary_dir, test, zircon_nodename)
else:
subprocess.check_call(os.path.join(binary_dir, test))

View File

@ -210,35 +210,18 @@ Software Development Kit.
### Android
To test on Android, use [ADB (Android Debug
Bridge)](https://developer.android.com/studio/command-line/adb.html) to `adb
push` test executables and test data to a device or emulator, then use `adb
shell` to get a shell to run the test executables from. ADB is part of the
[Android SDK](https://developer.android.com/sdk/). Note that it is sufficient to
install just the command-line tools. The entire Android Studio IDE is not
necessary to obtain ADB.
To test on Android, [ADB (Android Debug
Bridge)](https://developer.android.com/studio/command-line/adb.html) from the
[Android SDK](https://developer.android.com/sdk/) must be in the `PATH`. Note
that it is sufficient to install just the command-line tools from the Android
SDK. The entire Android Studio IDE is not necessary to obtain ADB.
This example runs `crashpad_test_test` on a device. This test executable has a
run-time dependency on a second executable and a test data file, which are also
transferred to the device prior to running the test.
```
$ cd ~/crashpad/crashpad
$ adb push out/android_arm64_api21/out/Debug/crashpad_test_test /data/local/tmp/
[100%] /data/local/tmp/crashpad_test_test
$ adb push \
out/android_arm64_api21/out/Debug/crashpad_test_test_multiprocess_exec_test_child \
/data/local/tmp/
[100%] /data/local/tmp/crashpad_test_test_multiprocess_exec_test_child
$ adb shell mkdir -p /data/local/tmp/crashpad_test_data_root/test
$ adb push test/test_paths_test_data_root.txt \
/data/local/tmp/crashpad_test_data_root/test/
[100%] /data/local/tmp/crashpad_test_data_root/test/test_paths_test_data_root.txt
$ adb shell
device:/ $ cd /data/local/tmp
device:/data/local/tmp $ CRASHPAD_TEST_DATA_ROOT=crashpad_test_data_root \
./crashpad_test_test
```
When asked to test an Android build directory, `run_tests.py` will detect a
single connected Android device (including an emulator). If multiple devices are
connected, one may be chosen explicitly with the `ANDROID_DEVICE` environment
variable. `run_tests.py` will upload test executables and data to a temporary
location on the detected or selected device, run them, and clean up after itself
when done.
## Contributing