| #!/usr/bin/env python |
| # coding: utf-8 |
| |
| # Copyright 2014 The Crashpad Authors. All rights reserved. |
| # |
| # 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. |
| |
| from __future__ import print_function |
| |
| import argparse |
| import os |
| import pipes |
| import posixpath |
| import re |
| import subprocess |
| import sys |
| import tempfile |
| import uuid |
| |
| CRASHPAD_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), |
| os.pardir) |
| IS_WINDOWS_HOST = sys.platform.startswith('win') |
| |
| |
| def _FindGNFromBinaryDir(binary_dir): |
| """Attempts to determine the path to a GN binary used to generate the build |
| files in the given binary_dir. This is necessary because `gn` might not be in |
| the path or might be in a non-standard location, particularly on build |
| machines.""" |
| |
| build_ninja = os.path.join(binary_dir, 'build.ninja') |
| if os.path.isfile(build_ninja): |
| with open(build_ninja, 'rb') as f: |
| # Look for the always-generated regeneration rule of the form: |
| # |
| # rule gn |
| # command = <gn binary> ... arguments ... |
| # |
| # to extract the gn binary's full path. |
| found_rule_gn = False |
| for line in f: |
| if line.strip() == 'rule gn': |
| found_rule_gn = True |
| continue |
| if found_rule_gn: |
| if len(line) == 0 or line[0] != ' ': |
| return None |
| if line.startswith(' command = '): |
| gn_command_line_parts = line.strip().split(' ') |
| if len(gn_command_line_parts) > 2: |
| return os.path.join(binary_dir, gn_command_line_parts[2]) |
| |
| return None |
| |
| |
| def _BinaryDirTargetOS(binary_dir): |
| """Returns the apparent target OS of binary_dir, or None if none appear to be |
| explicitly specified.""" |
| |
| gn_path = _FindGNFromBinaryDir(binary_dir) |
| |
| if gn_path: |
| # Look for a GN “target_os”. |
| popen = subprocess.Popen([gn_path, '--root=' + CRASHPAD_DIR, |
| 'args', binary_dir, |
| '--list=target_os', '--short'], |
| shell=IS_WINDOWS_HOST, |
| stdout=subprocess.PIPE, stderr=open(os.devnull)) |
| value = popen.communicate()[0] |
| 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. |
| build_ninja_path = os.path.join(binary_dir, 'build.ninja') |
| if os.path.exists(build_ninja_path): |
| with open(build_ninja_path) as build_ninja_file: |
| build_ninja_content = build_ninja_file.read() |
| match = re.search('-linux-android(eabi)?-ar$', |
| build_ninja_content, |
| re.MULTILINE) |
| if match: |
| return 'android' |
| |
| return None |
| |
| |
| def _EnableVTProcessingOnWindowsConsole(): |
| """Enables virtual terminal processing for ANSI/VT100-style escape sequences |
| on a Windows console attached to standard output. Returns True on success. |
| Returns False if standard output is not a console or if virtual terminal |
| processing is not supported. The feature was introduced in Windows 10. |
| """ |
| |
| import pywintypes |
| import win32console |
| import winerror |
| |
| stdout_console = win32console.GetStdHandle(win32console.STD_OUTPUT_HANDLE) |
| try: |
| console_mode = stdout_console.GetConsoleMode() |
| except pywintypes.error as e: |
| if e.winerror == winerror.ERROR_INVALID_HANDLE: |
| # Standard output is not a console. |
| return False |
| raise |
| |
| try: |
| # From <wincon.h>. This would be |
| # win32console.ENABLE_VIRTUAL_TERMINAL_PROCESSING, but it’s too new to be |
| # defined there. |
| ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 |
| |
| stdout_console.SetConsoleMode(console_mode | |
| ENABLE_VIRTUAL_TERMINAL_PROCESSING) |
| except pywintypes.error as e: |
| if e.winerror == winerror.ERROR_INVALID_PARAMETER: |
| # ANSI/VT100-style escape sequence processing isn’t supported before |
| # Windows 10. |
| return False |
| raise |
| |
| return True |
| |
| |
| def _RunOnAndroidTarget(binary_dir, test, android_device, extra_command_line): |
| 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('This test is not present and may not be supported, skipping') |
| return |
| |
| def _adb(*args): |
| # Flush all of this script’s 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) |
| |
| def _adb_push(sources, destination): |
| args = list(sources) |
| args.append(destination) |
| _adb('push', *args) |
| |
| def _adb_shell(command_args, env={}): |
| # Build a command to execute via “sh -c” instead of invoking it directly. |
| # Here’s why: |
| # |
| # /system/bin/env isn’t normally present prior to Android 6.0 (M), where |
| # toybox was introduced (Android platform/manifest 9a2c01e8450b). Instead, |
| # set environment variables by using the shell’s internal “export” command. |
| # |
| # adbd prior to Android 7.0 (N), and the adb client prior to SDK |
| # platform-tools version 24, don’t know how to communicate a shell command’s |
| # exit status. This was added in Android platform/system/core 606835ae5c4b). |
| # With older adb servers and clients, adb will “exit 0” indicating success |
| # even if the command failed on the device. This makes |
| # subprocess.check_call() semantics difficult to implement directly. As a |
| # workaround, have the device send the command’s exit status over stdout and |
| # pick it back up in this function. |
| # |
| # Both workarounds are implemented by giving the device a simple script, |
| # which adbd will run as an “sh -c” argument. |
| adb_command = ['adb', '-s', android_device, 'shell'] |
| script_commands = [] |
| for k, v in env.items(): |
| script_commands.append('export %s=%s' % (pipes.quote(k), pipes.quote(v))) |
| script_commands.extend([ |
| ' '.join(pipes.quote(x) for x in command_args), |
| 'status=${?}', |
| 'echo "status=${status}"', |
| 'exit ${status}']) |
| adb_command.append('; '.join(script_commands)) |
| child = subprocess.Popen(adb_command, |
| shell=IS_WINDOWS_HOST, |
| stdin=open(os.devnull), |
| stdout=subprocess.PIPE) |
| |
| FINAL_LINE_RE = re.compile('status=(\d+)$') |
| final_line = None |
| while True: |
| # Use readline so that the test output appears “live” when running. |
| data = child.stdout.readline().decode('utf-8') |
| if data == '': |
| break |
| if final_line is not None: |
| # It wasn’t really the final line. |
| print(final_line, end='') |
| final_line = None |
| if FINAL_LINE_RE.match(data.rstrip()): |
| final_line = data |
| else: |
| print(data, end='') |
| |
| if final_line is None: |
| # Maybe there was some stderr output after the end of stdout. Old versions |
| # of adb, prior to when the exit status could be communicated, smush the |
| # two together. |
| raise subprocess.CalledProcessError(-1, adb_command) |
| status = int(FINAL_LINE_RE.match(final_line.rstrip()).group(1)) |
| if status != 0: |
| raise subprocess.CalledProcessError(status, adb_command) |
| |
| child.wait() |
| if child.returncode != 0: |
| raise subprocess.CalledProcessError(subprocess.returncode, adb_command) |
| |
| # /system/bin/mktemp isn’t normally present prior to Android 6.0 (M), where |
| # toybox was introduced (Android platform/manifest 9a2c01e8450b). Fake it with |
| # a host-generated name. This won’t retry if the name is in use, but with 122 |
| # bits of randomness, it should be OK. This uses “mkdir” instead of “mkdir -p” |
| # because the latter will not indicate failure if the directory already |
| # exists. |
| device_temp_dir = '/data/local/tmp/%s.%s' % (test, uuid.uuid4().hex) |
| _adb_shell(['mkdir', device_temp_dir]) |
| |
| 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, 'crashpad_handler'] |
| 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/') |
| |
| # 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 what’s needed from the path split. All parent |
| # directories of any source_path need to be be represented in |
| # device_mkdirs, but it’s 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 = ['mkdir', '-p'] |
| adb_mkdir_command.extend(device_mkdirs) |
| _adb_shell(adb_mkdir_command) |
| |
| # Push the test binary and any other build output to the device. |
| local_test_build_artifacts = [] |
| for artifact in test_build_artifacts: |
| local_test_build_artifacts.append(os.path.join(binary_dir, artifact)) |
| _adb_push(local_test_build_artifacts, device_out_dir) |
| |
| # 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. Pass the test data root in the environment. |
| # |
| # Because the test will not run with its standard output attached to a |
| # pseudo-terminal device, gtest will not normally enable colored output, so |
| # mimic gtest’s own logic for deciding whether to enable color by checking |
| # this script’s own standard output connection. The whitelist of TERM values |
| # comes from gtest googletest/src/gtest.cc |
| # testing::internal::ShouldUseColor(). |
| env = {'CRASHPAD_TEST_DATA_ROOT': device_temp_dir} |
| gtest_color = os.environ.get('GTEST_COLOR') |
| if gtest_color in ('auto', None): |
| if (sys.stdout.isatty() and |
| (os.environ.get('TERM') in |
| ('xterm', 'xterm-color', 'xterm-256color', 'screen', |
| 'screen-256color', 'tmux', 'tmux-256color', 'rxvt-unicode', |
| 'rxvt-unicode-256color', 'linux', 'cygwin') or |
| (IS_WINDOWS_HOST and _EnableVTProcessingOnWindowsConsole()))): |
| gtest_color = 'yes' |
| else: |
| gtest_color = 'no' |
| env['GTEST_COLOR'] = gtest_color |
| _adb_shell([posixpath.join(device_out_dir, test)] + extra_command_line, env) |
| 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): |
| """Ensures a <binary_dir>/<test>.runtime_deps file exists for each test.""" |
| targets_file = os.path.join(binary_dir, 'targets.txt') |
| with open(targets_file, 'wb') as f: |
| f.write('//:' + '\n//:'.join(tests) + '\n') |
| gn_path = _FindGNFromBinaryDir(binary_dir) |
| subprocess.check_call( |
| [gn_path, '--root=' + CRASHPAD_DIR, 'gen', binary_dir, |
| '--runtime-deps-list-file=' + targets_file]) |
| |
| # Run again so that --runtime-deps-list-file isn't in the regen rule. See |
| # https://crbug.com/814816. |
| subprocess.check_call( |
| [gn_path, '--root=' + CRASHPAD_DIR, 'gen', binary_dir]) |
| |
| |
| def _HandleOutputFromFuchsiaLogListener(process, done_message): |
| """Pass through the output from |process| (which should be an instance of |
| Fuchsia's loglistener) until a special termination |done_message| is |
| encountered. |
| |
| Also attempts to determine if any tests failed by inspecting the log output, |
| and returns False if there were failures. |
| """ |
| success = True |
| while True: |
| line = process.stdout.readline().rstrip() |
| if 'FAILED TEST' in line: |
| success = False |
| elif done_message in line and 'echo ' not in line: |
| break |
| print(line) |
| return success |
| |
| |
| def _RunOnFuchsiaTarget(binary_dir, test, device_name, extra_command_line): |
| """Runs the given Fuchsia |test| executable on the given |device_name|. The |
| device must already be booted. |
| |
| Copies the executable and its runtime dependencies as specified by GN to the |
| target in /tmp using `netcp`, runs the binary on the target, and logs output |
| back to stdout on this machine via `loglistener`. |
| """ |
| sdk_root = _GetFuchsiaSDKRoot() |
| |
| # Run loglistener and filter the output to know when the test is done. |
| loglistener_process = subprocess.Popen( |
| [os.path.join(sdk_root, 'tools', 'loglistener'), device_name], |
| stdout=subprocess.PIPE, stdin=open(os.devnull), stderr=open(os.devnull)) |
| |
| runtime_deps_file = os.path.join(binary_dir, test + '.runtime_deps') |
| with open(runtime_deps_file, 'rb') as f: |
| runtime_deps = f.read().splitlines() |
| |
| def netruncmd(*args): |
| """Runs a list of commands on the target device. Each command is escaped |
| by using pipes.quote(), and then each command is chained by shell ';'. |
| """ |
| netruncmd_path = os.path.join(sdk_root, 'tools', 'netruncmd') |
| final_args = ' ; '.join(' '.join(pipes.quote(x) for x in command) |
| for command in args) |
| subprocess.check_call([netruncmd_path, device_name, final_args]) |
| |
| try: |
| unique_id = uuid.uuid4().hex |
| test_root = '/tmp/%s_%s' % (test, unique_id) |
| tmp_root = test_root + '/tmp' |
| staging_root = test_root + '/pkg' |
| |
| # Make a staging directory tree on the target. |
| directories_to_create = [tmp_root, |
| '%s/bin' % staging_root, |
| '%s/assets' % staging_root] |
| netruncmd(['mkdir', '-p'] + directories_to_create) |
| |
| def netcp(local_path): |
| """Uses `netcp` to copy a file or directory to the device. Files located |
| inside the build dir are stored to /pkg/bin, otherwise to /pkg/assets. |
| .so files are stored somewhere completely different, into /boot/lib (!). |
| This is because the loader service does not yet correctly handle the |
| namespace in which the caller is being run, and so can only load .so files |
| from a couple hardcoded locations, the only writable one of which is |
| /boot/lib, so we copy all .so files there. This bug is filed upstream as |
| ZX-1619. |
| """ |
| in_binary_dir = local_path.startswith(binary_dir + '/') |
| if in_binary_dir: |
| if local_path.endswith('.so'): |
| target_path = os.path.join( |
| '/boot/lib', local_path[len(binary_dir)+1:]) |
| else: |
| target_path = os.path.join( |
| staging_root, 'bin', local_path[len(binary_dir)+1:]) |
| else: |
| relative_path = os.path.relpath(local_path, CRASHPAD_DIR) |
| target_path = os.path.join(staging_root, 'assets', relative_path) |
| netcp_path = os.path.join(sdk_root, 'tools', 'netcp') |
| subprocess.check_call([netcp_path, local_path, |
| device_name + ':' + target_path], |
| stderr=open(os.devnull)) |
| |
| # Copy runtime deps into the staging tree. |
| for dep in runtime_deps: |
| local_path = os.path.normpath(os.path.join(binary_dir, dep)) |
| if os.path.isdir(local_path): |
| for root, dirs, files in os.walk(local_path): |
| for f in files: |
| netcp(os.path.join(root, f)) |
| else: |
| netcp(local_path) |
| |
| done_message = 'TERMINATED: ' + unique_id |
| namespace_command = [ |
| 'namespace', '/pkg=' + staging_root, '/tmp=' + tmp_root, '/svc=/svc', |
| '--replace-child-argv0=/pkg/bin/' + test, '--', |
| staging_root + '/bin/' + test] + extra_command_line |
| netruncmd(namespace_command, ['echo', done_message]) |
| |
| success = _HandleOutputFromFuchsiaLogListener( |
| loglistener_process, done_message) |
| if not success: |
| raise subprocess.CalledProcessError(1, test) |
| finally: |
| netruncmd(['rm', '-rf', test_root]) |
| |
| |
| def _RunOnIOSTarget(binary_dir, test, is_xcuitest=False): |
| """Runs the given iOS |test| app on iPhone 8 with the default OS version.""" |
| |
| def xctest(binary_dir, test): |
| """Returns a dict containing the xctestrun data needed to run an |
| XCTest-based test app.""" |
| test_path = os.path.join(CRASHPAD_DIR, binary_dir) |
| module_data = { |
| 'TestBundlePath': os.path.join(test_path, test + '_module.xctest'), |
| 'TestHostPath': os.path.join(test_path, test + '.app'), |
| 'TestingEnvironmentVariables': { |
| 'DYLD_FRAMEWORK_PATH': '__TESTROOT__/Debug-iphonesimulator:', |
| 'DYLD_INSERT_LIBRARIES': ( |
| '__PLATFORMS__/iPhoneSimulator.platform/Developer/' |
| 'usr/lib/libXCTestBundleInject.dylib'), |
| 'DYLD_LIBRARY_PATH': '__TESTROOT__/Debug-iphonesimulator', |
| 'IDEiPhoneInternalTestBundleName': test + '.app', |
| 'XCInjectBundleInto': '__TESTHOST__/' + test, |
| } |
| } |
| return { test: module_data } |
| |
| def xcuitest(binary_dir, test): |
| """Returns a dict containing the xctestrun data needed to run an |
| XCUITest-based test app.""" |
| |
| test_path = os.path.join(CRASHPAD_DIR, binary_dir) |
| runner_path = os.path.join(test_path, test + '_module-Runner.app') |
| bundle_path = os.path.join(runner_path, 'PlugIns', test + '_module.xctest') |
| target_app_path = os.path.join(test_path, test + '.app') |
| module_data = { |
| 'IsUITestBundle': True, |
| 'IsXCTRunnerHostedTestBundle': True, |
| 'TestBundlePath': bundle_path, |
| 'TestHostPath': runner_path, |
| 'UITargetAppPath': target_app_path, |
| 'DependentProductPaths': [ bundle_path, runner_path, target_app_path ], |
| 'TestingEnvironmentVariables': { |
| 'DYLD_FRAMEWORK_PATH': '__TESTROOT__/Debug-iphonesimulator:', |
| 'DYLD_INSERT_LIBRARIES': ( |
| '__PLATFORMS__/iPhoneSimulator.platform/Developer/' |
| 'usr/lib/libXCTestBundleInject.dylib'), |
| 'DYLD_LIBRARY_PATH': '__TESTROOT__/Debug-iphonesimulator', |
| 'XCInjectBundleInto': '__TESTHOST__/' + test + '_module-Runner', |
| }, |
| } |
| return { test: module_data } |
| |
| with tempfile.NamedTemporaryFile() as f: |
| import plistlib |
| |
| xctestrun_path = f.name |
| print(xctestrun_path) |
| if is_xcuitest: |
| plistlib.writePlist(xcuitest(binary_dir, test), xctestrun_path) |
| else: |
| plistlib.writePlist(xctest(binary_dir, test), xctestrun_path) |
| |
| subprocess.check_call(['xcodebuild', 'test-without-building', |
| '-xctestrun', xctestrun_path, '-destination', |
| 'platform=iOS Simulator,name=iPhone 8']) |
| |
| # This script is primarily used from the waterfall so that the list of tests |
| # that are run is maintained in-tree, rather than in a separate infrastructure |
| # location in the recipe. |
| def main(args): |
| parser = argparse.ArgumentParser(description='Run Crashpad unittests.') |
| parser.add_argument('binary_dir', help='Root of build dir') |
| parser.add_argument('test', nargs='*', help='Specific test(s) to run.') |
| parser.add_argument('--gtest_filter', |
| help='GTest filter applied to GTest binary runs.') |
| args = parser.parse_args() |
| |
| # Tell 64-bit Windows tests where to find 32-bit test executables, for |
| # cross-bitted testing. This relies on the fact that the GYP build by default |
| # uses {Debug,Release} for the 32-bit build and {Debug,Release}_x64 for the |
| # 64-bit build. This is not a universally valid assumption, and if it’s not |
| # met, 64-bit tests that require 32-bit build output will disable themselves |
| # dynamically. |
| if (sys.platform == 'win32' and args.binary_dir.endswith('_x64') and |
| 'CRASHPAD_TEST_32_BIT_OUTPUT' not in os.environ): |
| binary_dir_32 = args.binary_dir[:-4] |
| if os.path.isdir(binary_dir_32): |
| os.environ['CRASHPAD_TEST_32_BIT_OUTPUT'] = binary_dir_32 |
| |
| target_os = _BinaryDirTargetOS(args.binary_dir) |
| is_android = target_os == 'android' |
| is_fuchsia = target_os == 'fuchsia' |
| is_ios = target_os == 'ios' |
| |
| tests = [ |
| 'crashpad_client_test', |
| 'crashpad_handler_test', |
| 'crashpad_minidump_test', |
| 'crashpad_snapshot_test', |
| 'crashpad_test_test', |
| 'crashpad_util_test', |
| ] |
| |
| 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') |
| popen = subprocess.Popen([netls, '--nowait'], stdout=subprocess.PIPE) |
| devices = popen.communicate()[0].splitlines() |
| if popen.returncode != 0 or len(devices) != 1: |
| print("Please set ZIRCON_NODENAME to your device's hostname", |
| file=sys.stderr) |
| return 2 |
| zircon_nodename = devices[0].strip().split()[1] |
| print('Using autodetected Fuchsia device:', zircon_nodename) |
| _GenerateFuchsiaRuntimeDepsFiles( |
| args.binary_dir, [t for t in tests if not t.endswith('.py')]) |
| elif is_ios: |
| tests.append('ios_crash_xcuitests') |
| elif IS_WINDOWS_HOST: |
| tests.append('snapshot/win/end_to_end_test.py') |
| |
| if args.test: |
| for t in args.test: |
| if t not in tests: |
| print('Unrecognized test:', t, file=sys.stderr) |
| return 3 |
| tests = args.test |
| |
| for test in tests: |
| print('-' * 80) |
| print(test) |
| print('-' * 80) |
| if test.endswith('.py'): |
| subprocess.check_call( |
| [sys.executable, os.path.join(CRASHPAD_DIR, test), args.binary_dir]) |
| else: |
| extra_command_line = [] |
| if args.gtest_filter: |
| extra_command_line.append('--gtest_filter=' + args.gtest_filter) |
| if is_android: |
| _RunOnAndroidTarget(args.binary_dir, test, android_device, |
| extra_command_line) |
| elif is_fuchsia: |
| _RunOnFuchsiaTarget(args.binary_dir, test, zircon_nodename, |
| extra_command_line) |
| elif is_ios: |
| _RunOnIOSTarget(args.binary_dir, test, |
| is_xcuitest=test.startswith('ios')) |
| else: |
| subprocess.check_call([os.path.join(args.binary_dir, test)] + |
| extra_command_line) |
| |
| return 0 |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main(sys.argv[1:])) |