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