1# Copyright 2015 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import errno 6import os 7import re 8import shutil 9import signal 10import stat 11import subprocess 12import sys 13import tempfile 14import threading 15 16import logging 17# Turn the logging level to INFO before importing other autotest 18# code, to avoid having failed import logging messages confuse the 19# test_that user. 20logging.basicConfig(level=logging.INFO) 21 22import common 23from autotest_lib.client.common_lib.cros import dev_server, retry 24from autotest_lib.client.common_lib import logging_manager 25from autotest_lib.server.cros.dynamic_suite import suite, constants 26from autotest_lib.server.cros import provision 27from autotest_lib.server.hosts import factory 28from autotest_lib.server import autoserv_utils 29from autotest_lib.server import server_logging_config 30from autotest_lib.server import utils 31 32 33_autoserv_proc = None 34_sigint_handler_lock = threading.Lock() 35 36_AUTOSERV_SIGINT_TIMEOUT_SECONDS = 5 37NO_BOARD = 'ad_hoc_board' 38NO_BUILD = 'ad_hoc_build' 39_SUITE_REGEX = r'suite:(.*)' 40 41_TEST_KEY_FILENAME = 'testing_rsa' 42TEST_KEY_PATH = ('/mnt/host/source/src/scripts/mod_for_test_scripts/' 43 'ssh_keys/%s' % _TEST_KEY_FILENAME) 44 45_LATEST_RESULTS_DIRECTORY = '/tmp/test_that_latest' 46 47 48class TestThatRunError(Exception): 49 """Raised if test_that encounters something unexpected while running.""" 50 51 52class TestThatProvisioningError(Exception): 53 """Raised when it fails to provision the DUT to the requested build.""" 54 55 56def add_common_args(parser): 57 """ 58 Add common arguments for both test_that and test_droid to their parser. 59 60 @param parser: argparse.ArgumentParser object to add arguments to. 61 """ 62 parser.add_argument('tests', nargs='+', metavar='TEST', 63 help='Run given test(s). Use suite:SUITE to specify ' 64 'test suite. Use e:[NAME_PATTERN] to specify a ' 65 'NAME-matching regular expression. Use ' 66 'f:[FILE_PATTERN] to specify a filename matching ' 67 'regular expression. Specified regular ' 68 'expressions will be implicitly wrapped in ' 69 '^ and $.') 70 parser.add_argument('--fast', action='store_true', dest='fast_mode', 71 default=False, 72 help='Enable fast mode. This will cause test_droid ' 73 'to skip time consuming steps like sysinfo and ' 74 'collecting crash information.') 75 parser.add_argument('--args', metavar='ARGS', 76 help='Whitespace separated argument string to pass ' 77 'through to test. Only supported for runs ' 78 'against a local DUT. ' 79 "e.g. --args='foo=bar cat=\"in a hat\"'.") 80 parser.add_argument('--results_dir', metavar='RESULTS_DIR', default=None, 81 help='Instead of storing results in a new subdirectory' 82 ' of /tmp , store results in RESULTS_DIR. If ' 83 'RESULTS_DIR already exists, it will be deleted.') 84 parser.add_argument('--pretend', action='store_true', default=False, 85 help='Print autoserv commands that would be run, ' 86 'rather than running them.') 87 parser.add_argument('--no-experimental', action='store_true', 88 default=False, dest='no_experimental', 89 help='When scheduling a suite, skip any tests marked ' 90 'as experimental. Applies only to tests scheduled' 91 ' via suite:[SUITE].') 92 parser.add_argument('--enforce-deps', action='store_true', 93 default=False, dest='enforce_deps', 94 help='Skip tests whose DEPENDENCIES can not ' 95 'be satisfied.') 96 parser.add_argument('--debug', action='store_true', 97 help='Include DEBUG level messages in stdout. Note: ' 98 'these messages will be included in output log ' 99 'file regardless. In addition, turn on autoserv ' 100 'verbosity.') 101 parser.add_argument('--iterations', action='store', type=int, default=1, 102 help='Number of times to run the tests specified.') 103 parser.add_argument('--ssh_verbosity', action='store', type=int, 104 choices=[0, 1, 2, 3], default=0, 105 help='Verbosity level for ssh, between 0 and 3 ' 106 'inclusive.') 107 parser.add_argument('--ssh_options', action='store', default=None, 108 help='A string giving additional options to be ' 109 'added to ssh commands.') 110 111 112 113def fetch_local_suite(autotest_path, suite_predicate, afe, test_arg, remote, 114 build=NO_BUILD, board=NO_BOARD, 115 results_directory=None, no_experimental=False, 116 ignore_deps=True): 117 """Create a suite from the given suite predicate. 118 119 Satisfaction of dependencies is enforced by Suite.schedule() if 120 ignore_deps is False. Note that this method assumes only one host, 121 i.e. |remote|, was added to afe. Suite.schedule() will not 122 schedule a job if none of the hosts in the afe (in our case, 123 just one host |remote|) has a label that matches a requested 124 test dependency. 125 126 @param autotest_path: Absolute path to autotest (in sysroot or 127 custom autotest directory set by --autotest_dir). 128 @param suite_predicate: callable that takes ControlData objects, and 129 returns True on those that should be in suite 130 @param afe: afe object to schedule against (typically a directAFE) 131 @param test_arg: String. An individual TEST command line argument, e.g. 132 'login_CryptohomeMounted' or 'suite:smoke'. 133 @param remote: String representing the IP of the remote host. 134 @param build: Build to schedule suite for. 135 @param board: Board to schedule suite for. 136 @param results_directory: Absolute path of directory to store results in. 137 (results will be stored in subdirectory of this). 138 @param no_experimental: Skip experimental tests when scheduling a suite. 139 @param ignore_deps: If True, test dependencies will be ignored. 140 141 @returns: A suite.Suite object. 142 143 """ 144 fs_getter = suite.Suite.create_fs_getter(autotest_path) 145 devserver = dev_server.ImageServer('') 146 my_suite = suite.Suite.create_from_predicates([suite_predicate], 147 {provision.CROS_VERSION_PREFIX: build}, 148 constants.BOARD_PREFIX + board, 149 devserver, fs_getter, afe=afe, 150 ignore_deps=ignore_deps, 151 results_dir=results_directory, forgiving_parser=False) 152 if len(my_suite.tests) == 0: 153 (similarity_predicate, similarity_description) = ( 154 get_predicate_for_possible_test_arg(test_arg)) 155 logging.error('No test found, searching for possible tests with %s', 156 similarity_description) 157 possible_tests = suite.Suite.find_possible_tests(fs_getter, 158 similarity_predicate) 159 raise ValueError('Found no tests. Check your suite name, test name, ' 160 'or test matching wildcard.\nDid you mean any of ' 161 'following tests?\n %s' % '\n '.join(possible_tests)) 162 163 if not ignore_deps: 164 # Log tests whose dependencies can't be satisfied. 165 labels = [label.name for label in 166 afe.get_labels(host__hostname=remote)] 167 for test in my_suite.tests: 168 if test.experimental and no_experimental: 169 continue 170 unsatisfiable_deps = set(test.dependencies).difference(labels) 171 if unsatisfiable_deps: 172 logging.warning('%s will be skipped, unsatisfiable ' 173 'test dependencies: %s', test.name, 174 unsatisfiable_deps) 175 return my_suite 176 177 178def _run_autoserv(command, pretend=False): 179 """Run autoserv command. 180 181 Run the autoserv command and wait on it. Log the stdout. 182 Ensure that SIGINT signals are passed along to autoserv. 183 184 @param command: the autoserv command to run. 185 @returns: exit code of the command. 186 187 """ 188 if not pretend: 189 logging.debug('Running autoserv command: %s', command) 190 global _autoserv_proc 191 _autoserv_proc = subprocess.Popen(command, 192 stdout=subprocess.PIPE, 193 stderr=subprocess.STDOUT) 194 # This incantation forces unbuffered reading from stdout, 195 # so that autoserv output can be displayed to the user 196 # immediately. 197 for message in iter(_autoserv_proc.stdout.readline, b''): 198 logging.info('autoserv| %s', message.strip()) 199 200 _autoserv_proc.wait() 201 returncode = _autoserv_proc.returncode 202 _autoserv_proc = None 203 else: 204 logging.info('Pretend mode. Would run autoserv command: %s', 205 command) 206 returncode = 0 207 return returncode 208 209 210def run_provisioning_job(provision_label, host, autotest_path, 211 results_directory, fast_mode, 212 ssh_verbosity=0, ssh_options=None, 213 pretend=False, autoserv_verbose=False): 214 """Shell out to autoserv to run provisioning job. 215 216 @param provision_label: Label to provision the machine to. 217 @param host: Hostname of DUT. 218 @param autotest_path: Absolute path of autotest directory. 219 @param results_directory: Absolute path of directory to store results in. 220 (results will be stored in subdirectory of this). 221 @param fast_mode: bool to use fast mode (disables slow autotest features). 222 @param ssh_verbosity: SSH verbosity level, passed along to autoserv_utils 223 @param ssh_options: Additional ssh options to be passed to autoserv_utils 224 @param pretend: If True, will print out autoserv commands rather than 225 running them. 226 @param autoserv_verbose: If true, pass the --verbose flag to autoserv. 227 228 @returns: Absolute path of directory where results were stored. 229 230 """ 231 # TODO(fdeng): When running against a local DUT, autoserv 232 # is still hitting the AFE in the lab. 233 # provision_AutoUpdate checks the current build of DUT by 234 # retrieving build info from AFE. crosbug.com/295178 235 results_directory = os.path.join(results_directory, 'results-provision') 236 command = autoserv_utils.autoserv_run_job_command( 237 os.path.join(autotest_path, 'server'), 238 machines=host, job=None, verbose=autoserv_verbose, 239 results_directory=results_directory, 240 fast_mode=fast_mode, ssh_verbosity=ssh_verbosity, 241 ssh_options=ssh_options, 242 extra_args=['--provision', '--job-labels', provision_label], 243 no_console_prefix=True) 244 if _run_autoserv(command, pretend) != 0: 245 raise TestThatProvisioningError('Command returns non-zero code: %s ' % 246 command) 247 return results_directory 248 249 250def run_job(job, host, autotest_path, results_directory, fast_mode, 251 id_digits=1, ssh_verbosity=0, ssh_options=None, 252 args=None, pretend=False, 253 autoserv_verbose=False, host_attributes={}): 254 """ 255 Shell out to autoserv to run an individual test job. 256 257 @param job: A Job object containing the control file contents and other 258 relevent metadata for this test. 259 @param host: Hostname of DUT to run test against. 260 @param autotest_path: Absolute path of autotest directory. 261 @param results_directory: Absolute path of directory to store results in. 262 (results will be stored in subdirectory of this). 263 @param fast_mode: bool to use fast mode (disables slow autotest features). 264 @param id_digits: The minimum number of digits that job ids should be 265 0-padded to when formatting as a string for results 266 directory. 267 @param ssh_verbosity: SSH verbosity level, passed along to autoserv_utils 268 @param ssh_options: Additional ssh options to be passed to autoserv_utils 269 @param args: String that should be passed as args parameter to autoserv, 270 and then ultimitely to test itself. 271 @param pretend: If True, will print out autoserv commands rather than 272 running them. 273 @param autoserv_verbose: If true, pass the --verbose flag to autoserv. 274 @param host_attributes: Dict of host attributes to pass into autoserv. 275 276 @returns: a tuple, return code of the job and absolute path of directory 277 where results were stored. 278 """ 279 with tempfile.NamedTemporaryFile() as temp_file: 280 temp_file.write(job.control_file) 281 temp_file.flush() 282 name_tail = job.name.split('/')[-1] 283 results_directory = os.path.join(results_directory, 284 'results-%0*d-%s' % (id_digits, job.id, 285 name_tail)) 286 # Drop experimental keyval in the keval file in the job result folder. 287 os.makedirs(results_directory) 288 utils.write_keyval(results_directory, 289 {constants.JOB_EXPERIMENTAL_KEY: job.keyvals[ 290 constants.JOB_EXPERIMENTAL_KEY]}) 291 extra_args = [temp_file.name] 292 if args: 293 extra_args.extend(['--args', args]) 294 295 command = autoserv_utils.autoserv_run_job_command( 296 os.path.join(autotest_path, 'server'), 297 machines=host, job=job, verbose=autoserv_verbose, 298 results_directory=results_directory, 299 fast_mode=fast_mode, ssh_verbosity=ssh_verbosity, 300 ssh_options=ssh_options, 301 extra_args=extra_args, 302 no_console_prefix=True, 303 use_packaging=False, 304 host_attributes=host_attributes) 305 306 code = _run_autoserv(command, pretend) 307 return code, results_directory 308 309 310def setup_local_afe(): 311 """ 312 Setup a local afe database and return a direct_afe object to access it. 313 314 @returns: A autotest_lib.frontend.afe.direct_afe instance. 315 """ 316 # This import statement is delayed until now rather than running at 317 # module load time, because it kicks off a local sqlite :memory: backed 318 # database, and we don't need that unless we are doing a local run. 319 from autotest_lib.frontend import setup_django_lite_environment 320 from autotest_lib.frontend.afe import direct_afe 321 return direct_afe.directAFE() 322 323 324def get_predicate_for_test_arg(test): 325 """ 326 Gets a suite predicte function for a given command-line argument. 327 328 @param test: String. An individual TEST command line argument, e.g. 329 'login_CryptohomeMounted' or 'suite:smoke' 330 @returns: A (predicate, string) tuple with the necessary suite 331 predicate, and a description string of the suite that 332 this predicate will produce. 333 """ 334 suitematch = re.match(_SUITE_REGEX, test) 335 name_pattern_match = re.match(r'e:(.*)', test) 336 file_pattern_match = re.match(r'f:(.*)', test) 337 if suitematch: 338 suitename = suitematch.group(1) 339 return (suite.Suite.name_in_tag_predicate(suitename), 340 'suite named %s' % suitename) 341 if name_pattern_match: 342 pattern = '^%s$' % name_pattern_match.group(1) 343 return (suite.Suite.test_name_matches_pattern_predicate(pattern), 344 'suite to match name pattern %s' % pattern) 345 if file_pattern_match: 346 pattern = '^%s$' % file_pattern_match.group(1) 347 return (suite.Suite.test_file_matches_pattern_predicate(pattern), 348 'suite to match file name pattern %s' % pattern) 349 return (suite.Suite.test_name_equals_predicate(test), 350 'job named %s' % test) 351 352 353def get_predicate_for_possible_test_arg(test): 354 """ 355 Gets a suite predicte function to calculate the similarity of given test 356 and possible tests. 357 358 @param test: String. An individual TEST command line argument, e.g. 359 'login_CryptohomeMounted' or 'suite:smoke' 360 @returns: A (predicate, string) tuple with the necessary suite 361 predicate, and a description string of the suite that 362 this predicate will produce. 363 """ 364 suitematch = re.match(_SUITE_REGEX, test) 365 name_pattern_match = re.match(r'e:(.*)', test) 366 file_pattern_match = re.match(r'f:(.*)', test) 367 if suitematch: 368 suitename = suitematch.group(1) 369 return (suite.Suite.name_in_tag_similarity_predicate(suitename), 370 'suite name similar to %s' % suitename) 371 if name_pattern_match: 372 pattern = '^%s$' % name_pattern_match.group(1) 373 return (suite.Suite.test_name_similarity_predicate(pattern), 374 'job name similar to %s' % pattern) 375 if file_pattern_match: 376 pattern = '^%s$' % file_pattern_match.group(1) 377 return (suite.Suite.test_file_similarity_predicate(pattern), 378 'suite to match file name similar to %s' % pattern) 379 return (suite.Suite.test_name_similarity_predicate(test), 380 'job name similar to %s' % test) 381 382 383def add_ssh_identity(temp_directory, ssh_private_key=TEST_KEY_PATH): 384 """Add an ssh identity to the agent. 385 386 TODO (sbasi) b/26186193: Add support for test_droid and make TEST_KEY_PATH 387 not Chrome OS specific. 388 389 @param temp_directory: A directory to copy the |private key| into. 390 @param ssh_private_key: Path to the ssh private key to use for testing. 391 """ 392 # Add the testing key to the current ssh agent. 393 if os.environ.has_key('SSH_AGENT_PID'): 394 # Copy the testing key to the temp directory and make it NOT 395 # world-readable. Otherwise, ssh-add complains. 396 shutil.copy(ssh_private_key, temp_directory) 397 key_copy_path = os.path.join(temp_directory, 398 os.path.basename(ssh_private_key)) 399 os.chmod(key_copy_path, stat.S_IRUSR | stat.S_IWUSR) 400 p = subprocess.Popen(['ssh-add', key_copy_path], 401 stderr=subprocess.STDOUT, stdout=subprocess.PIPE) 402 p_out, _ = p.communicate() 403 for line in p_out.splitlines(): 404 logging.info(line) 405 else: 406 logging.warning('There appears to be no running ssh-agent. Attempting ' 407 'to continue without running ssh-add, but ssh commands ' 408 'may fail.') 409 410 411def _auto_detect_labels(afe, remote): 412 """Automatically detect host labels and add them to the host in afe. 413 414 Note that the label of board will not be auto-detected. 415 This method assumes the host |remote| has already been added to afe. 416 417 @param afe: A direct_afe object used to interact with local afe database. 418 @param remote: The hostname of the remote device. 419 420 """ 421 cros_host = factory.create_host(remote) 422 labels_to_create = [label for label in cros_host.get_labels() 423 if not label.startswith(constants.BOARD_PREFIX)] 424 labels_to_add_to_afe_host = [] 425 for label in labels_to_create: 426 new_label = afe.create_label(label) 427 labels_to_add_to_afe_host.append(new_label.name) 428 hosts = afe.get_hosts(hostname=remote) 429 if not hosts: 430 raise TestThatRunError('Unexpected error: %s has not ' 431 'been added to afe.' % remote) 432 afe_host = hosts[0] 433 afe_host.add_labels(labels_to_add_to_afe_host) 434 435 436def perform_local_run(afe, autotest_path, tests, remote, fast_mode, 437 build=NO_BUILD, board=NO_BOARD, args=None, 438 pretend=False, no_experimental=False, 439 ignore_deps=True, 440 results_directory=None, ssh_verbosity=0, 441 ssh_options=None, 442 autoserv_verbose=False, 443 iterations=1, 444 host_attributes={}): 445 """Perform local run of tests. 446 447 This method enforces satisfaction of test dependencies for tests that are 448 run as a part of a suite. 449 450 @param afe: A direct_afe object used to interact with local afe database. 451 @param autotest_path: Absolute path of autotest installed in sysroot or 452 custom autotest path set by --autotest_dir. 453 @param tests: List of strings naming tests and suites to run. Suite strings 454 should be formed like "suite:smoke". 455 @param remote: Remote hostname. 456 @param fast_mode: bool to use fast mode (disables slow autotest features). 457 @param build: String specifying build for local run. 458 @param board: String specifyinb board for local run. 459 @param args: String that should be passed as args parameter to autoserv, 460 and then ultimitely to test itself. 461 @param pretend: If True, will print out autoserv commands rather than 462 running them. 463 @param no_experimental: Skip experimental tests when scheduling a suite. 464 @param ignore_deps: If True, test dependencies will be ignored. 465 @param results_directory: Directory to store results in. Defaults to None, 466 in which case results will be stored in a new 467 subdirectory of /tmp 468 @param ssh_verbosity: SSH verbosity level, passed through to 469 autoserv_utils. 470 @param ssh_options: Additional ssh options to be passed to autoserv_utils 471 @param autoserv_verbose: If true, pass the --verbose flag to autoserv. 472 @param iterations: int number of times to schedule tests. 473 @param host_attributes: Dict of host attributes to pass into autoserv. 474 475 @returns: A list of return codes each job that has run. Or [1] if 476 provision failed prior to running any jobs. 477 """ 478 # Create host in afe, add board and build labels. 479 cros_version_label = provision.cros_version_to_label(build) 480 build_label = afe.create_label(cros_version_label) 481 board_label = afe.create_label(constants.BOARD_PREFIX + board) 482 new_host = afe.create_host(remote) 483 new_host.add_labels([build_label.name, board_label.name]) 484 if not ignore_deps: 485 logging.info('Auto-detecting labels for %s', remote) 486 _auto_detect_labels(afe, remote) 487 # Provision the host to |build|. 488 if build != NO_BUILD: 489 logging.info('Provisioning %s...', cros_version_label) 490 try: 491 run_provisioning_job(cros_version_label, remote, autotest_path, 492 results_directory, fast_mode, 493 ssh_verbosity, ssh_options, 494 pretend, autoserv_verbose) 495 except TestThatProvisioningError as e: 496 logging.error('Provisioning %s to %s failed, tests are aborted, ' 497 'failure reason: %s', 498 remote, cros_version_label, e) 499 return [1] 500 501 # Create suites that will be scheduled. 502 suites_and_descriptions = [] 503 for test in tests: 504 (predicate, description) = get_predicate_for_test_arg(test) 505 logging.info('Fetching suite for %s...', description) 506 suite = fetch_local_suite(autotest_path, predicate, afe, test_arg=test, 507 remote=remote, 508 build=build, board=board, 509 results_directory=results_directory, 510 no_experimental=no_experimental, 511 ignore_deps=ignore_deps) 512 suites_and_descriptions.append((suite, description)) 513 514 # Schedule the suites, looping over iterations if necessary. 515 for iteration in range(iterations): 516 if iteration > 0: 517 logging.info('Repeating scheduling for iteration %d:', iteration) 518 519 for suite, description in suites_and_descriptions: 520 logging.info('Scheduling suite for %s...', description) 521 ntests = suite.schedule( 522 lambda log_entry, log_in_subdir=False: None, 523 add_experimental=not no_experimental) 524 logging.info('... scheduled %s job(s).', ntests) 525 526 if not afe.get_jobs(): 527 logging.info('No jobs scheduled. End of local run.') 528 return [] 529 530 last_job_id = afe.get_jobs()[-1].id 531 job_id_digits = len(str(last_job_id)) 532 codes = [] 533 for job in afe.get_jobs(): 534 code, _ = run_job(job, remote, autotest_path, results_directory, 535 fast_mode, job_id_digits, ssh_verbosity, ssh_options, args, 536 pretend, autoserv_verbose, host_attributes) 537 codes.append(code) 538 return codes 539 540 541def sigint_handler(signum, stack_frame): 542 #pylint: disable-msg=C0111 543 """Handle SIGINT or SIGTERM to a local test_that run. 544 545 This handler sends a SIGINT to the running autoserv process, 546 if one is running, giving it up to 5 seconds to clean up and exit. After 547 the timeout elapses, autoserv is killed. In either case, after autoserv 548 exits then this process exits with status 1. 549 """ 550 # If multiple signals arrive before handler is unset, ignore duplicates 551 if not _sigint_handler_lock.acquire(False): 552 return 553 try: 554 # Ignore future signals by unsetting handler. 555 signal.signal(signal.SIGINT, signal.SIG_IGN) 556 signal.signal(signal.SIGTERM, signal.SIG_IGN) 557 558 logging.warning('Received SIGINT or SIGTERM. Cleaning up and exiting.') 559 if _autoserv_proc: 560 logging.warning('Sending SIGINT to autoserv process. Waiting up ' 561 'to %s seconds for cleanup.', 562 _AUTOSERV_SIGINT_TIMEOUT_SECONDS) 563 _autoserv_proc.send_signal(signal.SIGINT) 564 timed_out, _ = retry.timeout(_autoserv_proc.wait, 565 timeout_sec=_AUTOSERV_SIGINT_TIMEOUT_SECONDS) 566 if timed_out: 567 _autoserv_proc.kill() 568 logging.warning('Timed out waiting for autoserv to handle ' 569 'SIGINT. Killed autoserv.') 570 finally: 571 _sigint_handler_lock.release() # this is not really necessary? 572 sys.exit(1) 573 574 575def create_results_directory(results_directory=None): 576 """Create a results directory. 577 578 If no directory is specified this method will create and return a 579 temp directory to hold results. If a directory name is specified this 580 method will create a directory at the given path, provided it doesn't 581 already exist. 582 583 @param results_directory: The path to the results_directory to create. 584 585 @return results_directory: A path to the results_directory, ready for use. 586 """ 587 if results_directory is None: 588 # Create a results_directory as subdir of /tmp 589 results_directory = tempfile.mkdtemp(prefix='test_that_results_') 590 else: 591 # Delete results_directory if it already exists. 592 try: 593 shutil.rmtree(results_directory) 594 except OSError as e: 595 if e.errno != errno.ENOENT: 596 raise 597 598 # Create results_directory if it does not exist 599 try: 600 os.makedirs(results_directory) 601 except OSError as e: 602 if e.errno != errno.EEXIST: 603 raise 604 return results_directory 605 606 607def perform_run_from_autotest_root(autotest_path, argv, tests, remote, 608 build=NO_BUILD, board=NO_BOARD, args=None, 609 pretend=False, no_experimental=False, 610 ignore_deps=True, 611 results_directory=None, ssh_verbosity=0, 612 ssh_options=None, 613 iterations=1, fast_mode=False, debug=False, 614 whitelist_chrome_crashes=False, 615 host_attributes={}): 616 """ 617 Perform a test_that run, from the |autotest_path|. 618 619 This function is to be called from test_that/test_droid's main() script, 620 when tests are executed from the |autotest_path|. It handles all stages 621 of a test run that come after the bootstrap into |autotest_path|. 622 623 @param autotest_path: Full absolute path to the autotest root directory. 624 @param argv: The arguments list, as passed to main(...) 625 @param tests: List of strings naming tests and suites to run. Suite strings 626 should be formed like "suite:smoke". 627 @param remote: Remote hostname. 628 @param build: String specifying build for local run. 629 @param board: String specifyinb board for local run. 630 @param args: String that should be passed as args parameter to autoserv, 631 and then ultimitely to test itself. 632 @param pretend: If True, will print out autoserv commands rather than 633 running them. 634 @param no_experimental: Skip experimental tests when scheduling a suite. 635 @param ignore_deps: If True, test dependencies will be ignored. 636 @param results_directory: Directory to store results in. Defaults to None, 637 in which case results will be stored in a new 638 subdirectory of /tmp 639 @param ssh_verbosity: SSH verbosity level, passed through to 640 autoserv_utils. 641 @param ssh_options: Additional ssh options to be passed to autoserv_utils 642 @param autoserv_verbose: If true, pass the --verbose flag to autoserv. 643 @param iterations: int number of times to schedule tests. 644 @param fast_mode: bool to use fast mode (disables slow autotest features). 645 @param debug: Logging and autoserv verbosity. 646 @param whitelist_chrome_crashes: If True, whitelist chrome crashes. 647 @param host_attributes: Dict of host attributes to pass into autoserv. 648 649 @returns: A return code that test_that should exit with. 650 """ 651 if results_directory is None or not os.path.exists(results_directory): 652 raise ValueError('Expected valid results directory, got %s' % 653 results_directory) 654 655 logging_manager.configure_logging( 656 server_logging_config.ServerLoggingConfig(), 657 results_dir=results_directory, 658 use_console=True, 659 verbose=debug, 660 debug_log_name='test_that') 661 logging.info('Began logging to %s', results_directory) 662 663 logging.debug('test_that command line was: %s', argv) 664 665 signal.signal(signal.SIGINT, sigint_handler) 666 signal.signal(signal.SIGTERM, sigint_handler) 667 668 afe = setup_local_afe() 669 codes = perform_local_run(afe, autotest_path, tests, remote, fast_mode, 670 build, board, 671 args=args, 672 pretend=pretend, 673 no_experimental=no_experimental, 674 ignore_deps=ignore_deps, 675 results_directory=results_directory, 676 ssh_verbosity=ssh_verbosity, 677 ssh_options=ssh_options, 678 autoserv_verbose=debug, 679 iterations=iterations, 680 host_attributes=host_attributes) 681 if pretend: 682 logging.info('Finished pretend run. Exiting.') 683 return 0 684 685 test_report_command = [os.path.join(os.path.dirname(__file__), 686 'generate_test_report')] 687 # Experimental test results do not influence the exit code. 688 test_report_command.append('--ignore_experimental_tests') 689 if whitelist_chrome_crashes: 690 test_report_command.append('--whitelist_chrome_crashes') 691 test_report_command.append(results_directory) 692 final_result = subprocess.call(test_report_command) 693 with open(os.path.join(results_directory, 'test_report.log'), 694 'w') as report_log: 695 subprocess.call(test_report_command, stdout=report_log) 696 try: 697 os.unlink(_LATEST_RESULTS_DIRECTORY) 698 except OSError: 699 pass 700 link_target = os.path.relpath(results_directory, 701 os.path.dirname(_LATEST_RESULTS_DIRECTORY)) 702 if any(codes): 703 logging.error('Autoserv encountered unexpected errors ' 704 'when executing jobs.') 705 final_result = final_result or 1 706 os.symlink(link_target, _LATEST_RESULTS_DIRECTORY) 707 logging.info('Finished running tests. Results can be found in %s or %s', 708 results_directory, _LATEST_RESULTS_DIRECTORY) 709 return final_result 710