1#!/usr/bin/python 2# Copyright 2015 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 logging 7import os 8import sys 9import tempfile 10import time 11 12import common 13try: 14 # Ensure the chromite site-package is installed. 15 from chromite.lib import * 16except ImportError: 17 import subprocess 18 build_externals_path = os.path.join( 19 os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 20 'utils', 'build_externals.py') 21 subprocess.check_call([build_externals_path, 'chromiterepo']) 22 # Restart the script so python now finds the autotest site-packages. 23 sys.exit(os.execv(__file__, sys.argv)) 24from autotest_lib.client.common_lib import control_data 25from autotest_lib.server import utils 26from autotest_lib.server.cros.dynamic_suite import control_file_getter 27from autotest_lib.server.cros.dynamic_suite import tools 28from autotest_lib.server.hosts import moblab_host 29from autotest_lib.site_utils import brillo_common 30from autotest_lib.site_utils import run_suite 31 32 33_AFE_JOB_PAGE_TEMPLATE = ('http://%(moblab)s/afe/#tab_id=view_job&' 34 'object_id=%(job_id)s') 35_AFE_HOST_PAGE_TEMPLATE = ('http://%(moblab)s/afe/#tab_id=view_host&' 36 'object_id=%(host_id)s') 37_QUICKMERGE_LIST = ('client/', 38 'global_config.ini', 39 'server/', 40 'site_utils/', 41 'test_suites/', 42 'utils/') 43 44 45class BrilloTestExecutionError(brillo_common.BrilloTestError): 46 """An error while launching and running a test.""" 47 48 49def setup_parser(parser): 50 """Add parser options. 51 52 @param parser: argparse.ArgumentParser of the script. 53 """ 54 parser.add_argument('-t', '--test_name', 55 help="Name of the test to run. This is either the " 56 "name in the test's default control file e.g. " 57 "brillo_Gtests or a specific control file's " 58 "filename e.g. control.brillo_GtestsWhitelist.") 59 parser.add_argument('-A', '--test_arg', metavar='NAME=VAL', 60 dest='test_args', default=[], action='append', 61 help='An argument to pass to the test.') 62 63 64def quickmerge(moblab): 65 """Transfer over a subset of Autotest directories. 66 67 Quickmerge allows developers to do basic editting of tests and test 68 libraries on their workstation without requiring them to emerge and cros 69 deploy the autotest-server package. 70 71 @param moblab: MoblabHost representing the MobLab being used to launch the 72 testing. 73 """ 74 autotest_rootdir = os.path.dirname( 75 os.path.dirname(os.path.realpath(__file__))) 76 # We use rsync -R to copy a bunch of sources in a single run, adding a dot 77 # to pinpoint the relative path root. 78 rsync_cmd = ['rsync', '-aR', '--exclude', '*.pyc'] 79 ssh_cmd = 'ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null' 80 if int(moblab.port) != 22: 81 ssh_cmd += ' -p %s' % moblab.port 82 rsync_cmd += ['-e', ssh_cmd] 83 rsync_cmd += [os.path.join(autotest_rootdir, '.', path) 84 for path in _QUICKMERGE_LIST] 85 rsync_cmd.append('moblab@%s:%s' % 86 (moblab.hostname, moblab_host.AUTOTEST_INSTALL_DIR)) 87 utils.run(rsync_cmd, timeout=240) 88 89 90def add_adb_host(moblab, adb_hostname): 91 """Add the ADB host to the MobLab's host list. 92 93 @param moblab: MoblabHost representing the MobLab being used to launch the 94 tests. 95 @param adb_hostname: Hostname of the ADB Host. 96 97 @returns The adb host to use for launching tests. 98 """ 99 if not adb_hostname: 100 adb_hostname = 'localhost' 101 moblab.enable_adb_testing() 102 if all([host.hostname != adb_hostname for host in moblab.afe.get_hosts()]): 103 moblab.add_dut(adb_hostname) 104 return adb_hostname 105 106 107def schedule_test(moblab, host, test, test_args): 108 """Schedule a Brillo test. 109 110 @param moblab: MoblabHost representing the MobLab being used for testing. 111 @param host: Hostname of the DUT. 112 @param test: Test name. 113 @param test_args: Iterable of 'NAME=VAL' (strings) encoding argument 114 assignments for the test. 115 116 @returns autotest_lib.server.frontend.Job object representing the scheduled 117 job. 118 """ 119 getter = control_file_getter.FileSystemGetter( 120 [os.path.dirname(os.path.dirname(os.path.realpath(__file__)))]) 121 controlfile_conts = getter.get_control_file_contents_by_name(test) 122 123 # TODO(garnold) This should be removed and arguments injected by feeding 124 # args=test_args to create_jobs() directly once crbug.com/545572 is fixed. 125 if test_args: 126 controlfile_conts = tools.inject_vars({'args': test_args}, 127 controlfile_conts) 128 129 job = moblab.afe.create_job( 130 controlfile_conts, name=test, 131 control_type=control_data.CONTROL_TYPE_NAMES.SERVER, 132 hosts=[host], require_ssp=False) 133 logging.info('Tests Scheduled. Please wait for results.') 134 job_page = _AFE_JOB_PAGE_TEMPLATE % dict(moblab=moblab.web_address, 135 job_id=job.id) 136 logging.info('Progress can be monitored at %s', job_page) 137 logging.info('Please note tests that launch other tests (e.g. sequences) ' 138 'might complete quickly, but links to child jobs will appear ' 139 'shortly at the bottom on the page (Hit Refresh).') 140 return job 141 142 143def get_all_jobs(moblab, parent_job): 144 """Generate a list of the parent_job and it's subjobs. 145 146 @param moblab: MoblabHost representing the MobLab being used for testing. 147 @param host: Hostname of the DUT. 148 @param parent_job: autotest_lib.server.frontend.Job object representing the 149 parent job. 150 151 @returns list of autotest_lib.server.frontend.Job objects. 152 """ 153 jobs_list = moblab.afe.get_jobs(id=parent_job.id) 154 jobs_list.extend(moblab.afe.get_jobs(parent_job=parent_job.id)) 155 return jobs_list 156 157 158def wait_for_test_completion(moblab, host, parent_job): 159 """Wait for the parent job and it's subjobs to complete. 160 161 @param moblab: MoblabHost representing the MobLab being used for testing. 162 @param host: Hostname of the DUT. 163 @param parent_job: autotest_lib.server.frontend.Job object representing the 164 test job. 165 """ 166 # Wait for the sequence job and it's sub-jobs to finish, while monitoring 167 # the DUT state. As long as the DUT does not go into 'Repair Failed' the 168 # tests will complete. 169 while (moblab.afe.get_jobs(id=parent_job.id, not_yet_run=True, 170 running=True) 171 or moblab.afe.get_jobs(parent_job=parent_job.id, not_yet_run=True, 172 running=True)): 173 afe_host = moblab.afe.get_hosts(hostnames=(host,))[0] 174 if afe_host.status == 'Repair Failed': 175 moblab.afe.abort_jobs( 176 [j.id for j in get_all_jobs(moblab, parent_job)]) 177 host_page = _AFE_HOST_PAGE_TEMPLATE % dict( 178 moblab=moblab.web_address, host_id=afe_host.id) 179 raise BrilloTestExecutionError( 180 'ADB dut %s has become Repair Failed. More information ' 181 'can be found at %s' % (host, host_page)) 182 time.sleep(10) 183 184 185def copy_results(moblab, parent_job): 186 """Copy job results locally. 187 188 @param moblab: MoblabHost representing the MobLab being used for testing. 189 @param parent_job: autotest_lib.server.frontend.Job object representing the 190 parent job. 191 192 @returns Temporary directory path. 193 """ 194 tempdir = tempfile.mkdtemp(prefix='brillo_test_results') 195 for job in get_all_jobs(moblab, parent_job): 196 moblab.get_file('/usr/local/autotest/results/%d-moblab' % job.id, 197 tempdir) 198 return tempdir 199 200 201def output_results(moblab, parent_job): 202 """Output the Brillo PTS and it's subjobs results. 203 204 @param moblab: MoblabHost representing the MobLab being used for testing. 205 @param parent_job: autotest_lib.server.frontend.Job object representing the 206 test job. 207 """ 208 solo_test_run = len(moblab.afe.get_jobs(parent_job=parent_job.id)) == 0 209 rc = run_suite.ResultCollector(moblab.web_address, moblab.afe, moblab.tko, 210 None, None, parent_job.name, parent_job.id, 211 user='moblab', solo_test_run=solo_test_run) 212 rc.run() 213 rc.output_results() 214 215 216def main(args): 217 """The main function.""" 218 args = brillo_common.parse_args('Launch a Brillo test using Moblab.', 219 setup_parser=setup_parser) 220 moblab, _ = brillo_common.get_moblab_and_devserver_port(args.moblab_host) 221 222 if args.quickmerge: 223 quickmerge(moblab) 224 225 # Add the adb host object to the MobLab. 226 adb_host = add_adb_host(moblab, args.adb_host) 227 228 # Schedule the test job. 229 test_job = schedule_test(moblab, adb_host, args.test_name, args.test_args) 230 wait_for_test_completion(moblab, adb_host, test_job) 231 232 # Gather and report the test results. 233 local_results_folder = copy_results(moblab, test_job) 234 output_results(moblab, test_job) 235 logging.info('Results have also been copied locally to %s', 236 local_results_folder) 237 238 239if __name__ == '__main__': 240 try: 241 main(sys.argv) 242 sys.exit(0) 243 except brillo_common.BrilloTestError as e: 244 logging.error('Error: %s', e) 245 246 sys.exit(1) 247