1*9c5db199SXin Li# Lint as: python2, python3 2*9c5db199SXin Li# Copyright 2015 The Chromium OS Authors. All rights reserved. 3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 4*9c5db199SXin Li# found in the LICENSE file. 5*9c5db199SXin Li 6*9c5db199SXin Lifrom __future__ import absolute_import 7*9c5db199SXin Lifrom __future__ import division 8*9c5db199SXin Lifrom __future__ import print_function 9*9c5db199SXin Li 10*9c5db199SXin Liimport errno 11*9c5db199SXin Liimport os 12*9c5db199SXin Liimport re 13*9c5db199SXin Liimport shutil 14*9c5db199SXin Liimport signal 15*9c5db199SXin Liimport stat 16*9c5db199SXin Liimport subprocess 17*9c5db199SXin Liimport sys 18*9c5db199SXin Liimport tempfile 19*9c5db199SXin Liimport threading 20*9c5db199SXin Li 21*9c5db199SXin Liimport logging 22*9c5db199SXin Li# Turn the logging level to INFO before importing other autotest 23*9c5db199SXin Li# code, to avoid having failed import logging messages confuse the 24*9c5db199SXin Li# test_that user. 25*9c5db199SXin Lilogging.basicConfig(level=logging.INFO) 26*9c5db199SXin Li 27*9c5db199SXin Liimport common 28*9c5db199SXin Lifrom autotest_lib.client.common_lib.cros import retry 29*9c5db199SXin Lifrom autotest_lib.client.common_lib import logging_manager 30*9c5db199SXin Lifrom autotest_lib.server.cros.dynamic_suite import suite, constants 31*9c5db199SXin Lifrom autotest_lib.server.hosts import factory 32*9c5db199SXin Lifrom autotest_lib.server.hosts import file_store 33*9c5db199SXin Lifrom autotest_lib.server.hosts import host_info 34*9c5db199SXin Lifrom autotest_lib.server import autoserv_utils 35*9c5db199SXin Lifrom autotest_lib.server import server_logging_config 36*9c5db199SXin Lifrom autotest_lib.server import utils 37*9c5db199SXin Li 38*9c5db199SXin Li 39*9c5db199SXin Li_autoserv_proc = None 40*9c5db199SXin Li_sigint_handler_lock = threading.Lock() 41*9c5db199SXin Li 42*9c5db199SXin Li_AUTOSERV_SIGINT_TIMEOUT_SECONDS = 5 43*9c5db199SXin LiNO_BOARD = 'ad_hoc_board' 44*9c5db199SXin LiNO_BUILD = 'ad_hoc_build' 45*9c5db199SXin LiNO_MODEL = 'ad_hoc_model' 46*9c5db199SXin Li_SUITE_REGEX = r'suite:(.*)' 47*9c5db199SXin Li 48*9c5db199SXin Li_TEST_KEY_FILENAME = 'testing_rsa' 49*9c5db199SXin LiTEST_KEY_PATH = ('/mnt/host/source/src/scripts/mod_for_test_scripts/' 50*9c5db199SXin Li 'ssh_keys/%s' % _TEST_KEY_FILENAME) 51*9c5db199SXin Li 52*9c5db199SXin Li_LATEST_RESULTS_DIRECTORY = '/tmp/test_that_latest' 53*9c5db199SXin Li_HOST_INFO_SUBDIR = 'host_info_store' 54*9c5db199SXin Li 55*9c5db199SXin Li 56*9c5db199SXin Liclass TestThatRunError(Exception): 57*9c5db199SXin Li """Raised if test_that encounters something unexpected while running.""" 58*9c5db199SXin Li 59*9c5db199SXin Li 60*9c5db199SXin Liclass TestThatProvisioningError(Exception): 61*9c5db199SXin Li """Raised when it fails to provision the DUT to the requested build.""" 62*9c5db199SXin Li 63*9c5db199SXin Li 64*9c5db199SXin Liclass TestThatControlError(Exception): 65*9c5db199SXin Li """Raise when there is an issue the specified test's control file.""" 66*9c5db199SXin Li 67*9c5db199SXin Li 68*9c5db199SXin Lidef add_common_args(parser): 69*9c5db199SXin Li """ 70*9c5db199SXin Li Add common arguments for both test_that and test_droid to their parser. 71*9c5db199SXin Li 72*9c5db199SXin Li @param parser: argparse.ArgumentParser object to add arguments to. 73*9c5db199SXin Li """ 74*9c5db199SXin Li parser.add_argument('tests', nargs='+', metavar='TEST', 75*9c5db199SXin Li help='Run given test(s). Use suite:SUITE to specify ' 76*9c5db199SXin Li 'test suite. Use e:[NAME_PATTERN] to specify a ' 77*9c5db199SXin Li 'NAME-matching regular expression. Use ' 78*9c5db199SXin Li 'f:[FILE_PATTERN] to specify a filename matching ' 79*9c5db199SXin Li 'regular expression. Specified regular ' 80*9c5db199SXin Li 'expressions will be implicitly wrapped in ' 81*9c5db199SXin Li '^ and $.') 82*9c5db199SXin Li parser.add_argument('--fast', action='store_true', dest='fast_mode', 83*9c5db199SXin Li default=False, 84*9c5db199SXin Li help='Enable fast mode. This will cause test_droid ' 85*9c5db199SXin Li 'to skip time consuming steps like sysinfo and ' 86*9c5db199SXin Li 'collecting crash information.') 87*9c5db199SXin Li parser.add_argument('--args', metavar='ARGS', 88*9c5db199SXin Li help='Whitespace separated argument string to pass ' 89*9c5db199SXin Li 'through to test. Only supported for runs ' 90*9c5db199SXin Li 'against a local DUT. ' 91*9c5db199SXin Li "e.g. --args='foo=bar cat=\"in a hat\"'.") 92*9c5db199SXin Li parser.add_argument('--results_dir', metavar='RESULTS_DIR', default=None, 93*9c5db199SXin Li help='Instead of storing results in a new subdirectory' 94*9c5db199SXin Li ' of /tmp , store results in RESULTS_DIR. If ' 95*9c5db199SXin Li 'RESULTS_DIR already exists, it will be deleted.') 96*9c5db199SXin Li parser.add_argument('--pretend', action='store_true', default=False, 97*9c5db199SXin Li help='Print autoserv commands that would be run, ' 98*9c5db199SXin Li 'rather than running them.') 99*9c5db199SXin Li parser.add_argument('--no-experimental', 100*9c5db199SXin Li action='store_true', 101*9c5db199SXin Li default=False, 102*9c5db199SXin Li dest='no_experimental', 103*9c5db199SXin Li help='DEPRECATED DO NOT USE.') 104*9c5db199SXin Li parser.add_argument('--enforce-deps', action='store_true', 105*9c5db199SXin Li default=False, dest='enforce_deps', 106*9c5db199SXin Li help='Skip tests whose DEPENDENCIES can not ' 107*9c5db199SXin Li 'be satisfied.') 108*9c5db199SXin Li parser.add_argument('--debug', action='store_true', 109*9c5db199SXin Li help='Include DEBUG level messages in stdout. Note: ' 110*9c5db199SXin Li 'these messages will be included in output log ' 111*9c5db199SXin Li 'file regardless. In addition, turn on autoserv ' 112*9c5db199SXin Li 'verbosity.') 113*9c5db199SXin Li parser.add_argument('--iterations', action='store', type=int, default=1, 114*9c5db199SXin Li help='Number of times to run the tests specified.') 115*9c5db199SXin Li parser.add_argument('--ssh_verbosity', action='store', type=int, 116*9c5db199SXin Li choices=[0, 1, 2, 3], default=0, 117*9c5db199SXin Li help='Verbosity level for ssh, between 0 and 3 ' 118*9c5db199SXin Li 'inclusive.') 119*9c5db199SXin Li parser.add_argument('--ssh_options', action='store', default=None, 120*9c5db199SXin Li help='A string giving additional options to be ' 121*9c5db199SXin Li 'added to ssh commands.') 122*9c5db199SXin Li 123*9c5db199SXin Li 124*9c5db199SXin Liclass LocalSuite(suite.Suite): 125*9c5db199SXin Li """Subclass of Suite with methods for running locally""" 126*9c5db199SXin Li 127*9c5db199SXin Li def handle_local_result(self, job_id, results_dir, record): 128*9c5db199SXin Li """ 129*9c5db199SXin Li Handle recording and/or retrying a completed job run locally. 130*9c5db199SXin Li 131*9c5db199SXin Li @param job_id: int ID of job 132*9c5db199SXin Li @param results_dir: absolute path where test results were stored. 133*9c5db199SXin Li @param record: callable that records job status 134*9c5db199SXin Li 135*9c5db199SXin Li @returns: new job_id if a job was scheduled for retry, None otherwise. 136*9c5db199SXin Li """ 137*9c5db199SXin Li logging.debug('Parsing test results for job %s',job_id) 138*9c5db199SXin Li code = generate_report(results_dir, just_status_code=True) 139*9c5db199SXin Li if not self._retry_handler: 140*9c5db199SXin Li return None 141*9c5db199SXin Li logging.debug('Handling result of job %s',job_id) 142*9c5db199SXin Li logging.debug(self._retry_handler._retry_map) 143*9c5db199SXin Li if code == 0: 144*9c5db199SXin Li logging.debug('All tests for job %s succeeded, no retry', job_id) 145*9c5db199SXin Li if self._retry_handler.job_present(job_id): 146*9c5db199SXin Li self._retry_handler.set_attempted(job_id) 147*9c5db199SXin Li return None 148*9c5db199SXin Li 149*9c5db199SXin Li new_job_id = None 150*9c5db199SXin Li go_ahead = (self._job_retry and 151*9c5db199SXin Li self._retry_handler._should_retry_local_job(job_id)) 152*9c5db199SXin Li if go_ahead: 153*9c5db199SXin Li new_job_id = self._retry_local_result(job_id, record) 154*9c5db199SXin Li return new_job_id 155*9c5db199SXin Li 156*9c5db199SXin Li def _retry_local_result(self, job_id, record): 157*9c5db199SXin Li """ 158*9c5db199SXin Li Retry a test job by id. 159*9c5db199SXin Li 160*9c5db199SXin Li @param job_id: int ID of job 161*9c5db199SXin Li @param record: callable that records job status. 162*9c5db199SXin Li prototype: 163*9c5db199SXin Li record(base_job.status_log_entry) 164*9c5db199SXin Li 165*9c5db199SXin Li @returns: new job_id if a job was scheduled for retry, None otherwise. 166*9c5db199SXin Li """ 167*9c5db199SXin Li test = self._jobs_to_tests[job_id] 168*9c5db199SXin Li logging.debug('Attempting to retry job %s, test %s', job_id, test.name) 169*9c5db199SXin Li test.fast = False 170*9c5db199SXin Li new_job = self._schedule_test( 171*9c5db199SXin Li record=record, test=test, retry_for=job_id) 172*9c5db199SXin Li if new_job: 173*9c5db199SXin Li return new_job.id 174*9c5db199SXin Li return None 175*9c5db199SXin Li 176*9c5db199SXin Li def test_name_from_job(self, job_id): 177*9c5db199SXin Li """Find the name of the test run by a job with a given job ID.""" 178*9c5db199SXin Li if self._jobs_to_tests[job_id]: 179*9c5db199SXin Li return self._jobs_to_tests[job_id].name 180*9c5db199SXin Li 181*9c5db199SXin Li 182*9c5db199SXin Lidef _run_autoserv(command, pretend=False): 183*9c5db199SXin Li """Run autoserv command. 184*9c5db199SXin Li 185*9c5db199SXin Li Run the autoserv command and wait on it. Log the stdout. 186*9c5db199SXin Li Ensure that SIGINT signals are passed along to autoserv. 187*9c5db199SXin Li 188*9c5db199SXin Li @param command: the autoserv command to run. 189*9c5db199SXin Li @returns: exit code of the command. 190*9c5db199SXin Li 191*9c5db199SXin Li """ 192*9c5db199SXin Li if not pretend: 193*9c5db199SXin Li logging.debug('Running autoserv command: %s', command) 194*9c5db199SXin Li global _autoserv_proc 195*9c5db199SXin Li _autoserv_proc = subprocess.Popen(command, 196*9c5db199SXin Li stdout=subprocess.PIPE, 197*9c5db199SXin Li stderr=subprocess.STDOUT) 198*9c5db199SXin Li # This incantation forces unbuffered reading from stdout, 199*9c5db199SXin Li # so that autoserv output can be displayed to the user 200*9c5db199SXin Li # immediately. 201*9c5db199SXin Li for message in iter(_autoserv_proc.stdout.readline, b''): 202*9c5db199SXin Li logging.info('autoserv| %s', message.rstrip().decode('utf-8')) 203*9c5db199SXin Li _autoserv_proc.wait() 204*9c5db199SXin Li returncode = _autoserv_proc.returncode 205*9c5db199SXin Li _autoserv_proc = None 206*9c5db199SXin Li else: 207*9c5db199SXin Li logging.info('Pretend mode. Would run autoserv command: %s', 208*9c5db199SXin Li command) 209*9c5db199SXin Li returncode = 0 210*9c5db199SXin Li return returncode 211*9c5db199SXin Li 212*9c5db199SXin Li 213*9c5db199SXin Lidef run_provisioning_job(provision_label, host, info, autotest_path, 214*9c5db199SXin Li results_directory, fast_mode, 215*9c5db199SXin Li ssh_verbosity=0, ssh_options=None, 216*9c5db199SXin Li pretend=False, autoserv_verbose=False): 217*9c5db199SXin Li """Shell out to autoserv to run provisioning job. 218*9c5db199SXin Li 219*9c5db199SXin Li @param provision_label: Label to provision the machine to. 220*9c5db199SXin Li @param host: Hostname of DUT. 221*9c5db199SXin Li @param info: A host_info.HostInfo for the remote host. 222*9c5db199SXin Li @param autotest_path: Absolute path of autotest directory. 223*9c5db199SXin Li @param results_directory: Absolute path of directory to store results in. 224*9c5db199SXin Li (results will be stored in subdirectory of this). 225*9c5db199SXin Li @param fast_mode: bool to use fast mode (disables slow autotest features). 226*9c5db199SXin Li @param ssh_verbosity: SSH verbosity level, passed along to autoserv_utils 227*9c5db199SXin Li @param ssh_options: Additional ssh options to be passed to autoserv_utils 228*9c5db199SXin Li @param pretend: If True, will print out autoserv commands rather than 229*9c5db199SXin Li running them. 230*9c5db199SXin Li @param autoserv_verbose: If true, pass the --verbose flag to autoserv. 231*9c5db199SXin Li 232*9c5db199SXin Li @returns: Absolute path of directory where results were stored. 233*9c5db199SXin Li 234*9c5db199SXin Li """ 235*9c5db199SXin Li # TODO(fdeng): When running against a local DUT, autoserv 236*9c5db199SXin Li # is still hitting the AFE in the lab. 237*9c5db199SXin Li # provision_QuickProvision checks the current build of DUT by 238*9c5db199SXin Li # retrieving build info from AFE. crosbug.com/295178 239*9c5db199SXin Li results_directory = os.path.join(results_directory, 'results-provision') 240*9c5db199SXin Li _write_host_info(results_directory, _HOST_INFO_SUBDIR, host, info) 241*9c5db199SXin Li command = autoserv_utils.autoserv_run_job_command( 242*9c5db199SXin Li os.path.join(autotest_path, 'server'), 243*9c5db199SXin Li machines=host, job=None, verbose=autoserv_verbose, 244*9c5db199SXin Li results_directory=results_directory, 245*9c5db199SXin Li fast_mode=fast_mode, ssh_verbosity=ssh_verbosity, 246*9c5db199SXin Li ssh_options=ssh_options, 247*9c5db199SXin Li extra_args=['--provision', '--job-labels', provision_label], 248*9c5db199SXin Li no_console_prefix=True, 249*9c5db199SXin Li host_info_subdir=_HOST_INFO_SUBDIR) 250*9c5db199SXin Li if _run_autoserv(command, pretend) != 0: 251*9c5db199SXin Li raise TestThatProvisioningError('Command returns non-zero code: %s ' % 252*9c5db199SXin Li command) 253*9c5db199SXin Li return results_directory 254*9c5db199SXin Li 255*9c5db199SXin Li 256*9c5db199SXin Lidef run_job(job, 257*9c5db199SXin Li host, 258*9c5db199SXin Li info, 259*9c5db199SXin Li autotest_path, 260*9c5db199SXin Li results_directory, 261*9c5db199SXin Li fast_mode, 262*9c5db199SXin Li id_digits=1, 263*9c5db199SXin Li ssh_verbosity=0, 264*9c5db199SXin Li ssh_options=None, 265*9c5db199SXin Li args=None, 266*9c5db199SXin Li pretend=False, 267*9c5db199SXin Li autoserv_verbose=False, 268*9c5db199SXin Li companion_hosts=None, 269*9c5db199SXin Li dut_servers=None, 270*9c5db199SXin Li is_cft=False, 271*9c5db199SXin Li ch_info=None): 272*9c5db199SXin Li """ 273*9c5db199SXin Li Shell out to autoserv to run an individual test job. 274*9c5db199SXin Li 275*9c5db199SXin Li @param job: A Job object containing the control file contents and other 276*9c5db199SXin Li relevent metadata for this test. 277*9c5db199SXin Li @param host: Hostname of DUT to run test against. 278*9c5db199SXin Li @param info: a host_info.HostInfo for the remote host. 279*9c5db199SXin Li @param autotest_path: Absolute path of autotest directory. 280*9c5db199SXin Li @param results_directory: Absolute path of directory to store results in. 281*9c5db199SXin Li (results will be stored in subdirectory of this). 282*9c5db199SXin Li @param fast_mode: bool to use fast mode (disables slow autotest features). 283*9c5db199SXin Li @param id_digits: The minimum number of digits that job ids should be 284*9c5db199SXin Li 0-padded to when formatting as a string for results 285*9c5db199SXin Li directory. 286*9c5db199SXin Li @param ssh_verbosity: SSH verbosity level, passed along to autoserv_utils 287*9c5db199SXin Li @param ssh_options: Additional ssh options to be passed to autoserv_utils 288*9c5db199SXin Li @param args: String that should be passed as args parameter to autoserv, 289*9c5db199SXin Li and then ultimitely to test itself. 290*9c5db199SXin Li @param pretend: If True, will print out autoserv commands rather than 291*9c5db199SXin Li running them. 292*9c5db199SXin Li @param autoserv_verbose: If true, pass the --verbose flag to autoserv. 293*9c5db199SXin Li @param companion_hosts: Companion hosts for the test. 294*9c5db199SXin Li @param dut_servers: DUT servers for the test. 295*9c5db199SXin Li @param ch_info: hostinfo for companion hosts. 296*9c5db199SXin Li 297*9c5db199SXin Li @returns: a tuple, return code of the job and absolute path of directory 298*9c5db199SXin Li where results were stored. 299*9c5db199SXin Li """ 300*9c5db199SXin Li with tempfile.NamedTemporaryFile() as temp_file: 301*9c5db199SXin Li temp_file.write(job.control_file.encode()) 302*9c5db199SXin Li temp_file.flush() 303*9c5db199SXin Li 304*9c5db199SXin Li name_tail = job.ctrlname.split('/')[-1] 305*9c5db199SXin Li results_directory = os.path.join(results_directory, 306*9c5db199SXin Li 'results-%0*d-%s' % (id_digits, job.id, 307*9c5db199SXin Li name_tail)) 308*9c5db199SXin Li # Drop experimental keyval in the keval file in the job result folder. 309*9c5db199SXin Li os.makedirs(results_directory) 310*9c5db199SXin Li utils.write_keyval(results_directory, 311*9c5db199SXin Li {constants.JOB_EXPERIMENTAL_KEY: job.keyvals[ 312*9c5db199SXin Li constants.JOB_EXPERIMENTAL_KEY]}) 313*9c5db199SXin Li _write_host_info(results_directory, _HOST_INFO_SUBDIR, host, info) 314*9c5db199SXin Li 315*9c5db199SXin Li if ch_info: 316*9c5db199SXin Li for chost in companion_hosts.split(" "): 317*9c5db199SXin Li _write_host_info(results_directory, _HOST_INFO_SUBDIR, chost, 318*9c5db199SXin Li ch_info[chost], False) 319*9c5db199SXin Li 320*9c5db199SXin Li extra_args = [temp_file.name] 321*9c5db199SXin Li if args: 322*9c5db199SXin Li extra_args.extend(['--args', args]) 323*9c5db199SXin Li 324*9c5db199SXin Li command = autoserv_utils.autoserv_run_job_command( 325*9c5db199SXin Li os.path.join(autotest_path, 'server'), 326*9c5db199SXin Li machines=host, 327*9c5db199SXin Li job=job, 328*9c5db199SXin Li verbose=autoserv_verbose, 329*9c5db199SXin Li results_directory=results_directory, 330*9c5db199SXin Li fast_mode=fast_mode, 331*9c5db199SXin Li ssh_verbosity=ssh_verbosity, 332*9c5db199SXin Li ssh_options=ssh_options, 333*9c5db199SXin Li extra_args=extra_args, 334*9c5db199SXin Li no_console_prefix=True, 335*9c5db199SXin Li use_packaging=False, 336*9c5db199SXin Li host_attributes=info.attributes, 337*9c5db199SXin Li host_info_subdir=_HOST_INFO_SUBDIR, 338*9c5db199SXin Li companion_hosts=companion_hosts, 339*9c5db199SXin Li dut_servers=dut_servers, 340*9c5db199SXin Li is_cft=is_cft) 341*9c5db199SXin Li 342*9c5db199SXin Li code = _run_autoserv(command, pretend) 343*9c5db199SXin Li return code, results_directory 344*9c5db199SXin Li 345*9c5db199SXin Li 346*9c5db199SXin Lidef setup_local_afe(): 347*9c5db199SXin Li """ 348*9c5db199SXin Li Setup a local afe database and return a direct_afe object to access it. 349*9c5db199SXin Li 350*9c5db199SXin Li @returns: A autotest_lib.frontend.afe.direct_afe instance. 351*9c5db199SXin Li """ 352*9c5db199SXin Li # This import statement is delayed until now rather than running at 353*9c5db199SXin Li # module load time, because it kicks off a local sqlite :memory: backed 354*9c5db199SXin Li # database, and we don't need that unless we are doing a local run. 355*9c5db199SXin Li from autotest_lib.frontend import setup_django_lite_environment 356*9c5db199SXin Li from autotest_lib.frontend.afe import direct_afe 357*9c5db199SXin Li return direct_afe.directAFE() 358*9c5db199SXin Li 359*9c5db199SXin Li 360*9c5db199SXin Lidef get_predicate_for_test_arg(test): 361*9c5db199SXin Li """ 362*9c5db199SXin Li Gets a suite predicte function for a given command-line argument. 363*9c5db199SXin Li 364*9c5db199SXin Li @param test: String. An individual TEST command line argument, e.g. 365*9c5db199SXin Li 'login_CryptohomeMounted' or 'suite:smoke' 366*9c5db199SXin Li @returns: A (predicate, string) tuple with the necessary suite 367*9c5db199SXin Li predicate, and a description string of the suite that 368*9c5db199SXin Li this predicate will produce. 369*9c5db199SXin Li """ 370*9c5db199SXin Li suitematch = re.match(_SUITE_REGEX, test) 371*9c5db199SXin Li name_pattern_match = re.match(r'e:(.*)', test) 372*9c5db199SXin Li file_pattern_match = re.match(r'f:(.*)', test) 373*9c5db199SXin Li if suitematch: 374*9c5db199SXin Li suitename = suitematch.group(1) 375*9c5db199SXin Li return (suite.name_in_tag_predicate(suitename), 376*9c5db199SXin Li 'suite named %s' % suitename) 377*9c5db199SXin Li if name_pattern_match: 378*9c5db199SXin Li pattern = '^%s$' % name_pattern_match.group(1) 379*9c5db199SXin Li return (suite.test_name_matches_pattern_predicate(pattern), 380*9c5db199SXin Li 'suite to match name pattern %s' % pattern) 381*9c5db199SXin Li if file_pattern_match: 382*9c5db199SXin Li pattern = '^%s$' % file_pattern_match.group(1) 383*9c5db199SXin Li return (suite.test_file_matches_pattern_predicate(pattern), 384*9c5db199SXin Li 'suite to match file name pattern %s' % pattern) 385*9c5db199SXin Li return (suite.test_name_equals_predicate(test), 386*9c5db199SXin Li 'job named %s' % test) 387*9c5db199SXin Li 388*9c5db199SXin Li 389*9c5db199SXin Lidef get_predicate_for_possible_test_arg(test): 390*9c5db199SXin Li """ 391*9c5db199SXin Li Gets a suite predicte function to calculate the similarity of given test 392*9c5db199SXin Li and possible tests. 393*9c5db199SXin Li 394*9c5db199SXin Li @param test: String. An individual TEST command line argument, e.g. 395*9c5db199SXin Li 'login_CryptohomeMounted' or 'suite:smoke' 396*9c5db199SXin Li @returns: A (predicate, string) tuple with the necessary suite 397*9c5db199SXin Li predicate, and a description string of the suite that 398*9c5db199SXin Li this predicate will produce. 399*9c5db199SXin Li """ 400*9c5db199SXin Li suitematch = re.match(_SUITE_REGEX, test) 401*9c5db199SXin Li name_pattern_match = re.match(r'e:(.*)', test) 402*9c5db199SXin Li file_pattern_match = re.match(r'f:(.*)', test) 403*9c5db199SXin Li if suitematch: 404*9c5db199SXin Li suitename = suitematch.group(1) 405*9c5db199SXin Li return (suite.name_in_tag_similarity_predicate(suitename), 406*9c5db199SXin Li 'suite name similar to %s' % suitename) 407*9c5db199SXin Li if name_pattern_match: 408*9c5db199SXin Li pattern = '^%s$' % name_pattern_match.group(1) 409*9c5db199SXin Li return (suite.test_name_similarity_predicate(pattern), 410*9c5db199SXin Li 'job name similar to %s' % pattern) 411*9c5db199SXin Li if file_pattern_match: 412*9c5db199SXin Li pattern = '^%s$' % file_pattern_match.group(1) 413*9c5db199SXin Li return (suite.test_file_similarity_predicate(pattern), 414*9c5db199SXin Li 'suite to match file name similar to %s' % pattern) 415*9c5db199SXin Li return (suite.test_name_similarity_predicate(test), 416*9c5db199SXin Li 'job name similar to %s' % test) 417*9c5db199SXin Li 418*9c5db199SXin Li 419*9c5db199SXin Lidef add_ssh_identity(temp_directory, ssh_private_key=TEST_KEY_PATH): 420*9c5db199SXin Li """Add an ssh identity to the agent. 421*9c5db199SXin Li 422*9c5db199SXin Li TODO (sbasi) b/26186193: Add support for test_droid and make TEST_KEY_PATH 423*9c5db199SXin Li not ChromeOS specific. 424*9c5db199SXin Li 425*9c5db199SXin Li @param temp_directory: A directory to copy the |private key| into. 426*9c5db199SXin Li @param ssh_private_key: Path to the ssh private key to use for testing. 427*9c5db199SXin Li """ 428*9c5db199SXin Li # Add the testing key to the current ssh agent. 429*9c5db199SXin Li if 'SSH_AGENT_PID' in os.environ: 430*9c5db199SXin Li # Copy the testing key to the temp directory and make it NOT 431*9c5db199SXin Li # world-readable. Otherwise, ssh-add complains. 432*9c5db199SXin Li shutil.copy(ssh_private_key, temp_directory) 433*9c5db199SXin Li key_copy_path = os.path.join(temp_directory, 434*9c5db199SXin Li os.path.basename(ssh_private_key)) 435*9c5db199SXin Li os.chmod(key_copy_path, stat.S_IRUSR | stat.S_IWUSR) 436*9c5db199SXin Li p = subprocess.Popen(['ssh-add', key_copy_path], 437*9c5db199SXin Li stderr=subprocess.STDOUT, stdout=subprocess.PIPE) 438*9c5db199SXin Li p_out, _ = p.communicate() 439*9c5db199SXin Li for line in p_out.splitlines(): 440*9c5db199SXin Li logging.info(line) 441*9c5db199SXin Li else: 442*9c5db199SXin Li logging.warning('There appears to be no running ssh-agent. Attempting ' 443*9c5db199SXin Li 'to continue without running ssh-add, but ssh commands ' 444*9c5db199SXin Li 'may fail.') 445*9c5db199SXin Li 446*9c5db199SXin Li 447*9c5db199SXin Lidef _auto_detect_labels(remote): 448*9c5db199SXin Li """Automatically detect host labels and return them. 449*9c5db199SXin Li 450*9c5db199SXin Li Note that the label of board will not be auto-detected. 451*9c5db199SXin Li 452*9c5db199SXin Li @param remote: The hostname of the remote device. 453*9c5db199SXin Li 454*9c5db199SXin Li @returns: the detected labels as a list of strings. 455*9c5db199SXin Li """ 456*9c5db199SXin Li cros_host = factory.create_host(remote) 457*9c5db199SXin Li labels_to_create = [label for label in cros_host.get_labels() 458*9c5db199SXin Li if not label.startswith(constants.BOARD_PREFIX)] 459*9c5db199SXin Li return labels_to_create 460*9c5db199SXin Li 461*9c5db199SXin Li 462*9c5db199SXin Lidef get_all_control_files(test, autotest_path): 463*9c5db199SXin Li """Get all control files for specified test in the given autotest_path. 464*9c5db199SXin Li 465*9c5db199SXin Li @param test: name of the test or suite to fetch 466*9c5db199SXin Li @praram autotest_path: Absolute path of autotest installed in sysroot 467*9c5db199SXin Li """ 468*9c5db199SXin Li (predicate, description) = get_predicate_for_test_arg(test) 469*9c5db199SXin Li logging.info('Fetching suite for %s...', description) 470*9c5db199SXin Li return get_control_files(autotest_path=autotest_path, pred=predicate) 471*9c5db199SXin Li 472*9c5db199SXin Li 473*9c5db199SXin Lidef get_possible_tests(test, autotest_path): 474*9c5db199SXin Li fs_getter = suite.create_fs_getter(autotest_path) 475*9c5db199SXin Li 476*9c5db199SXin Li (similarity_predicate, 477*9c5db199SXin Li similarity_description) = (get_predicate_for_possible_test_arg(test)) 478*9c5db199SXin Li 479*9c5db199SXin Li logging.error('No test found, searching for possible tests with %s', 480*9c5db199SXin Li similarity_description) 481*9c5db199SXin Li possible_tests = suite.find_possible_tests(fs_getter, similarity_predicate) 482*9c5db199SXin Li raise SystemExit('Found no tests. Check your suite name, test name, ' 483*9c5db199SXin Li 'or test matching wildcard.\nDid you mean any of ' 484*9c5db199SXin Li 'following tests?\n %s' % '\n '.join(possible_tests)) 485*9c5db199SXin Li 486*9c5db199SXin Li 487*9c5db199SXin Lidef perform_local_run(autotest_path, 488*9c5db199SXin Li tests, 489*9c5db199SXin Li remote, 490*9c5db199SXin Li fast_mode, 491*9c5db199SXin Li build=NO_BUILD, 492*9c5db199SXin Li board=NO_BOARD, 493*9c5db199SXin Li model=NO_MODEL, 494*9c5db199SXin Li args=None, 495*9c5db199SXin Li pretend=False, 496*9c5db199SXin Li ignore_deps=True, 497*9c5db199SXin Li results_directory=None, 498*9c5db199SXin Li ssh_verbosity=0, 499*9c5db199SXin Li ssh_options=None, 500*9c5db199SXin Li autoserv_verbose=False, 501*9c5db199SXin Li iterations=1, 502*9c5db199SXin Li host_attributes={}, 503*9c5db199SXin Li job_retry=True, 504*9c5db199SXin Li companion_hosts=None, 505*9c5db199SXin Li minus=[], 506*9c5db199SXin Li dut_servers=None, 507*9c5db199SXin Li is_cft=False, 508*9c5db199SXin Li host_labels=None, 509*9c5db199SXin Li label=None): 510*9c5db199SXin Li """Perform local run of tests. 511*9c5db199SXin Li 512*9c5db199SXin Li This method enforces satisfaction of test dependencies for tests that are 513*9c5db199SXin Li run as a part of a suite. 514*9c5db199SXin Li 515*9c5db199SXin Li @param autotest_path: Absolute path of autotest installed in sysroot or 516*9c5db199SXin Li custom autotest path set by --autotest_dir. 517*9c5db199SXin Li @param tests: List of strings naming tests and suites to run. Suite strings 518*9c5db199SXin Li should be formed like "suite:smoke". 519*9c5db199SXin Li @param remote: Remote hostname. 520*9c5db199SXin Li @param fast_mode: bool to use fast mode (disables slow autotest features). 521*9c5db199SXin Li @param build: String specifying build for local run. 522*9c5db199SXin Li @param board: String specifying board for local run. 523*9c5db199SXin Li @param model: String specifying model for local run. 524*9c5db199SXin Li @param args: String that should be passed as args parameter to autoserv, 525*9c5db199SXin Li and then ultimitely to test itself. 526*9c5db199SXin Li @param pretend: If True, will print out autoserv commands rather than 527*9c5db199SXin Li running them. 528*9c5db199SXin Li @param results_directory: Directory to store results in. Defaults to None, 529*9c5db199SXin Li in which case results will be stored in a new 530*9c5db199SXin Li subdirectory of /tmp 531*9c5db199SXin Li @param ssh_verbosity: SSH verbosity level, passed through to 532*9c5db199SXin Li autoserv_utils. 533*9c5db199SXin Li @param ssh_options: Additional ssh options to be passed to autoserv_utils 534*9c5db199SXin Li @param autoserv_verbose: If true, pass the --verbose flag to autoserv. 535*9c5db199SXin Li @param iterations: int number of times to schedule tests. 536*9c5db199SXin Li @param host_attributes: Dict of host attributes to pass into autoserv. 537*9c5db199SXin Li @param job_retry: If False, tests will not be retried at all. 538*9c5db199SXin Li @param companion_hosts: companion hosts for the test. 539*9c5db199SXin Li @param dut_servers: dut servers for the test. 540*9c5db199SXin Li @param label: Optional label to use for the jobname. Will be appended to 541*9c5db199SXin Li the keyval file via server_job. 542*9c5db199SXin Li 543*9c5db199SXin Li @returns: A list of return codes each job that has run. Or [1] if 544*9c5db199SXin Li provision failed prior to running any jobs. 545*9c5db199SXin Li """ 546*9c5db199SXin Li args = _set_default_servo_args(args) 547*9c5db199SXin Li 548*9c5db199SXin Li # version doesn't really matter for local runs... 549*9c5db199SXin Li if not host_labels: 550*9c5db199SXin Li host_labels = [ 551*9c5db199SXin Li u'cros-version:ad_hoc_build', 552*9c5db199SXin Li u'board:%s' % board, 553*9c5db199SXin Li u'model:%s' % model 554*9c5db199SXin Li ] 555*9c5db199SXin Li if not ignore_deps: 556*9c5db199SXin Li logging.info('Auto-detecting labels for %s', remote) 557*9c5db199SXin Li # Auto-detected labels may duplicate explicitly set ones. 558*9c5db199SXin Li host_labels += list(set(_auto_detect_labels(remote))) 559*9c5db199SXin Li 560*9c5db199SXin Li else: 561*9c5db199SXin Li host_labels = host_labels.split(" ") 562*9c5db199SXin Li info = host_info.HostInfo(host_labels, host_attributes) 563*9c5db199SXin Li 564*9c5db199SXin Li # If using test_that, there needs to a hostinfo file (even if blank) 565*9c5db199SXin Li # for each host (including companions). 566*9c5db199SXin Li # TODO: Determine if we want to auto-detect labels, and/or expose 567*9c5db199SXin Li # CLI options for them (which might be required in CFT) 568*9c5db199SXin Li ch_info = {} 569*9c5db199SXin Li if companion_hosts: 570*9c5db199SXin Li for chost in companion_hosts.split(" "): 571*9c5db199SXin Li chost_labels = [] 572*9c5db199SXin Li if not ignore_deps: 573*9c5db199SXin Li logging.info('Auto-detecting labels for %s', chost) 574*9c5db199SXin Li # Auto-detected labels may duplicate explicitly set ones. 575*9c5db199SXin Li chost_labels += list(set(_auto_detect_labels(chost))) 576*9c5db199SXin Li ch_info[chost] = host_info.HostInfo(chost_labels, {}) 577*9c5db199SXin Li 578*9c5db199SXin Li job_queue = [] 579*9c5db199SXin Li test_num = 0 580*9c5db199SXin Li 581*9c5db199SXin Li m_queue = [] 582*9c5db199SXin Li for m in minus: 583*9c5db199SXin Li ctrl_files = get_all_control_files(m, autotest_path) 584*9c5db199SXin Li for ctrl in ctrl_files: 585*9c5db199SXin Li m_queue.append(ctrl) 586*9c5db199SXin Li 587*9c5db199SXin Li if iterations > 1: 588*9c5db199SXin Li logging.info("Scheduling for %s iterations", iterations) 589*9c5db199SXin Li for _ in range(iterations): 590*9c5db199SXin Li for test in tests: 591*9c5db199SXin Li ctrl_files = get_all_control_files(test, autotest_path) 592*9c5db199SXin Li if len(ctrl_files) == 0: 593*9c5db199SXin Li get_possible_tests(test, autotest_path) 594*9c5db199SXin Li for control in ctrl_files: 595*9c5db199SXin Li if any([control.name == no_run.name for no_run in m_queue]): 596*9c5db199SXin Li continue 597*9c5db199SXin Li test_num += 1 598*9c5db199SXin Li if label: 599*9c5db199SXin Li name = label 600*9c5db199SXin Li else: 601*9c5db199SXin Li name = "adhoc/{}".format(control.name) 602*9c5db199SXin Li job = SimpleJob(name=name, 603*9c5db199SXin Li owner='autotest_system', 604*9c5db199SXin Li test_num=test_num, 605*9c5db199SXin Li ctrlname=control.name) 606*9c5db199SXin Li job.set_control_file(control) 607*9c5db199SXin Li if ignore_deps: 608*9c5db199SXin Li job_queue.append(job) 609*9c5db199SXin Li elif job.deps_satisfied(host_labels): 610*9c5db199SXin Li job_queue.append(job) 611*9c5db199SXin Li _set_pyversion(job_queue) 612*9c5db199SXin Li codes = [] 613*9c5db199SXin Li job_id_digits = 0 614*9c5db199SXin Li for job in job_queue: 615*9c5db199SXin Li logging.info('%s jobs in job queue', len(job_queue)) 616*9c5db199SXin Li # could also math.log10... but for a single conversion, not worth. 617*9c5db199SXin Li job_id_digits = len(str(job.id)) 618*9c5db199SXin Li logging.debug('Running job %s of test %s', job.id, (job.name)) 619*9c5db199SXin Li code, abs_dir = run_job(job=job, 620*9c5db199SXin Li host=remote, 621*9c5db199SXin Li info=info, 622*9c5db199SXin Li autotest_path=autotest_path, 623*9c5db199SXin Li results_directory=results_directory, 624*9c5db199SXin Li fast_mode=fast_mode, 625*9c5db199SXin Li id_digits=job_id_digits, 626*9c5db199SXin Li ssh_verbosity=ssh_verbosity, 627*9c5db199SXin Li ssh_options=ssh_options, 628*9c5db199SXin Li args=args, 629*9c5db199SXin Li pretend=pretend, 630*9c5db199SXin Li autoserv_verbose=autoserv_verbose, 631*9c5db199SXin Li companion_hosts=companion_hosts, 632*9c5db199SXin Li dut_servers=dut_servers, 633*9c5db199SXin Li is_cft=is_cft, 634*9c5db199SXin Li ch_info=ch_info) 635*9c5db199SXin Li codes.append(code) 636*9c5db199SXin Li logging.debug("Code: %s, Results in %s", code, abs_dir) 637*9c5db199SXin Li 638*9c5db199SXin Li return codes 639*9c5db199SXin Li 640*9c5db199SXin Li 641*9c5db199SXin Lidef _set_default_servo_args(args): 642*9c5db199SXin Li """Add default servo arguments for backward compatibitlity. 643*9c5db199SXin Li 644*9c5db199SXin Li See crbug.com/881006 for context. Some servo related defaults were baked 645*9c5db199SXin Li into the autotest ServoHost code. These have now been deleted. A side effect 646*9c5db199SXin Li was that users of test_that relied on these defaults for some tests to work 647*9c5db199SXin Li magically in the chroot environment. 648*9c5db199SXin Li 649*9c5db199SXin Li Current plan is to add back these defaults to test_that invocations for 650*9c5db199SXin Li backwards compatibility of these use cases. There is no planned removal date 651*9c5db199SXin Li for this hack. 652*9c5db199SXin Li 653*9c5db199SXin Li @return modified args str. 654*9c5db199SXin Li """ 655*9c5db199SXin Li # args is a str with whitespace separated key=value arguments. 656*9c5db199SXin Li # Avoid parsing args here (to avoid adding another implicit constraint on 657*9c5db199SXin Li # the exact args format) by adding defaults only in the obvious cases where 658*9c5db199SXin Li # relevant keys are entirely missing. 659*9c5db199SXin Li if args is None: 660*9c5db199SXin Li args = '' 661*9c5db199SXin Li if 'servo_host' not in args: 662*9c5db199SXin Li args += ' servo_host=localhost' 663*9c5db199SXin Li if 'servo_port' not in args: 664*9c5db199SXin Li args += ' servo_port=9999' 665*9c5db199SXin Li return args 666*9c5db199SXin Li 667*9c5db199SXin Li 668*9c5db199SXin Lidef sigint_handler(signum, stack_frame): 669*9c5db199SXin Li #pylint: disable-msg=C0111 670*9c5db199SXin Li """Handle SIGINT or SIGTERM to a local test_that run. 671*9c5db199SXin Li 672*9c5db199SXin Li This handler sends a SIGINT to the running autoserv process, 673*9c5db199SXin Li if one is running, giving it up to 5 seconds to clean up and exit. After 674*9c5db199SXin Li the timeout elapses, autoserv is killed. In either case, after autoserv 675*9c5db199SXin Li exits then this process exits with status 1. 676*9c5db199SXin Li """ 677*9c5db199SXin Li # If multiple signals arrive before handler is unset, ignore duplicates 678*9c5db199SXin Li if not _sigint_handler_lock.acquire(False): 679*9c5db199SXin Li return 680*9c5db199SXin Li try: 681*9c5db199SXin Li # Ignore future signals by unsetting handler. 682*9c5db199SXin Li signal.signal(signal.SIGINT, signal.SIG_IGN) 683*9c5db199SXin Li signal.signal(signal.SIGTERM, signal.SIG_IGN) 684*9c5db199SXin Li 685*9c5db199SXin Li logging.warning('Received SIGINT or SIGTERM. Cleaning up and exiting.') 686*9c5db199SXin Li if _autoserv_proc: 687*9c5db199SXin Li logging.warning('Sending SIGINT to autoserv process. Waiting up ' 688*9c5db199SXin Li 'to %s seconds for cleanup.', 689*9c5db199SXin Li _AUTOSERV_SIGINT_TIMEOUT_SECONDS) 690*9c5db199SXin Li _autoserv_proc.send_signal(signal.SIGINT) 691*9c5db199SXin Li timed_out, _ = retry.timeout(_autoserv_proc.wait, 692*9c5db199SXin Li timeout_sec=_AUTOSERV_SIGINT_TIMEOUT_SECONDS) 693*9c5db199SXin Li if timed_out: 694*9c5db199SXin Li _autoserv_proc.kill() 695*9c5db199SXin Li logging.warning('Timed out waiting for autoserv to handle ' 696*9c5db199SXin Li 'SIGINT. Killed autoserv.') 697*9c5db199SXin Li finally: 698*9c5db199SXin Li _sigint_handler_lock.release() # this is not really necessary? 699*9c5db199SXin Li sys.exit(1) 700*9c5db199SXin Li 701*9c5db199SXin Li 702*9c5db199SXin Lidef create_results_directory(results_directory=None, board_name=None): 703*9c5db199SXin Li """Create a results directory. 704*9c5db199SXin Li 705*9c5db199SXin Li If no directory is specified this method will create and return a 706*9c5db199SXin Li temp directory to hold results. If a directory name is specified this 707*9c5db199SXin Li method will create a directory at the given path, provided it doesn't 708*9c5db199SXin Li already exist. 709*9c5db199SXin Li 710*9c5db199SXin Li @param results_directory: The path to the results_directory to create. 711*9c5db199SXin Li 712*9c5db199SXin Li @return results_directory: A path to the results_directory, ready for use. 713*9c5db199SXin Li """ 714*9c5db199SXin Li if results_directory is None: 715*9c5db199SXin Li # Create a results_directory as subdir of /tmp 716*9c5db199SXin Li dirname_prefix='test_that_results_' 717*9c5db199SXin Li if board_name is not None: 718*9c5db199SXin Li dirname_prefix += (board_name + '_') 719*9c5db199SXin Li results_directory = tempfile.mkdtemp(prefix=dirname_prefix) 720*9c5db199SXin Li else: 721*9c5db199SXin Li # Delete results_directory if it already exists. 722*9c5db199SXin Li try: 723*9c5db199SXin Li shutil.rmtree(results_directory) 724*9c5db199SXin Li except OSError as e: 725*9c5db199SXin Li if e.errno != errno.ENOENT: 726*9c5db199SXin Li raise 727*9c5db199SXin Li 728*9c5db199SXin Li # Create results_directory if it does not exist 729*9c5db199SXin Li try: 730*9c5db199SXin Li os.makedirs(results_directory) 731*9c5db199SXin Li except OSError as e: 732*9c5db199SXin Li if e.errno != errno.EEXIST: 733*9c5db199SXin Li raise 734*9c5db199SXin Li return results_directory 735*9c5db199SXin Li 736*9c5db199SXin Li 737*9c5db199SXin Lidef generate_report(directory, 738*9c5db199SXin Li allow_chrome_crashes=False, 739*9c5db199SXin Li just_status_code=False, 740*9c5db199SXin Li html_report=False, 741*9c5db199SXin Li is_cft=False): 742*9c5db199SXin Li """Parse the test result files in the given directory into a report. 743*9c5db199SXin Li 744*9c5db199SXin Li @param directory: string, the absolute path of the directory to look in 745*9c5db199SXin Li @param allow_chrome_crashes: boolean, ignore Chrome crashes in the 746*9c5db199SXin Li report. Default: False, report Chrome crashes. 747*9c5db199SXin Li @param just_status_code: boolean, skip the report and only parse the files 748*9c5db199SXin Li to determine whether there were failures. Default: False, generate report. 749*9c5db199SXin Li """ 750*9c5db199SXin Li test_report_command = [os.path.join(os.path.dirname(__file__), 751*9c5db199SXin Li 'generate_test_report')] 752*9c5db199SXin Li # Experimental test results do not influence the exit code. 753*9c5db199SXin Li test_report_command.append('--ignore_experimental_tests') 754*9c5db199SXin Li if is_cft: 755*9c5db199SXin Li test_report_command.append('--cft') 756*9c5db199SXin Li if html_report: 757*9c5db199SXin Li test_report_command.append('--html') 758*9c5db199SXin Li test_report_command.append('--html-report-dir=%s' % directory) 759*9c5db199SXin Li if allow_chrome_crashes: 760*9c5db199SXin Li test_report_command.append('--allow_chrome_crashes') 761*9c5db199SXin Li if just_status_code: 762*9c5db199SXin Li test_report_command.append('--just_status_code') 763*9c5db199SXin Li test_report_command.append(directory) 764*9c5db199SXin Li status_code = subprocess.call(test_report_command) 765*9c5db199SXin Li if not just_status_code: 766*9c5db199SXin Li with open(os.path.join(directory, 'test_report.log'), 767*9c5db199SXin Li 'w') as report_log: 768*9c5db199SXin Li subprocess.call(test_report_command, stdout=report_log) 769*9c5db199SXin Li return status_code 770*9c5db199SXin Li 771*9c5db199SXin Li 772*9c5db199SXin Lidef perform_run_from_autotest_root(autotest_path, 773*9c5db199SXin Li argv, 774*9c5db199SXin Li tests, 775*9c5db199SXin Li remote, 776*9c5db199SXin Li build=NO_BUILD, 777*9c5db199SXin Li board=NO_BOARD, 778*9c5db199SXin Li model=NO_MODEL, 779*9c5db199SXin Li args=None, 780*9c5db199SXin Li pretend=False, 781*9c5db199SXin Li ignore_deps=True, 782*9c5db199SXin Li results_directory=None, 783*9c5db199SXin Li ssh_verbosity=0, 784*9c5db199SXin Li ssh_options=None, 785*9c5db199SXin Li iterations=1, 786*9c5db199SXin Li fast_mode=False, 787*9c5db199SXin Li debug=False, 788*9c5db199SXin Li allow_chrome_crashes=False, 789*9c5db199SXin Li host_attributes={}, 790*9c5db199SXin Li job_retry=True, 791*9c5db199SXin Li companion_hosts=None, 792*9c5db199SXin Li minus=[], 793*9c5db199SXin Li dut_servers=None, 794*9c5db199SXin Li is_cft=False, 795*9c5db199SXin Li host_labels=None, 796*9c5db199SXin Li label=None): 797*9c5db199SXin Li """ 798*9c5db199SXin Li Perform a test_that run, from the |autotest_path|. 799*9c5db199SXin Li 800*9c5db199SXin Li This function is to be called from test_that/test_droid's main() script, 801*9c5db199SXin Li when tests are executed from the |autotest_path|. It handles all stages 802*9c5db199SXin Li of a test run that come after the bootstrap into |autotest_path|. 803*9c5db199SXin Li 804*9c5db199SXin Li @param autotest_path: Full absolute path to the autotest root directory. 805*9c5db199SXin Li @param argv: The arguments list, as passed to main(...) 806*9c5db199SXin Li @param tests: List of strings naming tests and suites to run. Suite strings 807*9c5db199SXin Li should be formed like "suite:smoke". 808*9c5db199SXin Li @param remote: Remote hostname. 809*9c5db199SXin Li @param build: String specifying build for local run. 810*9c5db199SXin Li @param board: String specifying board for local run. 811*9c5db199SXin Li @param model: String specifying model for local run. 812*9c5db199SXin Li @param args: String that should be passed as args parameter to autoserv, 813*9c5db199SXin Li and then ultimitely to test itself. 814*9c5db199SXin Li @param pretend: If True, will print out autoserv commands rather than 815*9c5db199SXin Li running them. 816*9c5db199SXin Li @param ignore_deps: If True, test dependencies will be ignored. 817*9c5db199SXin Li @param results_directory: Directory to store results in. Defaults to None, 818*9c5db199SXin Li in which case results will be stored in a new 819*9c5db199SXin Li subdirectory of /tmp 820*9c5db199SXin Li @param ssh_verbosity: SSH verbosity level, passed through to 821*9c5db199SXin Li autoserv_utils. 822*9c5db199SXin Li @param ssh_options: Additional ssh options to be passed to autoserv_utils 823*9c5db199SXin Li @param autoserv_verbose: If true, pass the --verbose flag to autoserv. 824*9c5db199SXin Li @param iterations: int number of times to schedule tests. 825*9c5db199SXin Li @param fast_mode: bool to use fast mode (disables slow autotest features). 826*9c5db199SXin Li @param debug: Logging and autoserv verbosity. 827*9c5db199SXin Li @param allow_chrome_crashes: If True, allow chrome crashes. 828*9c5db199SXin Li @param host_attributes: Dict of host attributes to pass into autoserv. 829*9c5db199SXin Li @param job_retry: If False, tests will not be retried at all. 830*9c5db199SXin Li @param companion_hosts: companion hosts for the test. 831*9c5db199SXin Li @param dut_servers: dut servers for the test. 832*9c5db199SXin Li @param label: Optional label to use for the jobname. Will be appended to 833*9c5db199SXin Li the keyval file via server_job. 834*9c5db199SXin Li 835*9c5db199SXin Li @return: A return code that test_that should exit with. 836*9c5db199SXin Li """ 837*9c5db199SXin Li if results_directory is None or not os.path.exists(results_directory): 838*9c5db199SXin Li raise ValueError('Expected valid results directory, got %s' % 839*9c5db199SXin Li results_directory) 840*9c5db199SXin Li 841*9c5db199SXin Li logging_manager.configure_logging( 842*9c5db199SXin Li server_logging_config.ServerLoggingConfig(), 843*9c5db199SXin Li results_dir=results_directory, 844*9c5db199SXin Li use_console=True, 845*9c5db199SXin Li verbose=debug, 846*9c5db199SXin Li debug_log_name='test_that') 847*9c5db199SXin Li logging.info('Began logging to %s', results_directory) 848*9c5db199SXin Li 849*9c5db199SXin Li logging.debug('test_that command line was: %s', argv) 850*9c5db199SXin Li 851*9c5db199SXin Li signal.signal(signal.SIGINT, sigint_handler) 852*9c5db199SXin Li signal.signal(signal.SIGTERM, sigint_handler) 853*9c5db199SXin Li 854*9c5db199SXin Li codes = perform_local_run(autotest_path, 855*9c5db199SXin Li tests, 856*9c5db199SXin Li remote, 857*9c5db199SXin Li fast_mode, 858*9c5db199SXin Li build, 859*9c5db199SXin Li board, 860*9c5db199SXin Li model, 861*9c5db199SXin Li args=args, 862*9c5db199SXin Li pretend=pretend, 863*9c5db199SXin Li ignore_deps=ignore_deps, 864*9c5db199SXin Li results_directory=results_directory, 865*9c5db199SXin Li ssh_verbosity=ssh_verbosity, 866*9c5db199SXin Li ssh_options=ssh_options, 867*9c5db199SXin Li autoserv_verbose=debug, 868*9c5db199SXin Li iterations=iterations, 869*9c5db199SXin Li host_attributes=host_attributes, 870*9c5db199SXin Li job_retry=job_retry, 871*9c5db199SXin Li companion_hosts=companion_hosts, 872*9c5db199SXin Li minus=minus, 873*9c5db199SXin Li dut_servers=dut_servers, 874*9c5db199SXin Li is_cft=is_cft, 875*9c5db199SXin Li host_labels=host_labels, 876*9c5db199SXin Li label=label) 877*9c5db199SXin Li if pretend: 878*9c5db199SXin Li logging.info('Finished pretend run. Exiting.') 879*9c5db199SXin Li return 0 880*9c5db199SXin Li 881*9c5db199SXin Li final_result = generate_report(results_directory, 882*9c5db199SXin Li allow_chrome_crashes=allow_chrome_crashes, 883*9c5db199SXin Li html_report=True, 884*9c5db199SXin Li is_cft=is_cft) 885*9c5db199SXin Li try: 886*9c5db199SXin Li os.unlink(_LATEST_RESULTS_DIRECTORY) 887*9c5db199SXin Li except OSError: 888*9c5db199SXin Li pass 889*9c5db199SXin Li link_target = os.path.relpath(results_directory, 890*9c5db199SXin Li os.path.dirname(_LATEST_RESULTS_DIRECTORY)) 891*9c5db199SXin Li if any(codes): 892*9c5db199SXin Li logging.error('Autoserv encountered unexpected errors ' 893*9c5db199SXin Li 'when executing jobs.') 894*9c5db199SXin Li final_result = final_result or 1 895*9c5db199SXin Li os.symlink(link_target, _LATEST_RESULTS_DIRECTORY) 896*9c5db199SXin Li logging.info('Finished running tests. Results can be found in %s or %s', 897*9c5db199SXin Li results_directory, _LATEST_RESULTS_DIRECTORY) 898*9c5db199SXin Li return final_result 899*9c5db199SXin Li 900*9c5db199SXin Li 901*9c5db199SXin Lidef _write_host_info(results_dir, 902*9c5db199SXin Li host_info_subdir, 903*9c5db199SXin Li hostname, 904*9c5db199SXin Li info, 905*9c5db199SXin Li new_dir=True): 906*9c5db199SXin Li """ Write HostInfo to a FileStore to be used by autoserv. 907*9c5db199SXin Li 908*9c5db199SXin Li @param results_dir: Path to the results directory. 909*9c5db199SXin Li @param host_info_subdir: Subdirectory of results directory for host info. 910*9c5db199SXin Li @param hostname: Hostname passed into autoserv. 911*9c5db199SXin Li @param info: hosts.HostInfo to write. 912*9c5db199SXin Li """ 913*9c5db199SXin Li d = os.path.join(results_dir, host_info_subdir) 914*9c5db199SXin Li if new_dir: 915*9c5db199SXin Li os.makedirs(d) 916*9c5db199SXin Li store = file_store.FileStore(os.path.join(d, '%s.store' % hostname)) 917*9c5db199SXin Li store.commit(info) 918*9c5db199SXin Li 919*9c5db199SXin Li 920*9c5db199SXin Liclass SimpleJob(object): 921*9c5db199SXin Li """ 922*9c5db199SXin Li A Simple job for running autotests without an AFE. 923*9c5db199SXin Li 924*9c5db199SXin Li The goal here is to remove the deps to frontend/afe, and their dependent 925*9c5db199SXin Li libs. Autotests will be run via 2 methods going forward: Skylab world, and 926*9c5db199SXin Li test_that. Skylab invokes autoserv directly, bypassing all of this. 927*9c5db199SXin Li test_that is a CLI, not a UI, and should be split free of the AFE libs. 928*9c5db199SXin Li """ 929*9c5db199SXin Li 930*9c5db199SXin Li def __init__(self, 931*9c5db199SXin Li owner, 932*9c5db199SXin Li name, 933*9c5db199SXin Li control_type='client', 934*9c5db199SXin Li test_num=1, 935*9c5db199SXin Li ctrlname=None): 936*9c5db199SXin Li self.owner = owner 937*9c5db199SXin Li self.name = name 938*9c5db199SXin Li self.control_type = control_type 939*9c5db199SXin Li self.id = test_num 940*9c5db199SXin Li self.keyvals = {'experimental': False} 941*9c5db199SXin Li self.dependencies = [] 942*9c5db199SXin Li self.py_version = None 943*9c5db199SXin Li self.ctrlname = ctrlname 944*9c5db199SXin Li 945*9c5db199SXin Li def set_control_file(self, control): 946*9c5db199SXin Li self.control_file = control.text 947*9c5db199SXin Li self.control_type = control.test_type.capitalize() 948*9c5db199SXin Li if hasattr(control, 'dependencies'): 949*9c5db199SXin Li self.dependencies = set(control.dependencies) 950*9c5db199SXin Li if control.py_version and control.py_version not in (2, 3): 951*9c5db199SXin Li raise TestThatControlError( 952*9c5db199SXin Li "Test py_version not compatible. Expected 2 or 3 got %s" % 953*9c5db199SXin Li control.py_version) 954*9c5db199SXin Li self.py_version = control.py_version 955*9c5db199SXin Li 956*9c5db199SXin Li def deps_satisfied(self, labels): 957*9c5db199SXin Li """Verify the deps for this job are satisfied on the given labels""" 958*9c5db199SXin Li return self.dependencies.issubset(labels) 959*9c5db199SXin Li 960*9c5db199SXin Li 961*9c5db199SXin Lidef _set_pyversion(tests): 962*9c5db199SXin Li """If there is a py_version specified, set it in the env. 963*9c5db199SXin Li 964*9c5db199SXin Li If not, set it to 2. If 2 is set, lock the entire suite into 2. 965*9c5db199SXin Li Different versions in the same suite is *not* supported. 966*9c5db199SXin Li """ 967*9c5db199SXin Li set2 = all(v.py_version == 2 for v in tests) 968*9c5db199SXin Li set3 = all(v.py_version == 3 for v in tests) 969*9c5db199SXin Li if not set2 and not set3: 970*9c5db199SXin Li return 971*9c5db199SXin Li if set2: 972*9c5db199SXin Li os.environ['PY_VERSION'] = "2" 973*9c5db199SXin Li elif set3: 974*9c5db199SXin Li os.environ['PY_VERSION'] = "3" 975*9c5db199SXin Li 976*9c5db199SXin Li 977*9c5db199SXin Lidef get_control_files(autotest_path, pred): 978*9c5db199SXin Li cf_getter = suite.create_fs_getter(autotest_path) 979*9c5db199SXin Li return list(suite.find_and_parse_tests(cf_getter, pred)) 980