| #!/usr/bin/env python |
| # |
| # Copyright 2018 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import argparse |
| import contextlib |
| import json |
| import logging |
| import os |
| import re |
| import stat |
| import subprocess |
| import sys |
| |
| |
| CHROMIUM_SRC_PATH = os.path.abspath(os.path.join( |
| os.path.dirname(__file__), '..', '..')) |
| |
| # Use the android test-runner's gtest results support library for generating |
| # output json ourselves. |
| sys.path.insert(0, os.path.join(CHROMIUM_SRC_PATH, 'build', 'android')) |
| from pylib.base import base_test_result |
| from pylib.results import json_results |
| |
| CHROMITE_PATH = os.path.abspath(os.path.join( |
| CHROMIUM_SRC_PATH, 'third_party', 'chromite')) |
| CROS_RUN_VM_TEST_PATH = os.path.abspath(os.path.join( |
| CHROMITE_PATH, 'bin', 'cros_run_vm_test')) |
| |
| |
| _FILE_BLACKLIST = [ |
| re.compile(r'.*build/chromeos.*'), |
| re.compile(r'.*build/cros_cache.*'), |
| re.compile(r'.*third_party/chromite.*'), |
| ] |
| |
| |
| def read_runtime_files(runtime_deps_path, outdir): |
| if not runtime_deps_path: |
| return [] |
| |
| abs_runtime_deps_path = os.path.abspath( |
| os.path.join(outdir, runtime_deps_path)) |
| with open(abs_runtime_deps_path) as runtime_deps_file: |
| files = [l.strip() for l in runtime_deps_file if l] |
| rel_file_paths = [] |
| for f in files: |
| rel_file_path = os.path.relpath( |
| os.path.abspath(os.path.join(outdir, f)), |
| os.getcwd()) |
| if not any(regex.match(rel_file_path) for regex in _FILE_BLACKLIST): |
| rel_file_paths.append(rel_file_path) |
| |
| return rel_file_paths |
| |
| |
| def host_cmd(args): |
| if not args.cmd: |
| logging.error('Must specify command to run on the host.') |
| return 1 |
| |
| cros_run_vm_test_cmd = [ |
| CROS_RUN_VM_TEST_PATH, |
| '--start', |
| '--board', args.board, |
| '--cache-dir', args.cros_cache, |
| ] |
| if args.verbose: |
| cros_run_vm_test_cmd.append('--debug') |
| |
| cros_run_vm_test_cmd += [ |
| '--host-cmd', |
| '--', |
| ] + args.cmd |
| |
| logging.info('Running the following command:') |
| logging.info(' '.join(cros_run_vm_test_cmd)) |
| |
| return subprocess.call( |
| cros_run_vm_test_cmd, stdout=sys.stdout, stderr=sys.stderr) |
| |
| |
| def vm_test(args): |
| is_sanity_test = args.test_exe == 'cros_vm_sanity_test' |
| |
| cros_run_vm_test_cmd = [ |
| CROS_RUN_VM_TEST_PATH, |
| '--start', |
| '--board', args.board, |
| '--cache-dir', args.cros_cache, |
| ] |
| |
| # cros_run_vm_test has trouble with relative paths that go up directories, so |
| # cd to src/, which should be the root of all data deps. |
| os.chdir(CHROMIUM_SRC_PATH) |
| |
| runtime_files = read_runtime_files( |
| args.runtime_deps_path, args.path_to_outdir) |
| # If we're pushing files, we need to set the cwd. |
| if runtime_files: |
| cros_run_vm_test_cmd.extend( |
| ['--cwd', os.path.relpath(args.path_to_outdir, CHROMIUM_SRC_PATH)]) |
| for f in runtime_files: |
| cros_run_vm_test_cmd.extend(['--files', f]) |
| |
| if args.test_launcher_summary_output and not is_sanity_test: |
| result_dir, result_file = os.path.split(args.test_launcher_summary_output) |
| # If args.test_launcher_summary_output is a file in cwd, result_dir will be |
| # an empty string, so replace it with '.' when this is the case so |
| # cros_run_vm_test can correctly handle it. |
| if not result_dir: |
| result_dir = '.' |
| vm_result_file = '/tmp/%s' % result_file |
| cros_run_vm_test_cmd += [ |
| '--results-src', vm_result_file, |
| '--results-dest-dir', result_dir, |
| ] |
| |
| if is_sanity_test: |
| # run_cros_vm_test's default behavior when no cmd is specified is the sanity |
| # test that's baked into the VM image. This test smoke-checks the system |
| # browser, so deploy our locally-built chrome to the VM before testing. |
| cros_run_vm_test_cmd += [ |
| '--deploy', |
| '--build-dir', os.path.relpath(args.path_to_outdir, CHROMIUM_SRC_PATH), |
| ] |
| else: |
| cros_run_vm_test_cmd += [ |
| '--cmd', |
| '--', |
| './' + args.test_exe, |
| '--test-launcher-shard-index=%d' % args.test_launcher_shard_index, |
| '--test-launcher-total-shards=%d' % args.test_launcher_total_shards, |
| ] |
| |
| if args.test_launcher_summary_output and not is_sanity_test: |
| cros_run_vm_test_cmd += [ |
| '--test-launcher-summary-output=%s' % vm_result_file, |
| ] |
| |
| logging.info('Running the following command:') |
| logging.info(' '.join(cros_run_vm_test_cmd)) |
| |
| # deploy_chrome needs a set of GN args used to build chrome to determine if |
| # certain libraries need to be pushed to the VM. It looks for the args via an |
| # env var. To trigger the default deploying behavior, give it a dummy set of |
| # args. |
| # TODO(crbug.com/823996): Make the GN-dependent deps controllable via cmd-line |
| # args. |
| env_copy = os.environ.copy() |
| if not env_copy.get('GN_ARGS'): |
| env_copy['GN_ARGS'] = 'is_chromeos = true' |
| env_copy['PATH'] = env_copy['PATH'] + ':' + os.path.join(CHROMITE_PATH, 'bin') |
| rc = subprocess.call( |
| cros_run_vm_test_cmd, stdout=sys.stdout, stderr=sys.stderr, env=env_copy) |
| |
| # Create a simple json results file for the sanity test if needed. The results |
| # will contain only one test ('cros_vm_sanity_test'), and will either be a |
| # PASS or FAIL depending on the return code of cros_run_vm_test above. |
| if args.test_launcher_summary_output and is_sanity_test: |
| result = (base_test_result.ResultType.FAIL if rc else |
| base_test_result.ResultType.PASS) |
| sanity_test_result = base_test_result.BaseTestResult( |
| 'cros_vm_sanity_test', result) |
| run_results = base_test_result.TestRunResults() |
| run_results.AddResult(sanity_test_result) |
| with open(args.test_launcher_summary_output, 'w') as f: |
| json.dump(json_results.GenerateResultsDict([run_results]), f) |
| |
| return rc |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser() |
| parser.add_argument('--verbose', '-v', action='store_true') |
| # Required args. |
| parser.add_argument( |
| '--board', type=str, required=True, help='Type of CrOS device.') |
| subparsers = parser.add_subparsers(dest='test_type') |
| # Host-side test args. |
| host_cmd_parser = subparsers.add_parser( |
| 'host-cmd', |
| help='Runs a host-side test. Pass the host-side command to run after ' |
| '"--". Hostname and port for the VM will be 127.0.0.1:9222.') |
| host_cmd_parser.set_defaults(func=host_cmd) |
| host_cmd_parser.add_argument( |
| '--cros-cache', type=str, required=True, help='Path to cros cache.') |
| host_cmd_parser.add_argument('cmd', nargs=argparse.REMAINDER) |
| # VM-side test args. |
| vm_test_parser = subparsers.add_parser( |
| 'vm-test', |
| help='Runs a vm-side gtest.') |
| vm_test_parser.set_defaults(func=vm_test) |
| vm_test_parser.add_argument( |
| '--cros-cache', type=str, required=True, help='Path to cros cache.') |
| vm_test_parser.add_argument( |
| '--test-exe', type=str, required=True, |
| help='Path to test executable to run inside VM. If the value is ' |
| '"cros_vm_sanity_test", the sanity test that ships with the VM ' |
| 'image runs instead. This test smokes-check the system browser ' |
| '(eg: loads a simple webpage, executes some javascript), so a ' |
| 'fully-built Chrome binary that can get deployed to the VM is ' |
| 'expected to available in the out-dir.') |
| vm_test_parser.add_argument( |
| '--path-to-outdir', type=str, required=True, |
| help='Path to output directory, all of whose contents will be deployed ' |
| 'to the device.') |
| vm_test_parser.add_argument( |
| '--runtime-deps-path', type=str, |
| help='Runtime data dependency file from GN.') |
| vm_test_parser.add_argument( |
| '--test-launcher-summary-output', type=str, |
| help='When set, will pass the same option down to the test and retrieve ' |
| 'its result file at the specified location.') |
| vm_test_parser.add_argument( |
| '--test-launcher-shard-index', |
| type=int, default=os.environ.get('GTEST_SHARD_INDEX', 0), |
| help='Index of the external shard to run.') |
| vm_test_parser.add_argument( |
| '--test-launcher-total-shards', |
| type=int, default=os.environ.get('GTEST_TOTAL_SHARDS', 1), |
| help='Total number of external shards.') |
| args = parser.parse_args() |
| |
| logging.basicConfig(level=logging.DEBUG if args.verbose else logging.WARN) |
| |
| if not os.path.exists('/dev/kvm'): |
| logging.error('/dev/kvm is missing. Is KVM installed on this machine?') |
| return 1 |
| elif not os.access('/dev/kvm', os.W_OK): |
| logging.error( |
| '/dev/kvm is not writable as current user. Perhaps you should be root?') |
| return 1 |
| |
| args.cros_cache = os.path.abspath(args.cros_cache) |
| return args.func(args) |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |