You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
396 lines
15 KiB
396 lines
15 KiB
#!/usr/bin/python2
|
|
# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
from __future__ import absolute_import
|
|
from __future__ import division
|
|
from __future__ import print_function
|
|
|
|
import argparse
|
|
import os
|
|
import signal
|
|
import subprocess
|
|
import sys
|
|
|
|
import logging
|
|
# Turn the logging level to INFO before importing other autotest
|
|
# code, to avoid having failed import logging messages confuse the
|
|
# test_that user.
|
|
logging.basicConfig(level=logging.INFO)
|
|
|
|
import common
|
|
from autotest_lib.client.common_lib import error, logging_manager
|
|
from autotest_lib.server import server_logging_config
|
|
from autotest_lib.server.cros.dynamic_suite import constants
|
|
from autotest_lib.server.hosts import factory
|
|
from autotest_lib.site_utils import test_runner_utils
|
|
|
|
|
|
_QUICKMERGE_SCRIPTNAME = '/mnt/host/source/chromite/bin/autotest_quickmerge'
|
|
|
|
|
|
def _get_board_from_host(remote):
|
|
"""Get the board of the remote host.
|
|
|
|
@param remote: string representing the IP of the remote host.
|
|
|
|
@return: A string representing the board of the remote host.
|
|
"""
|
|
logging.info('Board unspecified, attempting to determine board from host.')
|
|
host = factory.create_host(remote)
|
|
try:
|
|
board = host.get_board().replace(constants.BOARD_PREFIX, '')
|
|
except error.AutoservRunError:
|
|
raise test_runner_utils.TestThatRunError(
|
|
'Cannot determine board, please specify a --board option.')
|
|
logging.info('Detected host board: %s', board)
|
|
return board
|
|
|
|
|
|
def _get_model_from_host(remote):
|
|
"""Get the model of the remote host.
|
|
|
|
@param remote: string representing the IP of the remote host.
|
|
|
|
@return: A string representing the board of the remote host.
|
|
"""
|
|
logging.info('Model unspecified, attempting to determine model from host.')
|
|
host = factory.create_host(remote)
|
|
try:
|
|
model = host.get_platform()
|
|
except error.AutoservRunError:
|
|
raise test_runner_utils.TestThatRunError(
|
|
'Cannot determine model, please specify a --model option.')
|
|
logging.info('Detected host model: %s', model)
|
|
return model
|
|
|
|
|
|
def validate_arguments(arguments):
|
|
"""
|
|
Validates parsed arguments.
|
|
|
|
@param arguments: arguments object, as parsed by ParseArguments
|
|
@raises: ValueError if arguments were invalid.
|
|
"""
|
|
if arguments.remote == ':lab:':
|
|
if arguments.args:
|
|
raise ValueError('--args flag not supported when running against '
|
|
':lab:')
|
|
if arguments.pretend:
|
|
raise ValueError('--pretend flag not supported when running '
|
|
'against :lab:')
|
|
if arguments.ssh_verbosity:
|
|
raise ValueError('--ssh_verbosity flag not supported when running '
|
|
'against :lab:')
|
|
if not arguments.board or arguments.build == test_runner_utils.NO_BUILD:
|
|
raise ValueError('--board and --build are both required when '
|
|
'running against :lab:')
|
|
else:
|
|
if arguments.web:
|
|
raise ValueError('--web flag not supported when running locally')
|
|
|
|
|
|
def parse_arguments(argv):
|
|
"""
|
|
Parse command line arguments
|
|
|
|
@param argv: argument list to parse
|
|
@returns: parsed arguments
|
|
@raises SystemExit if arguments are malformed, or required arguments
|
|
are not present.
|
|
"""
|
|
return _parse_arguments_internal(argv)[0]
|
|
|
|
|
|
def _parse_arguments_internal(argv):
|
|
"""
|
|
Parse command line arguments
|
|
|
|
@param argv: argument list to parse
|
|
@returns: tuple of parsed arguments and argv suitable for remote runs
|
|
@raises SystemExit if arguments are malformed, or required arguments
|
|
are not present.
|
|
"""
|
|
local_parser, remote_argv = parse_local_arguments(argv)
|
|
|
|
parser = argparse.ArgumentParser(description='Run remote tests.',
|
|
parents=[local_parser])
|
|
|
|
parser.add_argument('remote', metavar='REMOTE',
|
|
help='hostname[:port] for remote device. Specify '
|
|
':lab: to run in test lab. When tests are run in '
|
|
'the lab, test_that will use the client autotest '
|
|
'package for the build specified with --build, '
|
|
'and the lab server code rather than local '
|
|
'changes.')
|
|
test_runner_utils.add_common_args(parser)
|
|
parser.add_argument('-b', '--board', metavar='BOARD',
|
|
action='store',
|
|
help='Board for which the test will run. '
|
|
'Default: %(default)s')
|
|
parser.add_argument('-m',
|
|
'--model',
|
|
metavar='MODEL',
|
|
help='Specific model the test will run against. '
|
|
'Matches the model:FAKE_MODEL label for the host.')
|
|
parser.add_argument('-i', '--build', metavar='BUILD',
|
|
default=test_runner_utils.NO_BUILD,
|
|
help='Build to test. Device will be reimaged if '
|
|
'necessary. Omit flag to skip reimage and test '
|
|
'against already installed DUT image. Examples: '
|
|
'link-paladin/R34-5222.0.0-rc2, '
|
|
'lumpy-release/R34-5205.0.0')
|
|
parser.add_argument('-p', '--pool', metavar='POOL', default='suites',
|
|
help='Pool to use when running tests in the lab. '
|
|
'Default is "suites"')
|
|
parser.add_argument('--autotest_dir', metavar='AUTOTEST_DIR',
|
|
help='Use AUTOTEST_DIR instead of normal board sysroot '
|
|
'copy of autotest, and skip the quickmerge step.')
|
|
parser.add_argument('--no-quickmerge', action='store_true', default=False,
|
|
dest='no_quickmerge',
|
|
help='Skip the quickmerge step and use the sysroot '
|
|
'as it currently is. May result in un-merged '
|
|
'source tree changes not being reflected in the '
|
|
'run. If using --autotest_dir, this flag is '
|
|
'automatically applied.')
|
|
parser.add_argument('--allow-chrome-crashes',
|
|
action='store_true',
|
|
default=False,
|
|
dest='allow_chrome_crashes',
|
|
help='Ignore chrome crashes when producing test '
|
|
'report. This flag gets passed along to the '
|
|
'report generation tool.')
|
|
parser.add_argument('--ssh_private_key', action='store',
|
|
default=test_runner_utils.TEST_KEY_PATH,
|
|
help='Path to the private ssh key.')
|
|
return parser.parse_args(argv), remote_argv
|
|
|
|
|
|
def parse_local_arguments(argv):
|
|
"""
|
|
Strips out arguments that are not to be passed through to runs.
|
|
|
|
Add any arguments that should not be passed to remote test_that runs here.
|
|
|
|
@param argv: argument list to parse.
|
|
@returns: tuple of local argument parser and remaining argv.
|
|
"""
|
|
parser = argparse.ArgumentParser(add_help=False)
|
|
parser.add_argument('-w', '--web', dest='web', default=None,
|
|
help='Address of a webserver to receive test requests.')
|
|
parser.add_argument('-x', '--max_runtime_mins', type=int,
|
|
dest='max_runtime_mins', default=20,
|
|
help='Default time allowed for the tests to complete.')
|
|
parser.add_argument('--no-retries', '--no-retry',
|
|
dest='retry', action='store_false', default=True,
|
|
help='For local runs only, ignore any retries '
|
|
'specified in the control files.')
|
|
_, remaining_argv = parser.parse_known_args(argv)
|
|
return parser, remaining_argv
|
|
|
|
|
|
def perform_bootstrap_into_autotest_root(arguments, autotest_path, argv):
|
|
"""
|
|
Perfoms a bootstrap to run test_that from the |autotest_path|.
|
|
|
|
This function is to be called from test_that's main() script, when
|
|
test_that is executed from the source tree location. It runs
|
|
autotest_quickmerge to update the sysroot unless arguments.no_quickmerge
|
|
is set. It then executes and waits on the version of test_that.py
|
|
in |autotest_path|.
|
|
|
|
@param arguments: A parsed arguments object, as returned from
|
|
test_that.parse_arguments(...).
|
|
@param autotest_path: Full absolute path to the autotest root directory.
|
|
@param argv: The arguments list, as passed to main(...)
|
|
|
|
@returns: The return code of the test_that script that was executed in
|
|
|autotest_path|.
|
|
"""
|
|
logging_manager.configure_logging(
|
|
server_logging_config.ServerLoggingConfig(),
|
|
use_console=True,
|
|
verbose=arguments.debug)
|
|
if arguments.no_quickmerge:
|
|
logging.info('Skipping quickmerge step.')
|
|
else:
|
|
logging.info('Running autotest_quickmerge step.')
|
|
command = [_QUICKMERGE_SCRIPTNAME, '--board='+arguments.board]
|
|
s = subprocess.Popen(command,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT)
|
|
for message in iter(s.stdout.readline, b''):
|
|
logging.info('quickmerge| %s', message.strip())
|
|
return_code = s.wait()
|
|
if return_code:
|
|
raise test_runner_utils.TestThatRunError(
|
|
'autotest_quickmerge failed with error code %s.' %
|
|
return_code)
|
|
|
|
logging.info('Re-running test_that script in %s copy of autotest.',
|
|
autotest_path)
|
|
script_command = os.path.join(autotest_path, 'site_utils',
|
|
'test_that.py')
|
|
if not os.path.exists(script_command):
|
|
raise test_runner_utils.TestThatRunError(
|
|
'Unable to bootstrap to autotest root, %s not found.' %
|
|
script_command)
|
|
proc = None
|
|
def resend_sig(signum, stack_frame):
|
|
#pylint: disable-msg=C0111
|
|
if proc:
|
|
proc.send_signal(signum)
|
|
signal.signal(signal.SIGINT, resend_sig)
|
|
signal.signal(signal.SIGTERM, resend_sig)
|
|
|
|
proc = subprocess.Popen([script_command] + argv)
|
|
|
|
return proc.wait()
|
|
|
|
|
|
def _main_for_local_run(argv, arguments):
|
|
"""
|
|
Effective entry point for local test_that runs.
|
|
|
|
@param argv: Script command line arguments.
|
|
@param arguments: Parsed command line arguments.
|
|
"""
|
|
if not os.path.exists('/etc/cros_chroot_version'):
|
|
print('For local runs, script must be run inside chroot.', file=sys.stderr)
|
|
return 1
|
|
|
|
results_directory = test_runner_utils.create_results_directory(
|
|
arguments.results_dir, arguments.board)
|
|
test_runner_utils.add_ssh_identity(results_directory,
|
|
arguments.ssh_private_key)
|
|
arguments.results_dir = results_directory
|
|
|
|
# If the board and/or model is not specified through --board and/or
|
|
# --model, and is not set in the default_board file, determine the board by
|
|
# ssh-ing into the host. Also prepend it to argv so we can re-use it when we
|
|
# run test_that from the sysroot.
|
|
if arguments.board is None:
|
|
arguments.board = _get_board_from_host(arguments.remote)
|
|
argv = ['--board=%s' % (arguments.board,)] + argv
|
|
|
|
if arguments.model is None:
|
|
arguments.model = _get_model_from_host(arguments.remote)
|
|
argv = ['--model=%s' % (arguments.model, )] + argv
|
|
|
|
if arguments.autotest_dir:
|
|
autotest_path = arguments.autotest_dir
|
|
arguments.no_quickmerge = True
|
|
else:
|
|
sysroot_path = os.path.join('/build', arguments.board, '')
|
|
|
|
if not os.path.exists(sysroot_path):
|
|
print(('%s does not exist. Have you run '
|
|
'setup_board?' % sysroot_path), file=sys.stderr)
|
|
return 1
|
|
|
|
path_ending = 'usr/local/build/autotest'
|
|
autotest_path = os.path.join(sysroot_path, path_ending)
|
|
|
|
site_utils_path = os.path.join(autotest_path, 'site_utils')
|
|
|
|
if not os.path.exists(autotest_path):
|
|
print(('%s does not exist. Have you run '
|
|
'build_packages? Or if you are using '
|
|
'--autotest_dir, make sure it points to '
|
|
'a valid autotest directory.' % autotest_path), file=sys.stderr)
|
|
return 1
|
|
|
|
realpath = os.path.realpath(__file__)
|
|
site_utils_path = os.path.realpath(site_utils_path)
|
|
|
|
# If we are not running the sysroot version of script, perform
|
|
# a quickmerge if necessary and then re-execute
|
|
# the sysroot version of script with the same arguments.
|
|
if os.path.dirname(realpath) != site_utils_path:
|
|
return perform_bootstrap_into_autotest_root(
|
|
arguments, autotest_path, argv)
|
|
else:
|
|
return test_runner_utils.perform_run_from_autotest_root(
|
|
autotest_path,
|
|
argv,
|
|
arguments.tests,
|
|
arguments.remote,
|
|
build=arguments.build,
|
|
board=arguments.board,
|
|
model=arguments.model,
|
|
args=arguments.args,
|
|
ignore_deps=not arguments.enforce_deps,
|
|
results_directory=results_directory,
|
|
ssh_verbosity=arguments.ssh_verbosity,
|
|
ssh_options=arguments.ssh_options,
|
|
iterations=arguments.iterations,
|
|
fast_mode=arguments.fast_mode,
|
|
debug=arguments.debug,
|
|
allow_chrome_crashes=arguments.allow_chrome_crashes,
|
|
pretend=arguments.pretend,
|
|
job_retry=arguments.retry)
|
|
|
|
|
|
def _main_for_lab_run(argv, arguments):
|
|
"""
|
|
Effective entry point for lab test_that runs.
|
|
|
|
@param argv: Script command line arguments.
|
|
@param arguments: Parsed command line arguments.
|
|
"""
|
|
autotest_path = os.path.realpath(os.path.join(
|
|
os.path.dirname(os.path.realpath(__file__)),
|
|
'..',
|
|
))
|
|
command = [os.path.join(autotest_path, 'site_utils',
|
|
'run_suite.py'),
|
|
'--board=%s' % (arguments.board,),
|
|
'--build=%s' % (arguments.build,),
|
|
'--model=%s' % (arguments.model,),
|
|
'--suite_name=%s' % 'test_that_wrapper',
|
|
'--pool=%s' % (arguments.pool,),
|
|
'--max_runtime_mins=%s' % str(arguments.max_runtime_mins),
|
|
'--suite_args=%s'
|
|
% repr({'tests': _suite_arg_tests(argv)})]
|
|
if arguments.web:
|
|
command.extend(['--web=%s' % (arguments.web,)])
|
|
logging.info('About to start lab suite with command %s.', command)
|
|
return subprocess.call(command)
|
|
|
|
|
|
def _suite_arg_tests(argv):
|
|
"""
|
|
Construct a list of tests to pass into suite_args.
|
|
|
|
This is passed in suite_args to run_suite for running a test in the
|
|
lab.
|
|
|
|
@param argv: Remote Script command line arguments.
|
|
"""
|
|
arguments = parse_arguments(argv)
|
|
return arguments.tests
|
|
|
|
|
|
def main(argv):
|
|
"""
|
|
Entry point for test_that script.
|
|
|
|
@param argv: arguments list
|
|
"""
|
|
arguments, remote_argv = _parse_arguments_internal(argv)
|
|
try:
|
|
validate_arguments(arguments)
|
|
except ValueError as err:
|
|
print(('Invalid arguments. %s' % str(err)), file=sys.stderr)
|
|
return 1
|
|
|
|
if arguments.remote == ':lab:':
|
|
return _main_for_lab_run(remote_argv, arguments)
|
|
else:
|
|
return _main_for_local_run(argv, arguments)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main(sys.argv[1:]))
|