#!/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())