1#!/usr/bin/python2 2# Copyright (c) 2013 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6import argparse 7import os 8import signal 9import subprocess 10import sys 11 12import logging 13# Turn the logging level to INFO before importing other autotest 14# code, to avoid having failed import logging messages confuse the 15# test_that user. 16logging.basicConfig(level=logging.INFO) 17 18import common 19from autotest_lib.client.common_lib import error, logging_manager 20from autotest_lib.server import server_logging_config 21from autotest_lib.server.cros.dynamic_suite import constants 22from autotest_lib.server.hosts import factory 23from autotest_lib.site_utils import test_runner_utils 24 25 26_QUICKMERGE_SCRIPTNAME = '/mnt/host/source/chromite/bin/autotest_quickmerge' 27 28 29def _get_board_from_host(remote): 30 """Get the board of the remote host. 31 32 @param remote: string representing the IP of the remote host. 33 34 @return: A string representing the board of the remote host. 35 """ 36 logging.info('Board unspecified, attempting to determine board from host.') 37 host = factory.create_host(remote) 38 try: 39 board = host.get_board().replace(constants.BOARD_PREFIX, '') 40 except error.AutoservRunError: 41 raise test_runner_utils.TestThatRunError( 42 'Cannot determine board, please specify a --board option.') 43 logging.info('Detected host board: %s', board) 44 return board 45 46 47def validate_arguments(arguments): 48 """ 49 Validates parsed arguments. 50 51 @param arguments: arguments object, as parsed by ParseArguments 52 @raises: ValueError if arguments were invalid. 53 """ 54 if arguments.remote == ':lab:': 55 if arguments.args: 56 raise ValueError('--args flag not supported when running against ' 57 ':lab:') 58 if arguments.pretend: 59 raise ValueError('--pretend flag not supported when running ' 60 'against :lab:') 61 if arguments.ssh_verbosity: 62 raise ValueError('--ssh_verbosity flag not supported when running ' 63 'against :lab:') 64 if not arguments.board or arguments.build == test_runner_utils.NO_BUILD: 65 raise ValueError('--board and --build are both required when ' 66 'running against :lab:') 67 else: 68 if arguments.web: 69 raise ValueError('--web flag not supported when running locally') 70 71 72def parse_arguments(argv): 73 """ 74 Parse command line arguments 75 76 @param argv: argument list to parse 77 @returns: parsed arguments 78 @raises SystemExit if arguments are malformed, or required arguments 79 are not present. 80 """ 81 return _parse_arguments_internal(argv)[0] 82 83 84def _parse_arguments_internal(argv): 85 """ 86 Parse command line arguments 87 88 @param argv: argument list to parse 89 @returns: tuple of parsed arguments and argv suitable for remote runs 90 @raises SystemExit if arguments are malformed, or required arguments 91 are not present. 92 """ 93 local_parser, remote_argv = parse_local_arguments(argv) 94 95 parser = argparse.ArgumentParser(description='Run remote tests.', 96 parents=[local_parser]) 97 98 parser.add_argument('remote', metavar='REMOTE', 99 help='hostname[:port] for remote device. Specify ' 100 ':lab: to run in test lab. When tests are run in ' 101 'the lab, test_that will use the client autotest ' 102 'package for the build specified with --build, ' 103 'and the lab server code rather than local ' 104 'changes.') 105 test_runner_utils.add_common_args(parser) 106 parser.add_argument('-b', '--board', metavar='BOARD', 107 action='store', 108 help='Board for which the test will run. ' 109 'Default: %(default)s') 110 parser.add_argument('-m', '--model', metavar='MODEL', default='', 111 help='Specific model the test will run against. ' 112 'Matches the model:FAKE_MODEL label for the host.') 113 parser.add_argument('-i', '--build', metavar='BUILD', 114 default=test_runner_utils.NO_BUILD, 115 help='Build to test. Device will be reimaged if ' 116 'necessary. Omit flag to skip reimage and test ' 117 'against already installed DUT image. Examples: ' 118 'link-paladin/R34-5222.0.0-rc2, ' 119 'lumpy-release/R34-5205.0.0') 120 parser.add_argument('-p', '--pool', metavar='POOL', default='suites', 121 help='Pool to use when running tests in the lab. ' 122 'Default is "suites"') 123 parser.add_argument('--autotest_dir', metavar='AUTOTEST_DIR', 124 help='Use AUTOTEST_DIR instead of normal board sysroot ' 125 'copy of autotest, and skip the quickmerge step.') 126 parser.add_argument('--no-quickmerge', action='store_true', default=False, 127 dest='no_quickmerge', 128 help='Skip the quickmerge step and use the sysroot ' 129 'as it currently is. May result in un-merged ' 130 'source tree changes not being reflected in the ' 131 'run. If using --autotest_dir, this flag is ' 132 'automatically applied.') 133 parser.add_argument('--whitelist-chrome-crashes', action='store_true', 134 default=False, dest='whitelist_chrome_crashes', 135 help='Ignore chrome crashes when producing test ' 136 'report. This flag gets passed along to the ' 137 'report generation tool.') 138 parser.add_argument('--ssh_private_key', action='store', 139 default=test_runner_utils.TEST_KEY_PATH, 140 help='Path to the private ssh key.') 141 return parser.parse_args(argv), remote_argv 142 143 144def parse_local_arguments(argv): 145 """ 146 Strips out arguments that are not to be passed through to runs. 147 148 Add any arguments that should not be passed to remote test_that runs here. 149 150 @param argv: argument list to parse. 151 @returns: tuple of local argument parser and remaining argv. 152 """ 153 parser = argparse.ArgumentParser(add_help=False) 154 parser.add_argument('-w', '--web', dest='web', default=None, 155 help='Address of a webserver to receive test requests.') 156 parser.add_argument('-x', '--max_runtime_mins', type=int, 157 dest='max_runtime_mins', default=20, 158 help='Default time allowed for the tests to complete.') 159 parser.add_argument('--no-retries', '--no-retry', 160 dest='retry', action='store_false', default=True, 161 help='For local runs only, ignore any retries ' 162 'specified in the control files.') 163 _, remaining_argv = parser.parse_known_args(argv) 164 return parser, remaining_argv 165 166 167def perform_bootstrap_into_autotest_root(arguments, autotest_path, argv): 168 """ 169 Perfoms a bootstrap to run test_that from the |autotest_path|. 170 171 This function is to be called from test_that's main() script, when 172 test_that is executed from the source tree location. It runs 173 autotest_quickmerge to update the sysroot unless arguments.no_quickmerge 174 is set. It then executes and waits on the version of test_that.py 175 in |autotest_path|. 176 177 @param arguments: A parsed arguments object, as returned from 178 test_that.parse_arguments(...). 179 @param autotest_path: Full absolute path to the autotest root directory. 180 @param argv: The arguments list, as passed to main(...) 181 182 @returns: The return code of the test_that script that was executed in 183 |autotest_path|. 184 """ 185 logging_manager.configure_logging( 186 server_logging_config.ServerLoggingConfig(), 187 use_console=True, 188 verbose=arguments.debug) 189 if arguments.no_quickmerge: 190 logging.info('Skipping quickmerge step.') 191 else: 192 logging.info('Running autotest_quickmerge step.') 193 command = [_QUICKMERGE_SCRIPTNAME, '--board='+arguments.board] 194 s = subprocess.Popen(command, 195 stdout=subprocess.PIPE, 196 stderr=subprocess.STDOUT) 197 for message in iter(s.stdout.readline, b''): 198 logging.info('quickmerge| %s', message.strip()) 199 return_code = s.wait() 200 if return_code: 201 raise test_runner_utils.TestThatRunError( 202 'autotest_quickmerge failed with error code %s.' % 203 return_code) 204 205 logging.info('Re-running test_that script in %s copy of autotest.', 206 autotest_path) 207 script_command = os.path.join(autotest_path, 'site_utils', 208 'test_that.py') 209 if not os.path.exists(script_command): 210 raise test_runner_utils.TestThatRunError( 211 'Unable to bootstrap to autotest root, %s not found.' % 212 script_command) 213 proc = None 214 def resend_sig(signum, stack_frame): 215 #pylint: disable-msg=C0111 216 if proc: 217 proc.send_signal(signum) 218 signal.signal(signal.SIGINT, resend_sig) 219 signal.signal(signal.SIGTERM, resend_sig) 220 221 proc = subprocess.Popen([script_command] + argv) 222 223 return proc.wait() 224 225 226def _main_for_local_run(argv, arguments): 227 """ 228 Effective entry point for local test_that runs. 229 230 @param argv: Script command line arguments. 231 @param arguments: Parsed command line arguments. 232 """ 233 if not os.path.exists('/etc/cros_chroot_version'): 234 print >> sys.stderr, 'For local runs, script must be run inside chroot.' 235 return 1 236 237 results_directory = test_runner_utils.create_results_directory( 238 arguments.results_dir, arguments.board) 239 test_runner_utils.add_ssh_identity(results_directory, 240 arguments.ssh_private_key) 241 arguments.results_dir = results_directory 242 243 # If the board has not been specified through --board, and is not set in the 244 # default_board file, determine the board by ssh-ing into the host. Also 245 # prepend it to argv so we can re-use it when we run test_that from the 246 # sysroot. 247 if arguments.board is None: 248 arguments.board = _get_board_from_host(arguments.remote) 249 argv = ['--board=%s' % (arguments.board,)] + argv 250 251 if arguments.autotest_dir: 252 autotest_path = arguments.autotest_dir 253 arguments.no_quickmerge = True 254 else: 255 sysroot_path = os.path.join('/build', arguments.board, '') 256 257 if not os.path.exists(sysroot_path): 258 print >> sys.stderr, ('%s does not exist. Have you run ' 259 'setup_board?' % sysroot_path) 260 return 1 261 262 path_ending = 'usr/local/build/autotest' 263 autotest_path = os.path.join(sysroot_path, path_ending) 264 265 site_utils_path = os.path.join(autotest_path, 'site_utils') 266 267 if not os.path.exists(autotest_path): 268 print >> sys.stderr, ('%s does not exist. Have you run ' 269 'build_packages? Or if you are using ' 270 '--autotest_dir, make sure it points to ' 271 'a valid autotest directory.' % autotest_path) 272 return 1 273 274 realpath = os.path.realpath(__file__) 275 site_utils_path = os.path.realpath(site_utils_path) 276 277 # If we are not running the sysroot version of script, perform 278 # a quickmerge if necessary and then re-execute 279 # the sysroot version of script with the same arguments. 280 if os.path.dirname(realpath) != site_utils_path: 281 return perform_bootstrap_into_autotest_root( 282 arguments, autotest_path, argv) 283 else: 284 return test_runner_utils.perform_run_from_autotest_root( 285 autotest_path, argv, arguments.tests, arguments.remote, 286 build=arguments.build, board=arguments.board, 287 args=arguments.args, ignore_deps=not arguments.enforce_deps, 288 results_directory=results_directory, 289 ssh_verbosity=arguments.ssh_verbosity, 290 ssh_options=arguments.ssh_options, 291 iterations=arguments.iterations, 292 fast_mode=arguments.fast_mode, debug=arguments.debug, 293 whitelist_chrome_crashes=arguments.whitelist_chrome_crashes, 294 pretend=arguments.pretend, job_retry=arguments.retry) 295 296 297def _main_for_lab_run(argv, arguments): 298 """ 299 Effective entry point for lab test_that runs. 300 301 @param argv: Script command line arguments. 302 @param arguments: Parsed command line arguments. 303 """ 304 autotest_path = os.path.realpath(os.path.join( 305 os.path.dirname(os.path.realpath(__file__)), 306 '..', 307 )) 308 command = [os.path.join(autotest_path, 'site_utils', 309 'run_suite.py'), 310 '--board=%s' % (arguments.board,), 311 '--build=%s' % (arguments.build,), 312 '--model=%s' % (arguments.model,), 313 '--suite_name=%s' % 'test_that_wrapper', 314 '--pool=%s' % (arguments.pool,), 315 '--max_runtime_mins=%s' % str(arguments.max_runtime_mins), 316 '--suite_args=%s' 317 % repr({'tests': _suite_arg_tests(argv)})] 318 if arguments.web: 319 command.extend(['--web=%s' % (arguments.web,)]) 320 logging.info('About to start lab suite with command %s.', command) 321 return subprocess.call(command) 322 323 324def _suite_arg_tests(argv): 325 """ 326 Construct a list of tests to pass into suite_args. 327 328 This is passed in suite_args to run_suite for running a test in the 329 lab. 330 331 @param argv: Remote Script command line arguments. 332 """ 333 arguments = parse_arguments(argv) 334 return arguments.tests 335 336 337def main(argv): 338 """ 339 Entry point for test_that script. 340 341 @param argv: arguments list 342 """ 343 arguments, remote_argv = _parse_arguments_internal(argv) 344 try: 345 validate_arguments(arguments) 346 except ValueError as err: 347 print >> sys.stderr, ('Invalid arguments. %s' % err.message) 348 return 1 349 350 if arguments.remote == ':lab:': 351 return _main_for_lab_run(remote_argv, arguments) 352 else: 353 return _main_for_local_run(argv, arguments) 354 355 356if __name__ == '__main__': 357 sys.exit(main(sys.argv[1:])) 358