android_ACTS.py revision 074d2948ced2d50ea30836abd071e71075978d26
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 json 6import logging 7import os 8import sys 9 10import common 11from autotest_lib.client.common_lib import error 12from autotest_lib.client.common_lib import global_config 13from autotest_lib.client.common_lib.cros import dev_server 14from autotest_lib.server import adb_utils 15from autotest_lib.server import afe_utils 16from autotest_lib.server import constants 17from autotest_lib.server import test 18from autotest_lib.server.cros import dnsname_mangler 19from autotest_lib.site_utils import sponge_utils 20 21CONFIG_FOLDER_LOCATION = global_config.global_config.get_config_value( 22 'ACTS', 'acts_config_folder', default='') 23 24TEST_CONFIG_FILE_FOLDER = os.path.join(CONFIG_FOLDER_LOCATION, 25 'autotest_config') 26TEST_CAMPAIGN_FILE_FOLDER = os.path.join(CONFIG_FOLDER_LOCATION, 27 'autotest_campaign') 28 29DEFAULT_TEST_RELATIVE_LOG_PATH = 'results/logs' 30 31 32class android_ACTS(test.test): 33 """Run an Android CTS test case. 34 35 Component relationship: 36 Workstation ----(ssh)---> TestStation -----(adb)-----> Android DUT 37 This code runs on Workstation. 38 """ 39 version = 1 40 acts_result_to_autotest = { 41 'PASS': 'GOOD', 42 'FAIL': 'FAIL', 43 'UNKNOWN': 'WARN', 44 'SKIP': 'ABORT' 45 } 46 47 def push_file_to_teststation(self, 48 filename, 49 input_path=None, 50 output_path=None): 51 """Ensures the file specified by a path exists on test station. If the 52 file specified by input_path does not exist, attempt to locate it in 53 ACTS dirctory. 54 55 @param filename: The name of the file relative to both the input and output 56 path. 57 @param input_path: The base path on the drone to get the file 58 from. If none, then the folder in the filename 59 is used. 60 @param output_path: The base path on the test station to put the file. 61 By default this is the temp folder. 62 63 @returns: The full path on the test station. 64 """ 65 logging.debug('Starting push for %s.', filename) 66 67 if not input_path: 68 input_path = os.path.dirname(os.path.abspath(filename)) 69 70 if not output_path: 71 output_path = os.path.join(self.ts_tempfolder, 'configs') 72 73 # Find the path on the source machine 74 full_input_path = os.path.abspath(input_path) 75 if not os.path.exists(full_input_path): 76 raise error.TestError('Invalid input path given %s' % input_path) 77 full_src_file = os.path.join(full_input_path, filename) 78 if not os.path.exists(full_src_file): 79 raise error.TestError( 80 'Invalid filename, no full path %s exists' % full_src_file) 81 82 # Find the directory part of the file 83 file_dir = os.path.dirname(filename) 84 85 # Find the path on the test station 86 dst_dir = os.path.join(output_path, file_dir) 87 88 logging.info('Pushing file %s to %s.', full_src_file, dst_dir) 89 90 self.test_station.send_file(full_src_file, dst_dir) 91 return os.path.join(output_path, filename) 92 93 def install_sl4a_apk(self): 94 """Installs sl4a on all phones connected to the testbed.""" 95 for serial, adb_host in self.testbed.get_adb_devices().iteritems(): 96 adb_utils.install_apk_from_build( 97 adb_host, 98 constants.SL4A_APK, 99 constants.SL4A_PACKAGE, 100 package_name=constants.SL4A_PACKAGE) 101 102 def download_acts(self, download_locaiton=None): 103 """Downloads acts onto a test station. 104 105 Pulls down acts.zip from from devserver and unzips it into the temp 106 directory for this test. 107 108 @param download_locaiton: The directory on the test station to download 109 acts into. 110 111 @returns: The base directory for acts. 112 """ 113 if not download_locaiton: 114 download_locaiton = self.ts_tempfolder 115 116 host = next(v for v in self.testbed.get_adb_devices().values()) 117 118 if not host: 119 raise error.TestError( 120 'No hosts defined for this test, cannot' 121 ' determine build to grab artifact from.') 122 123 job_repo_url = afe_utils.get_host_attribute( 124 host, host.job_repo_url_attribute) 125 if not job_repo_url: 126 raise error.TestError('No job repo url defined for this DUT.') 127 128 logging.info('Pulling acts from artifact, url: %s.', job_repo_url) 129 130 devserver_url = dev_server.AndroidBuildServer.get_server_url( 131 job_repo_url) 132 devserver = dev_server.AndroidBuildServer(devserver_url) 133 build_info = host.get_build_info_from_build_url(job_repo_url) 134 135 devserver.trigger_download( 136 build_info['target'], 137 build_info['build_id'], 138 build_info['branch'], 139 files='acts.zip', 140 synchronous=True) 141 142 temp_dir = download_locaiton 143 144 download_dir = '%s/acts' % temp_dir 145 146 logging.info('Downloading from dev server %s to %s', 147 job_repo_url, 148 download_dir) 149 150 host.download_file( 151 build_url=job_repo_url, 152 file='acts.zip', 153 dest_dir=temp_dir, 154 unzip=True, 155 unzip_dest=download_dir) 156 157 base_acts_dir = os.path.join( 158 download_dir, 'tools/test/connectivity/acts') 159 160 161 logging.info('ACTs downloaded to %s on test station', base_acts_dir) 162 163 return base_acts_dir 164 165 def setup_configs(self, 166 config_file, 167 additional_configs=[], 168 configs_local_location=None, 169 configs_remote_location=None): 170 """Setup config files on the test station. 171 172 Takes configuration files and uploads them onto the tests station. 173 Then does any additional setup that is needed. 174 175 @param config_file: The main config for acts to use. 176 @param additional_configs: An additional set of config files to send. 177 @param configs_local_location: Where on the drone are config files 178 being found. If none then the default 179 location defined in the autotest 180 configs is used. 181 @param configs_remote_location: The directory to store configs in, 182 by default it is a sub folder of the 183 temp folder. 184 185 @returns A list of locations on the test station where the configs where 186 uploaded. The first element is always the main config. 187 """ 188 if not configs_local_location: 189 configs_local_location = TEST_CONFIG_FILE_FOLDER 190 191 if not configs_remote_location: 192 configs_remote_location = os.path.join( 193 self.ts_tempfolder, 194 'configs') 195 196 if not config_file: 197 raise error.TestFail('A config file must be specified.') 198 199 logging.info('Pulling configs from %s.', configs_local_location) 200 201 remote_config_file = self.push_file_to_teststation( 202 config_file, 203 input_path=configs_local_location, 204 output_path=configs_remote_location) 205 206 remote_configs = [remote_config_file] 207 208 for additional_config in additional_configs: 209 remote_location = self.push_file_to_teststation( 210 additional_config, 211 input_path=configs_local_location, 212 output_path=configs_remote_location) 213 214 remote_configs.append(remote_location) 215 216 return remote_configs 217 218 def setup_campaign(self, 219 campaign_file, 220 campaign_local_location=None, 221 campagin_remote_location = None): 222 """Sets up campaign files on a test station. 223 224 Will take a local campaign file and upload it to the test station. 225 226 @param campaign_file: The name of the campaign file. 227 @param campaign_local_location: The local directory the campaign file 228 is in. 229 @param campagin_remote_location: The remote directory to place the 230 campaign file in. 231 232 @returns The remote path to the campaign file. 233 """ 234 if not campaign_local_location: 235 campaign_local_location = TEST_CAMPAIGN_FILE_FOLDER 236 237 if not campagin_remote_location: 238 campagin_remote_location = os.path.join(self.ts_tempfolder, 239 'campaigns') 240 241 logging.info('Pulling campaign from %s.', campaign_local_location) 242 243 remote_campaign_file = self.push_file_to_teststation( 244 campaign_file, 245 input_path=campaign_local_location, 246 output_path=campagin_remote_location) 247 248 return remote_campaign_file 249 250 251 def build_environment(self, base_acts_dir=None, log_path=None): 252 """Builds the environment variables for a run. 253 254 @param base_acts_dir: Where acts is stored. If none then the default 255 acts download location is used. 256 @param log_path: The path to the log file. If none then a log folder 257 under the temp folder is used. 258 259 @returns: The enviroment variables as a dictionary. 260 """ 261 if not log_path: 262 log_path = '%s/%s' % ( 263 self.ts_tempfolder, 264 DEFAULT_TEST_RELATIVE_LOG_PATH) 265 266 if not base_acts_dir: 267 base_acts_dir = self.acts_download_dir 268 269 framework_dir = os.path.join(base_acts_dir, 'framework') 270 base_test_dir = os.path.join(base_acts_dir, 'tests') 271 272 get_test_paths_result = self.test_station.run( 273 'find %s -type d' % base_test_dir) 274 test_search_dirs = get_test_paths_result.stdout.splitlines() 275 276 env = {'ACTS_TESTPATHS': ':'.join(test_search_dirs), 277 'ACTS_LOGPATH': log_path, 278 'PYTHONPATH': '%s:$PYTHONPATH' % framework_dir} 279 280 logging.info('Enviroment set to: %s', str(env)) 281 282 return env 283 284 def run_acts(self, 285 remote_config_file, 286 testing_working_dir=None, 287 remote_acts_file='framework/acts/bin/act.py', 288 test_file=None, 289 test_case=None, 290 env={}, 291 testbed_name=None, 292 timeout=7200): 293 """Runs ACTs on the test station. 294 295 Runs a test on on the test station and handles logging any details 296 about it during runtime. 297 298 @param remote_config_file: The config file on the test station to use. 299 @param testing_working_dir: The working directory to run acts from. 300 By default the acts download location is 301 used. 302 @param remote_acts_file: The acts file to use relative from the wokring 303 directory. 304 @param test_file: The -tf argument for acts. 305 @param test_case: The tc argument for acts. 306 @param env: The enviroment variables to use with acts. 307 @param testbed_name: The name of the test bed to use, if None then the 308 default testbed name for the test is used. 309 @param timeout: How long to wait for acts. 310 """ 311 if not testbed_name: 312 testbed_name = self.testbed_name 313 314 if not testing_working_dir: 315 testing_working_dir = self.acts_download_dir 316 317 exports = [] 318 for key, value in env.items(): 319 exports.append('export %s="%s"' % (key, str(value))) 320 env_setup = '; '.join(exports) 321 322 command_setup = 'cd %s' % testing_working_dir 323 act_base_cmd = 'python %s -c %s -tb %s ' % ( 324 remote_acts_file, remote_config_file, testbed_name) 325 326 if test_case and test_file: 327 raise ValueError( 328 'test_case and test_file cannot both have a value.') 329 elif test_case: 330 act_cmd = '%s -tc %s' % (act_base_cmd, test_case) 331 elif test_file: 332 full_test_file = self.setup_campaign(test_file) 333 act_cmd = '%s -tf %s' % (act_base_cmd, full_test_file) 334 else: 335 raise error.TestFail('No tests was specified!') 336 337 command_list = [command_setup, env_setup, act_cmd] 338 full_command = '; '.join(command_list) 339 340 try: 341 logging.debug('Running: %s', full_command) 342 act_result = self.test_station.run(full_command, timeout=timeout) 343 logging.debug('ACTS Output:\n%s', act_result.stdout) 344 except: 345 raise error.TestError('Unexpected error: %s', sys.exc_info()) 346 347 def post_act_cmd(self, test_case=None, test_file=None, log_path=None): 348 """Actions to take after act_cmd is finished or failed. 349 350 Actions include collect logs from test station generated by act_cmd 351 and record job results based on `test_run_summary.json`. 352 @param test_case: A string that's passed to act.py's -tc option. 353 @param test_file: A string that's passed to act.py's -tf option. 354 355 @param log_path: The path to where the log output. 356 """ 357 logging.info('Running cleanup.') 358 if not log_path: 359 log_path = os.path.join( 360 self.ts_tempfolder, 361 DEFAULT_TEST_RELATIVE_LOG_PATH) 362 363 testbed_log_path = os.path.join(log_path, self.testbed_name, 'latest') 364 365 # Following call may fail if logs are not generated by act_cmd yet. 366 # Anyhow, the test must have failed already in that case. 367 self.test_station.get_file(testbed_log_path, self.resultsdir) 368 # Load summary json file. 369 summary_path = os.path.join( 370 self.resultsdir, 'latest', 'test_run_summary.json') 371 # If the test has failed, test_run_summary.json may not exist. 372 if os.path.exists(summary_path): 373 sponge_utils.upload_results_in_test( 374 self, acts_summary=summary_path) 375 with open(summary_path, 'r') as f: 376 results = json.load(f)['Results'] 377 # Report results to Autotest. 378 for result in results: 379 verdict = self.acts_result_to_autotest[result['Result']] 380 details = result['Details'] 381 self.job.record( 382 verdict, 383 None, 384 test_case or test_file, 385 status=(details or '')) 386 else: 387 logging.debug('summary at path %s does not exist!', summary_path) 388 389 def run_once(self, 390 testbed=None, 391 config_file=None, 392 testbed_name=None, 393 test_case=None, 394 test_file=None, 395 additional_configs=[], 396 acts_timeout=7200): 397 """Run ACTS on the DUT. 398 399 Exactly one of test_case and test_file should be provided. 400 401 @param testbed: Testbed representing the testbed under test. Required. 402 @param config_file: Path to config file locally. Required. 403 @param testbed_name: A string that's passed to act.py's -tb option. 404 If testbed_name is not provided, set it to the 405 testbed's hostname without the DNS zone. 406 @param test_case: A string that's passed to act.py's -tc option. 407 @param test_file: A string that's passed to act.py's -tf option. 408 @param additional_configs: A list of paths to be copied over to 409 the test station. These files must reside 410 in the TEST_CONFIG_FILE_FOLDER. 411 @param acts_timeout: A timeout for the specific ACTS test. 412 """ 413 # setup properties 414 self.testbed = testbed 415 if not testbed_name: 416 hostname = testbed.hostname 417 if dnsname_mangler.is_ip_address(hostname): 418 self.testbed_name = hostname 419 else: 420 self.testbed_name = hostname.split('.')[0] 421 else: 422 self.testbed_name = testbed_name 423 self.test_station = testbed.teststation 424 self.ts_tempfolder = self.test_station.get_tmp_dir() 425 426 # install all needed tools 427 self.install_sl4a_apk() 428 self.acts_download_dir = self.download_acts() 429 remote_config_file = self.setup_configs( 430 config_file, additional_configs) 431 env = self.build_environment() 432 433 exception = None 434 try: 435 # launch acts 436 self.run_acts( 437 remote_config_file[0], 438 test_file=test_file, 439 test_case=test_case, 440 env=env, 441 timeout=acts_timeout) 442 except Exception, e: 443 logging.error('Unexpected error: %s', sys.exc_info()) 444 exception = e 445 446 # cleanup 447 self.post_act_cmd(test_case=test_case, test_file=test_file) 448 449 if exception: 450 raise exception 451