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